From c01ceeaa6aeba51a0c04bf82d020a076f7180cb4 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Fri, 3 Nov 2023 15:06:22 +0900 Subject: [PATCH] ebiten: replace (*Image).Dispose with Deallocate Closes #2808 --- examples/isometric/game.go | 2 +- examples/ui/main.go | 2 +- gameforui.go | 4 +- image.go | 29 ++++- image_test.go | 119 ++++++++++++++++++- internal/atlas/image.go | 5 +- internal/atlas/image_test.go | 89 +++++++------- internal/buffered/image.go | 4 +- internal/mipmap/mipmap.go | 15 ++- internal/processtest/testdata/issue2079.go | 2 +- internal/processtest/testdata/issue2154_1.go | 4 +- internal/ui/context.go | 18 ++- internal/ui/image.go | 14 +-- shader.go | 7 +- shader_test.go | 32 ++--- 15 files changed, 241 insertions(+), 105 deletions(-) diff --git a/examples/isometric/game.go b/examples/isometric/game.go index 752cc2750..9577a2a56 100644 --- a/examples/isometric/game.go +++ b/examples/isometric/game.go @@ -192,7 +192,7 @@ func (g *Game) renderLevel(screen *ebiten.Image) { if scaleLater { if g.offscreen != nil { if g.offscreen.Bounds().Size() != screen.Bounds().Size() { - g.offscreen.Dispose() + g.offscreen.Deallocate() g.offscreen = nil } } diff --git a/examples/ui/main.go b/examples/ui/main.go index f557bf0a2..8ed55651b 100644 --- a/examples/ui/main.go +++ b/examples/ui/main.go @@ -342,7 +342,7 @@ func (t *TextBox) Draw(dst *ebiten.Image) { vw, vh := t.viewSize() w, h := t.contentBuf.Bounds().Dx(), t.contentBuf.Bounds().Dy() if vw > w || vh > h { - t.contentBuf.Dispose() + t.contentBuf.Deallocate() t.contentBuf = nil } } diff --git a/gameforui.go b/gameforui.go index 2367de43e..a6852a237 100644 --- a/gameforui.go +++ b/gameforui.go @@ -87,7 +87,7 @@ func newGameForUI(game Game, transparent bool) *gameForUI { func (g *gameForUI) NewOffscreenImage(width, height int) *ui.Image { if g.offscreen != nil { - g.offscreen.Dispose() + g.offscreen.Deallocate() g.offscreen = nil } @@ -106,7 +106,7 @@ func (g *gameForUI) NewOffscreenImage(width, height int) *ui.Image { func (g *gameForUI) NewScreenImage(width, height int) *ui.Image { if g.screen != nil { - g.screen.Dispose() + g.screen.Deallocate() g.screen = nil } diff --git a/image.go b/image.go index b97433ee0..16e840b4e 100644 --- a/image.go +++ b/image.go @@ -963,7 +963,9 @@ func (i *Image) Set(x, y int, clr color.Color) { // // If the image is a sub-image, Dispose does nothing. // -// When the image is disposed, Dispose does nothing. +// If the image is disposed, Dispose does nothing. +// +// Deprecated: as of v2.7. Use Deallocate instead. func (i *Image) Dispose() { i.copyCheck() @@ -973,10 +975,33 @@ func (i *Image) Dispose() { if i.isSubImage() { return } - i.image.MarkDisposed() + i.image.Deallocate() i.image = nil } +// Deallocate clears the image and deallocates the internal state of the image. +// Even after Deallocate is called, the image is still available. +// In this case, the image's internal state is allocated again. +// +// Usually, you don't have to call Deallocate since the internal state is automatically released by GC. +// However, if you are sure that the image is no longer used but not sure how this image object is referred, +// you can call Deallocate to make sure that the internal state is deallocated. +// +// If the image is a sub-image, Deallocate does nothing. +// +// If the image is disposed, Deallocate does nothing. +func (i *Image) Deallocate() { + i.copyCheck() + + if i.isDisposed() { + return + } + if i.isSubImage() { + return + } + i.image.Deallocate() +} + // WritePixels replaces the pixels of the image. // // The given pixels are treated as RGBA pre-multiplied alpha values. diff --git a/image_test.go b/image_test.go index d62b78d9e..41e944ec6 100644 --- a/image_test.go +++ b/image_test.go @@ -264,7 +264,7 @@ func TestImageDotByDotInversion(t *testing.T) { func TestImageWritePixels(t *testing.T) { // Create a dummy image so that the shared texture is used and origImg's position is shifted. dummyImg := ebiten.NewImageFromImage(image.NewRGBA(image.Rect(0, 0, 16, 16))) - defer dummyImg.Dispose() + defer dummyImg.Deallocate() _, origImg, err := openEbitenImage() if err != nil { @@ -335,6 +335,19 @@ func TestImageDispose(t *testing.T) { } } +func TestImageDeallocate(t *testing.T) { + img := ebiten.NewImage(16, 16) + img.Fill(color.White) + img.Deallocate() + + // The color is transparent (color.RGBA{}). + got := img.At(0, 0) + want := color.RGBA{} + if got != want { + t.Errorf("img.At(0, 0) got: %v, want: %v", got, want) + } +} + type ordered interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string } @@ -1716,6 +1729,37 @@ func TestImageAtAfterDisposingSubImage(t *testing.T) { } } +func TestImageAtAfterDeallocateSubImage(t *testing.T) { + img := ebiten.NewImage(16, 16) + img.Set(0, 0, color.White) + img.SubImage(image.Rect(0, 0, 16, 16)) + runtime.GC() + + want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff} + want64 := color.RGBA64{R: 0xffff, G: 0xffff, B: 0xffff, A: 0xffff} + got := img.At(0, 0) + if got != want { + t.Errorf("At(0,0) got: %v, want: %v", got, want) + } + got = img.RGBA64At(0, 0) + if got != want64 { + t.Errorf("RGBA64At(0,0) got: %v, want: %v", got, want) + } + + img.Set(0, 1, color.White) + sub := img.SubImage(image.Rect(0, 0, 16, 16)).(*ebiten.Image) + sub.Deallocate() + + got = img.At(0, 1) + if got != want { + t.Errorf("At(0,1) got: %v, want: %v", got, want64) + } + got = img.RGBA64At(0, 1) + if got != want64 { + t.Errorf("RGBA64At(0,1) got: %v, want: %v", got, want64) + } +} + func TestImageSubImageSubImage(t *testing.T) { img := ebiten.NewImage(16, 16) img.Fill(color.White) @@ -2263,6 +2307,14 @@ func TestImageDrawDisposedImage(t *testing.T) { dst.DrawImage(src, nil) } +func TestImageDrawDeallocatedImage(t *testing.T) { + dst := ebiten.NewImage(16, 16) + src := ebiten.NewImage(16, 16) + src.Deallocate() + // DrawImage must not panic. + dst.DrawImage(src, nil) +} + func TestImageDrawTrianglesDisposedImage(t *testing.T) { defer func() { if r := recover(); r == nil { @@ -2278,6 +2330,16 @@ func TestImageDrawTrianglesDisposedImage(t *testing.T) { dst.DrawTriangles(vs, is, src, nil) } +func TestImageDrawTrianglesDeallocateImage(t *testing.T) { + dst := ebiten.NewImage(16, 16) + src := ebiten.NewImage(16, 16) + src.Deallocate() + vs := make([]ebiten.Vertex, 4) + is := []uint16{0, 1, 2, 1, 2, 3} + // DrawTriangles must not panic. + dst.DrawTriangles(vs, is, src, nil) +} + // #1137 func BenchmarkImageDrawOver(b *testing.B) { dst := ebiten.NewImage(16, 16) @@ -4344,3 +4406,58 @@ func TestImageWritePixelAndDispose(t *testing.T) { t.Errorf("got: %v, want: %v", got, want) } } + +func TestImageWritePixelAndDeallocate(t *testing.T) { + const ( + w = 16 + h = 16 + ) + img := ebiten.NewImage(w, h) + pix := make([]byte, 4*w*h) + for i := range pix { + pix[i] = 0xff + } + img.WritePixels(pix) + img.Deallocate() + + // Confirm that any pixel information is cleared after Dealocate is called. + if got, want := img.At(0, 0), (color.RGBA{}); got != want { + t.Errorf("got: %v, want: %v", got, want) + } +} + +func TestImageDrawImageAfterDeallocation(t *testing.T) { + src, _, err := openEbitenImage() + if err != nil { + t.Fatal(err) + return + } + + w, h := src.Bounds().Dx(), src.Bounds().Dy() + dst := ebiten.NewImage(w, h) + + dst.DrawImage(src, nil) + for j := 0; j < h; j++ { + for i := 0; i < w; i++ { + got := dst.At(i, j) + want := src.At(i, j) + if got != want { + t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want) + } + } + } + + // Even after deallocating the image, the image is still available. + dst.Deallocate() + + dst.DrawImage(src, nil) + for j := 0; j < h; j++ { + for i := 0; i < w; i++ { + got := dst.At(i, j) + want := src.At(i, j) + if got != want { + t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want) + } + } + } +} diff --git a/internal/atlas/image.go b/internal/atlas/image.go index 146f299d8..e85f1c320 100644 --- a/internal/atlas/image.go +++ b/internal/atlas/image.go @@ -542,8 +542,9 @@ func (i *Image) readPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte return i.backend.restorable.ReadPixels(graphicsDriver, pixels, region.Add(r.Min)) } -// MarkDisposed marks the image as disposed. -func (i *Image) MarkDisposed() { +// Deallocate deallocates the internal state. +// Even after this call, the image is still available as a new cleared image. +func (i *Image) Deallocate() { backendsM.Lock() defer backendsM.Unlock() diff --git a/internal/atlas/image_test.go b/internal/atlas/image_test.go index f0efd545d..3d7093589 100644 --- a/internal/atlas/image_test.go +++ b/internal/atlas/image_test.go @@ -62,25 +62,25 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) { // Create img1 and img2 with this size so that the next images are allocated // with non-upper-left location. img1 := atlas.NewImage(bigSize, 100, atlas.ImageTypeRegular) - defer img1.MarkDisposed() + defer img1.Deallocate() // Ensure img1's region is allocated. img1.WritePixels(make([]byte, 4*bigSize*100), image.Rect(0, 0, bigSize, 100)) img2 := atlas.NewImage(100, bigSize, atlas.ImageTypeRegular) - defer img2.MarkDisposed() + defer img2.Deallocate() img2.WritePixels(make([]byte, 4*100*bigSize), image.Rect(0, 0, 100, bigSize)) const size = 32 img3 := atlas.NewImage(size/2, size/2, atlas.ImageTypeRegular) - defer img3.MarkDisposed() + defer img3.Deallocate() img3.WritePixels(make([]byte, (size/2)*(size/2)*4), image.Rect(0, 0, size/2, size/2)) img4 := atlas.NewImage(size, size, atlas.ImageTypeRegular) - defer img4.MarkDisposed() + defer img4.Deallocate() img5 := atlas.NewImage(size/2, size/2, atlas.ImageTypeRegular) - defer img3.MarkDisposed() + defer img3.Deallocate() pix := make([]byte, size*size*4) for j := 0; j < size; j++ { @@ -148,18 +148,18 @@ func TestReputOnSourceBackend(t *testing.T) { const size = 16 img0 := atlas.NewImage(size, size, atlas.ImageTypeRegular) - defer img0.MarkDisposed() + defer img0.Deallocate() img0.WritePixels(make([]byte, 4*size*size), image.Rect(0, 0, size, size)) img1 := atlas.NewImage(size, size, atlas.ImageTypeRegular) - defer img1.MarkDisposed() + defer img1.Deallocate() img1.WritePixels(make([]byte, 4*size*size), image.Rect(0, 0, size, size)) if got, want := img1.IsOnSourceBackendForTesting(), true; got != want { t.Errorf("got: %v, want: %v", got, want) } img2 := atlas.NewImage(size, size, atlas.ImageTypeRegular) - defer img2.MarkDisposed() + defer img2.Deallocate() pix := make([]byte, 4*size*size) for j := 0; j < size; j++ { for i := 0; i < size; i++ { @@ -173,7 +173,7 @@ func TestReputOnSourceBackend(t *testing.T) { // Create a volatile image. This should always be on a non-source backend. img3 := atlas.NewImage(size, size, atlas.ImageTypeVolatile) - defer img3.MarkDisposed() + defer img3.Deallocate() img3.WritePixels(make([]byte, 4*size*size), image.Rect(0, 0, size, size)) if got, want := img3.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) @@ -298,7 +298,7 @@ func TestReputOnSourceBackend(t *testing.T) { func TestExtend(t *testing.T) { const w0, h0 = 100, 100 img0 := atlas.NewImage(w0, h0, atlas.ImageTypeRegular) - defer img0.MarkDisposed() + defer img0.Deallocate() p0 := make([]byte, 4*w0*h0) for i := 0; i < w0*h0; i++ { @@ -311,7 +311,7 @@ func TestExtend(t *testing.T) { const w1, h1 = minSourceImageSizeForTesting + 1, 100 img1 := atlas.NewImage(w1, h1, atlas.ImageTypeRegular) - defer img1.MarkDisposed() + defer img1.Deallocate() p1 := make([]byte, 4*w1*h1) for i := 0; i < w1*h1; i++ { @@ -365,9 +365,9 @@ func TestExtend(t *testing.T) { func TestWritePixelsAfterDrawTriangles(t *testing.T) { const w, h = 256, 256 src := atlas.NewImage(w, h, atlas.ImageTypeRegular) - defer src.MarkDisposed() + defer src.Deallocate() dst := atlas.NewImage(w, h, atlas.ImageTypeRegular) - defer dst.MarkDisposed() + defer dst.Deallocate() pix := make([]byte, 4*w*h) for i := 0; i < w*h; i++ { @@ -408,9 +408,9 @@ func TestWritePixelsAfterDrawTriangles(t *testing.T) { func TestSmallImages(t *testing.T) { const w, h = 4, 8 src := atlas.NewImage(w, h, atlas.ImageTypeRegular) - defer src.MarkDisposed() + defer src.Deallocate() dst := atlas.NewImage(w, h, atlas.ImageTypeRegular) - defer dst.MarkDisposed() + defer dst.Deallocate() pix := make([]byte, 4*w*h) for i := 0; i < w*h; i++ { @@ -448,11 +448,11 @@ func TestSmallImages(t *testing.T) { func TestLongImages(t *testing.T) { const w, h = 1, 6 src := atlas.NewImage(w, h, atlas.ImageTypeRegular) - defer src.MarkDisposed() + defer src.Deallocate() const dstW, dstH = 256, 256 dst := atlas.NewImage(dstW, dstH, atlas.ImageTypeRegular) - defer dst.MarkDisposed() + defer dst.Deallocate() pix := make([]byte, 4*w*h) for i := 0; i < w*h; i++ { @@ -487,16 +487,16 @@ func TestLongImages(t *testing.T) { } } -func TestDisposeImmediately(t *testing.T) { +func TestDeallocateImmediately(t *testing.T) { // This tests restorable.Image.ClearPixels is called but WritePixels is not called. img0 := atlas.NewImage(16, 16, atlas.ImageTypeRegular) img0.EnsureIsolatedFromSourceForTesting(nil) - defer img0.MarkDisposed() + defer img0.Deallocate() img1 := atlas.NewImage(16, 16, atlas.ImageTypeRegular) img1.EnsureIsolatedFromSourceForTesting(nil) - defer img1.MarkDisposed() + defer img1.Deallocate() // img0 and img1 should share the same backend in 99.9999% possibility. } @@ -504,12 +504,12 @@ func TestDisposeImmediately(t *testing.T) { // Issue #1028 func TestExtendWithBigImage(t *testing.T) { img0 := atlas.NewImage(1, 1, atlas.ImageTypeRegular) - defer img0.MarkDisposed() + defer img0.Deallocate() img0.WritePixels(make([]byte, 4*1*1), image.Rect(0, 0, 1, 1)) img1 := atlas.NewImage(minSourceImageSizeForTesting+1, minSourceImageSizeForTesting+1, atlas.ImageTypeRegular) - defer img1.MarkDisposed() + defer img1.Deallocate() img1.WritePixels(make([]byte, 4*(minSourceImageSizeForTesting+1)*(minSourceImageSizeForTesting+1)), image.Rect(0, 0, minSourceImageSizeForTesting+1, minSourceImageSizeForTesting+1)) } @@ -517,13 +517,13 @@ func TestExtendWithBigImage(t *testing.T) { // Issue #1217 func TestMaxImageSize(t *testing.T) { img0 := atlas.NewImage(1, 1, atlas.ImageTypeRegular) - defer img0.MarkDisposed() + defer img0.Deallocate() paddingSize := img0.PaddingSizeForTesting() // This tests that a too-big image is allocated correctly. s := maxImageSizeForTesting - 2*paddingSize img1 := atlas.NewImage(s, s, atlas.ImageTypeRegular) - defer img1.MarkDisposed() + defer img1.Deallocate() img1.WritePixels(make([]byte, 4*s*s), image.Rect(0, 0, s, s)) } @@ -536,7 +536,7 @@ func Disable_TestMinImageSize(t *testing.T) { // Though the image size is minimum size of the backend, extending the backend happens due to the paddings. s := minSourceImageSizeForTesting img := atlas.NewImage(s, s, atlas.ImageTypeRegular) - defer img.MarkDisposed() + defer img.Deallocate() img.WritePixels(make([]byte, 4*s*s), image.Rect(0, 0, s, s)) } @@ -545,7 +545,7 @@ func TestMaxImageSizeJust(t *testing.T) { // An unmanaged image never belongs to an atlas and doesn't have its paddings. // TODO: Should we allow such this size for ImageTypeRegular? img := atlas.NewImage(s, s, atlas.ImageTypeUnmanaged) - defer img.MarkDisposed() + defer img.Deallocate() img.WritePixels(make([]byte, 4*s*s), image.Rect(0, 0, s, s)) } @@ -553,7 +553,7 @@ func TestMaxImageSizeExceeded(t *testing.T) { // This tests that a too-big image is allocated correctly. s := maxImageSizeForTesting img := atlas.NewImage(s+1, s, atlas.ImageTypeRegular) - defer img.MarkDisposed() + defer img.Deallocate() defer func() { if err := recover(); err == nil { @@ -565,15 +565,15 @@ func TestMaxImageSizeExceeded(t *testing.T) { } // Issue #1421 -func TestDisposedAndReputOnSourceBackend(t *testing.T) { +func TestDeallocatedAndReputOnSourceBackend(t *testing.T) { const size = 16 src := atlas.NewImage(size, size, atlas.ImageTypeRegular) - defer src.MarkDisposed() + defer src.Deallocate() src2 := atlas.NewImage(size, size, atlas.ImageTypeRegular) - defer src2.MarkDisposed() + defer src2.Deallocate() dst := atlas.NewImage(size, size, atlas.ImageTypeRegular) - defer dst.MarkDisposed() + defer dst.Deallocate() // Use src as a render target so that src is not on an atlas. vs := quadVertices(size, size, 0, 0, 1) @@ -594,11 +594,8 @@ func TestDisposedAndReputOnSourceBackend(t *testing.T) { } } - // Before PutImagesOnSourceBackendForTesting, dispose the image. - src.MarkDisposed() - - // Force to dispose the image. - atlas.FlushDeferredForTesting() + // Before PutImagesOnSourceBackendForTesting, deallocate the image. + src.Deallocate() // Confirm that PutImagesOnSourceBackendForTesting doesn't panic. atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting()) @@ -609,11 +606,11 @@ func TestImageIsNotReputOnSourceBackendWithoutUsingAsSource(t *testing.T) { const size = 16 src := atlas.NewImage(size, size, atlas.ImageTypeRegular) - defer src.MarkDisposed() + defer src.Deallocate() src2 := atlas.NewImage(size, size, atlas.ImageTypeRegular) - defer src2.MarkDisposed() + defer src2.Deallocate() dst := atlas.NewImage(size, size, atlas.ImageTypeRegular) - defer dst.MarkDisposed() + defer dst.Deallocate() // Use src as a render target so that src is not on an atlas. vs := quadVertices(size, size, 0, 0, 1) @@ -660,7 +657,7 @@ func TestImageWritePixelsModify(t *testing.T) { for _, typ := range []atlas.ImageType{atlas.ImageTypeRegular, atlas.ImageTypeVolatile, atlas.ImageTypeUnmanaged} { const size = 16 img := atlas.NewImage(size, size, typ) - defer img.MarkDisposed() + defer img.Deallocate() pix := make([]byte, 4*size*size) for j := 0; j < size; j++ { for i := 0; i < size; i++ { @@ -754,11 +751,11 @@ func TestPowerOf2(t *testing.T) { func TestDestinationCountOverflow(t *testing.T) { const w, h = 256, 256 src := atlas.NewImage(w, h, atlas.ImageTypeRegular) - defer src.MarkDisposed() + defer src.Deallocate() dst0 := atlas.NewImage(w, h, atlas.ImageTypeRegular) - defer dst0.MarkDisposed() + defer dst0.Deallocate() dst1 := atlas.NewImage(w, h, atlas.ImageTypeRegular) - defer dst1.MarkDisposed() + defer dst1.Deallocate() vs := quadVertices(w, h, 0, 0, 1) is := graphics.QuadIndices() @@ -785,14 +782,14 @@ func TestDestinationCountOverflow(t *testing.T) { func TestIteratingImagesToPutOnSourceBackend(t *testing.T) { const w, h = 16, 16 src := atlas.NewImage(w, h, atlas.ImageTypeRegular) - defer src.MarkDisposed() + defer src.Deallocate() srcs := make([]*atlas.Image, 10) for i := range srcs { srcs[i] = atlas.NewImage(w, h, atlas.ImageTypeRegular) - defer srcs[i].MarkDisposed() + defer srcs[i].Deallocate() } dst := atlas.NewImage(w, h, atlas.ImageTypeRegular) - defer dst.MarkDisposed() + defer dst.Deallocate() // Use srcs as detinations once. vs := quadVertices(w, h, 0, 0, 1) diff --git a/internal/buffered/image.go b/internal/buffered/image.go index 01ae03829..1485e9285 100644 --- a/internal/buffered/image.go +++ b/internal/buffered/image.go @@ -45,8 +45,8 @@ func (i *Image) invalidatePixels() { i.pixels = nil } -func (i *Image) MarkDisposed() { - i.img.MarkDisposed() +func (i *Image) Deallocate() { + i.img.Deallocate() } func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error { diff --git a/internal/mipmap/mipmap.go b/internal/mipmap/mipmap.go index de3ec001e..f3b92204c 100644 --- a/internal/mipmap/mipmap.go +++ b/internal/mipmap/mipmap.go @@ -58,7 +58,7 @@ func (m *Mipmap) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, name str func (m *Mipmap) WritePixels(pix []byte, region image.Rectangle) { m.orig.WritePixels(pix, region) - m.disposeMipmaps() + m.deallocateMipmaps() } func (m *Mipmap) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error { @@ -124,7 +124,7 @@ func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageCount]*Mipmap, vertices } m.orig.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, evenOdd) - m.disposeMipmaps() + m.deallocateMipmaps() } func (m *Mipmap) setImg(level int, img *buffered.Image) { @@ -203,16 +203,15 @@ func sizeForLevel(x int, level int) int { return x } -func (m *Mipmap) MarkDisposed() { - m.disposeMipmaps() - m.orig.MarkDisposed() - m.orig = nil +func (m *Mipmap) Deallocate() { + m.deallocateMipmaps() + m.orig.Deallocate() } -func (m *Mipmap) disposeMipmaps() { +func (m *Mipmap) deallocateMipmaps() { for _, img := range m.imgs { if img != nil { - img.MarkDisposed() + img.Deallocate() } } for k := range m.imgs { diff --git a/internal/processtest/testdata/issue2079.go b/internal/processtest/testdata/issue2079.go index 9696ec30d..56bee0e67 100644 --- a/internal/processtest/testdata/issue2079.go +++ b/internal/processtest/testdata/issue2079.go @@ -45,7 +45,7 @@ func (g *Game) Layout(width, height int) (int, int) { go func() { i := ebiten.NewImage(width, height) i.Fill(color.White) - i.Dispose() + i.Deallocate() close(done) }() diff --git a/internal/processtest/testdata/issue2154_1.go b/internal/processtest/testdata/issue2154_1.go index c35613496..3369523ae 100644 --- a/internal/processtest/testdata/issue2154_1.go +++ b/internal/processtest/testdata/issue2154_1.go @@ -84,14 +84,14 @@ func (g *Game) Draw(screen *ebiten.Image) { ) src0 := ebiten.NewImage(w, h) - defer src0.Dispose() + defer src0.Deallocate() src0.Fill(color.RGBA{0xff, 0xff, 0xff, 0xff}) src0.Set(0, 0, color.RGBA{0, 0, 0, 0xff}) src0.Set(0, 1, color.RGBA{0, 0, 0, 0xff}) src0.Set(1, 0, color.RGBA{0, 0, 0, 0xff}) src1 := ebiten.NewImage(w, h) - defer src1.Dispose() + defer src1.Deallocate() src1.DrawImage(src0, nil) screen.Fill(color.RGBA{0xff, 0xff, 0xff, 0xff}) diff --git a/internal/ui/context.go b/internal/ui/context.go index 5c54c6653..ff957b020 100644 --- a/internal/ui/context.go +++ b/internal/ui/context.go @@ -170,7 +170,7 @@ func (c *context) newOffscreenImage(w, h int) *Image { func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInterface, forceDraw bool) error { if (c.offscreen.imageType == atlas.ImageTypeVolatile) != ui.IsScreenClearedEveryFrame() { w, h := c.offscreen.width, c.offscreen.height - c.offscreen.MarkDisposed() + c.offscreen.Deallocate() c.offscreen = c.newOffscreenImage(w, h) } @@ -230,21 +230,17 @@ func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFac ow := int(math.Ceil(c.offscreenWidth)) oh := int(math.Ceil(c.offscreenHeight)) - if c.screen != nil { - if c.screen.width != sw || c.screen.height != sh { - c.screen.MarkDisposed() - c.screen = nil - } + if c.screen != nil && (c.screen.width != sw || c.screen.height != sh) { + c.screen.Deallocate() + c.screen = nil } if c.screen == nil { c.screen = c.game.NewScreenImage(sw, sh) } - if c.offscreen != nil { - if c.offscreen.width != ow || c.offscreen.height != oh { - c.offscreen.MarkDisposed() - c.offscreen = nil - } + if c.offscreen != nil && (c.offscreen.width != ow || c.offscreen.height != oh) { + c.offscreen.Deallocate() + c.offscreen = nil } if c.offscreen == nil { c.offscreen = c.newOffscreenImage(ow, oh) diff --git a/internal/ui/image.go b/internal/ui/image.go index 25d7c8b87..33ac7ec30 100644 --- a/internal/ui/image.go +++ b/internal/ui/image.go @@ -69,18 +69,15 @@ func (u *UserInterface) NewImage(width, height int, imageType atlas.ImageType) * } } -func (i *Image) MarkDisposed() { +func (i *Image) Deallocate() { if i.mipmap == nil { return } if i.bigOffscreenBuffer != nil { - i.bigOffscreenBuffer.markDisposed() - i.bigOffscreenBuffer = nil + i.bigOffscreenBuffer.deallocate() } - i.mipmap.MarkDisposed() - i.mipmap = nil + i.mipmap.Deallocate() i.dotsBuffer = nil - i.modifyCallback = nil } func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint16, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, evenOdd bool, canSkipMipmap bool, antialias bool) { @@ -321,10 +318,9 @@ func (u *UserInterface) newBigOffscreenImage(orig *Image, imageType atlas.ImageT } } -func (i *bigOffscreenImage) markDisposed() { +func (i *bigOffscreenImage) deallocate() { if i.image != nil { - i.image.MarkDisposed() - i.image = nil + i.image.Deallocate() } i.dirty = false } diff --git a/shader.go b/shader.go index 84ddfd82d..9b017418e 100644 --- a/shader.go +++ b/shader.go @@ -57,14 +57,19 @@ func (s *Shader) Dispose() { s.shader = nil } -// Deallocate deallocates the internal state of shader. +// Deallocate deallocates the internal state of the shader. // Even after Deallocate is called, the shader is still available. // In this case, the shader's internal state is allocated again. // // Usually, you don't have to call Deallocate since the internal state is automatically released by GC. // However, if you are sure that the shader is no longer used but not sure how this shader object is referred, // you can call Deallocate to make sure that the internal state is deallocated. +// +// If the shader is disposed, Deallocate does nothing. func (s *Shader) Deallocate() { + if s.shader == nil { + return + } s.shader.Deallocate() } diff --git a/shader_test.go b/shader_test.go index a6f5502c5..574b80368 100644 --- a/shader_test.go +++ b/shader_test.go @@ -649,13 +649,13 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { const w, h = 1, 1 dst := ebiten.NewImage(w, h) - defer dst.Dispose() + defer dst.Deallocate() s, err := ebiten.NewShader([]byte(shader.Shader)) if err != nil { t.Fatal(err) } - defer s.Dispose() + defer s.Deallocate() op := &ebiten.DrawRectShaderOptions{} op.Uniforms = shader.Uniforms @@ -1588,13 +1588,13 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { const w, h = 1, 1 dst := ebiten.NewImage(w, h) - defer dst.Dispose() + defer dst.Deallocate() s, err := ebiten.NewShader([]byte(tc.Shader)) if err != nil { t.Fatal(err) } - defer s.Dispose() + defer s.Deallocate() op := &ebiten.DrawRectShaderOptions{} op.Uniforms = tc.Uniforms @@ -1621,13 +1621,13 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { const w, h = 1, 1 dst := ebiten.NewImage(w, h) - defer dst.Dispose() + defer dst.Deallocate() s, err := ebiten.NewShader([]byte(shader)) if err != nil { t.Fatal(err) } - defer s.Dispose() + defer s.Deallocate() op := &ebiten.DrawRectShaderOptions{} op.Uniforms = map[string]any{ @@ -1692,13 +1692,13 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { const w, h = 1, 1 dst := ebiten.NewImage(w, h) - defer dst.Dispose() + defer dst.Deallocate() s, err := ebiten.NewShader([]byte(shader)) if err != nil { t.Fatal(err) } - defer s.Dispose() + defer s.Deallocate() op := &ebiten.DrawRectShaderOptions{} dst.DrawRectShader(w, h, s, op) @@ -1763,12 +1763,12 @@ func TestShaderDifferentTextureSizes(t *testing.T) { src0 := ebiten.NewImageWithOptions(image.Rect(0, 0, 20, 4000), &ebiten.NewImageOptions{ Unmanaged: true, }).SubImage(image.Rect(4, 1025, 6, 1028)).(*ebiten.Image) - defer src0.Dispose() + defer src0.Deallocate() src1 := ebiten.NewImageWithOptions(image.Rect(0, 0, 4000, 20), &ebiten.NewImageOptions{ Unmanaged: true, }).SubImage(image.Rect(2047, 7, 2049, 10)).(*ebiten.Image) - defer src1.Dispose() + defer src1.Deallocate() src0.Fill(color.RGBA{0x10, 0x20, 0x30, 0xff}) src1.Fill(color.RGBA{0x30, 0x20, 0x10, 0xff}) @@ -1787,10 +1787,10 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { if err != nil { t.Fatal(err) } - defer shader.Dispose() + defer shader.Deallocate() dst := ebiten.NewImage(2, 3) - defer dst.Dispose() + defer dst.Deallocate() op := &ebiten.DrawRectShaderOptions{} op.Images[0] = src0 @@ -2137,12 +2137,12 @@ func TestShaderDifferentSourceSizes(t *testing.T) { src0 := ebiten.NewImageWithOptions(image.Rect(0, 0, 20, 4000), &ebiten.NewImageOptions{ Unmanaged: true, }).SubImage(image.Rect(4, 1025, 7, 1029)).(*ebiten.Image) // 3x4 - defer src0.Dispose() + defer src0.Deallocate() src1 := ebiten.NewImageWithOptions(image.Rect(0, 0, 4000, 20), &ebiten.NewImageOptions{ Unmanaged: true, }).SubImage(image.Rect(2047, 7, 2049, 10)).(*ebiten.Image) // 2x3 - defer src1.Dispose() + defer src1.Deallocate() src0.Fill(color.RGBA{0x10, 0x20, 0x30, 0xff}) src1.Fill(color.RGBA{0x30, 0x20, 0x10, 0xff}) @@ -2168,10 +2168,10 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { if err != nil { t.Fatal(err) } - defer shader.Dispose() + defer shader.Deallocate() dst := ebiten.NewImage(3, 4) - defer dst.Dispose() + defer dst.Deallocate() op := &ebiten.DrawTrianglesShaderOptions{} op.Images[0] = src0