mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-26 18:52:44 +01:00
shareable: Lazy allocation (#592)
Clear is called when an image becomes a render target, and it looks like Clear causes the memory spikes. Before this change, when an image is created, shared region on a GL texture is allocated. After that, the image's region is copied to non-shared region when the image becomes a rendering target. After this change, an image is not allocated on a GL texture first, and allocated only when it is necessary. Then, Clear calls can be avoided as much as possible.
This commit is contained in:
parent
916c7aac8a
commit
9c408dce7c
@ -82,6 +82,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
|
allocated bool
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
|
||||||
backend *backend
|
backend *backend
|
||||||
|
|
||||||
// If node is nil, the image is not shared.
|
// If node is nil, the image is not shared.
|
||||||
@ -89,11 +93,17 @@ type Image struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) ensureNotShared() {
|
func (i *Image) ensureNotShared() {
|
||||||
|
if !i.allocated {
|
||||||
|
i.allocate(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if i.node == nil {
|
if i.node == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
x, y, w, h := i.region()
|
x, y, w, h := i.region()
|
||||||
|
println(x, y, w, h)
|
||||||
newImg := restorable.NewImage(w, h, false)
|
newImg := restorable.NewImage(w, h, false)
|
||||||
newImg.DrawImage(i.backend.restorable, x, y, x+w, y+h, nil, nil, opengl.CompositeModeCopy, graphics.FilterNearest)
|
newImg.DrawImage(i.backend.restorable, x, y, x+w, y+h, nil, nil, opengl.CompositeModeCopy, graphics.FilterNearest)
|
||||||
|
|
||||||
@ -104,6 +114,9 @@ func (i *Image) ensureNotShared() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) region() (x, y, width, height int) {
|
func (i *Image) region() (x, y, width, height int) {
|
||||||
|
if !i.allocated {
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
if i.node == nil {
|
if i.node == nil {
|
||||||
w, h := i.backend.restorable.Size()
|
w, h := i.backend.restorable.Size()
|
||||||
return 0, 0, w, h
|
return 0, 0, w, h
|
||||||
@ -112,15 +125,17 @@ func (i *Image) region() (x, y, width, height int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Size() (width, height int) {
|
func (i *Image) Size() (width, height int) {
|
||||||
backendsM.Lock()
|
return i.width, i.height
|
||||||
defer backendsM.Unlock()
|
|
||||||
_, _, w, h := i.region()
|
|
||||||
return w, h
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) DrawImage(img *Image, sx0, sy0, sx1, sy1 int, geom *affine.GeoM, colorm *affine.ColorM, mode opengl.CompositeMode, filter graphics.Filter) {
|
func (i *Image) DrawImage(img *Image, sx0, sy0, sx1, sy1 int, geom *affine.GeoM, colorm *affine.ColorM, mode opengl.CompositeMode, filter graphics.Filter) {
|
||||||
backendsM.Lock()
|
backendsM.Lock()
|
||||||
defer backendsM.Unlock()
|
defer backendsM.Unlock()
|
||||||
|
|
||||||
|
if !img.allocated {
|
||||||
|
img.allocate(true)
|
||||||
|
}
|
||||||
|
|
||||||
i.ensureNotShared()
|
i.ensureNotShared()
|
||||||
|
|
||||||
// Compare i and img after ensuring i is not shared, or
|
// Compare i and img after ensuring i is not shared, or
|
||||||
@ -141,6 +156,10 @@ func (i *Image) ReplacePixels(p []byte) {
|
|||||||
backendsM.Lock()
|
backendsM.Lock()
|
||||||
defer backendsM.Unlock()
|
defer backendsM.Unlock()
|
||||||
|
|
||||||
|
if !i.allocated {
|
||||||
|
i.allocate(true)
|
||||||
|
}
|
||||||
|
|
||||||
x, y, w, h := i.region()
|
x, y, w, h := i.region()
|
||||||
if l := 4 * w * h; len(p) != l {
|
if l := 4 * w * h; len(p) != l {
|
||||||
panic(fmt.Sprintf("shareable: len(p) was %d but must be %d", len(p), l))
|
panic(fmt.Sprintf("shareable: len(p) was %d but must be %d", len(p), l))
|
||||||
@ -152,6 +171,10 @@ func (i *Image) At(x, y int) (color.Color, error) {
|
|||||||
backendsM.Lock()
|
backendsM.Lock()
|
||||||
defer backendsM.Unlock()
|
defer backendsM.Unlock()
|
||||||
|
|
||||||
|
if !i.allocated {
|
||||||
|
return color.RGBA{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
ox, oy, w, h := i.region()
|
ox, oy, w, h := i.region()
|
||||||
if x < 0 || y < 0 || x >= w || y >= h {
|
if x < 0 || y < 0 || x >= w || y >= h {
|
||||||
return color.RGBA{}, nil
|
return color.RGBA{}, nil
|
||||||
@ -217,33 +240,37 @@ func (i *Image) IsInvalidated() (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewImage(width, height int) *Image {
|
func NewImage(width, height int) *Image {
|
||||||
|
// Actual allocation is done lazily.
|
||||||
|
return &Image{
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) allocate(shareable bool) {
|
||||||
const (
|
const (
|
||||||
initSize = 1024
|
initSize = 1024
|
||||||
maxSize = 4096
|
maxSize = 4096
|
||||||
)
|
)
|
||||||
|
|
||||||
backendsM.Lock()
|
if !shareable || i.width > maxSize || i.height > maxSize {
|
||||||
defer backendsM.Unlock()
|
i.allocated = true
|
||||||
|
i.backend = &backend{
|
||||||
if width > maxSize || height > maxSize {
|
restorable: restorable.NewImage(i.width, i.height, false),
|
||||||
b := &backend{
|
|
||||||
restorable: restorable.NewImage(width, height, false),
|
|
||||||
}
|
|
||||||
return &Image{
|
|
||||||
backend: b,
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range theBackends {
|
for _, b := range theBackends {
|
||||||
if n, ok := b.TryAlloc(width, height); ok {
|
if n, ok := b.TryAlloc(i.width, i.height); ok {
|
||||||
return &Image{
|
i.allocated = true
|
||||||
backend: b,
|
i.backend = b
|
||||||
node: n,
|
i.node = n
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size := initSize
|
size := initSize
|
||||||
for width > size || height > size {
|
for i.width > size || i.height > size {
|
||||||
if size == maxSize {
|
if size == maxSize {
|
||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
@ -256,16 +283,15 @@ func NewImage(width, height int) *Image {
|
|||||||
}
|
}
|
||||||
theBackends = append(theBackends, b)
|
theBackends = append(theBackends, b)
|
||||||
|
|
||||||
n := b.page.Alloc(width, height)
|
n := b.page.Alloc(i.width, i.height)
|
||||||
if n == nil {
|
if n == nil {
|
||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
i := &Image{
|
i.allocated = true
|
||||||
backend: b,
|
i.backend = b
|
||||||
node: n,
|
i.node = n
|
||||||
}
|
|
||||||
runtime.SetFinalizer(i, (*Image).Dispose)
|
runtime.SetFinalizer(i, (*Image).Dispose)
|
||||||
return i
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVolatileImage(width, height int) *Image {
|
func NewVolatileImage(width, height int) *Image {
|
||||||
@ -274,6 +300,9 @@ func NewVolatileImage(width, height int) *Image {
|
|||||||
|
|
||||||
r := restorable.NewImage(width, height, true)
|
r := restorable.NewImage(width, height, true)
|
||||||
i := &Image{
|
i := &Image{
|
||||||
|
allocated: true,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
backend: &backend{
|
backend: &backend{
|
||||||
restorable: r,
|
restorable: r,
|
||||||
},
|
},
|
||||||
@ -288,6 +317,9 @@ func NewScreenFramebufferImage(width, height int) *Image {
|
|||||||
|
|
||||||
r := restorable.NewScreenFramebufferImage(width, height)
|
r := restorable.NewScreenFramebufferImage(width, height)
|
||||||
i := &Image{
|
i := &Image{
|
||||||
|
allocated: true,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
backend: &backend{
|
backend: &backend{
|
||||||
restorable: r,
|
restorable: r,
|
||||||
},
|
},
|
||||||
@ -319,12 +351,6 @@ func Restore() error {
|
|||||||
return restorable.Restore()
|
return restorable.Restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
func BackendNumForTesting() int {
|
|
||||||
backendsM.Lock()
|
|
||||||
defer backendsM.Unlock()
|
|
||||||
return len(theBackends)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Images() ([]image.Image, error) {
|
func Images() ([]image.Image, error) {
|
||||||
backendsM.Lock()
|
backendsM.Lock()
|
||||||
defer backendsM.Unlock()
|
defer backendsM.Unlock()
|
||||||
|
@ -103,26 +103,3 @@ func TestEnsureNotShared(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispose(t *testing.T) {
|
|
||||||
// There are already backend image for the offscreen or something.
|
|
||||||
// Creating a big image and the next image should be created at a new backend.
|
|
||||||
img1 := NewImage(bigSize, bigSize)
|
|
||||||
defer img1.Dispose()
|
|
||||||
|
|
||||||
if BackendNumForTesting() != 1 {
|
|
||||||
t.Errorf("BackendNumForTesting(): got: %d, want: %d", BackendNumForTesting(), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
img2 := NewImage(bigSize, bigSize)
|
|
||||||
|
|
||||||
if BackendNumForTesting() != 2 {
|
|
||||||
t.Errorf("BackendNumForTesting(): got: %d, want: %d", BackendNumForTesting(), 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
img2.Dispose()
|
|
||||||
|
|
||||||
if BackendNumForTesting() != 1 {
|
|
||||||
t.Errorf("BackendNumForTesting(): got: %d, want: %d", BackendNumForTesting(), 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user