ebiten: replace (*Image).Dispose with Deallocate

Closes #2808
This commit is contained in:
Hajime Hoshi 2023-11-03 15:06:22 +09:00
parent 8a44ef4f6c
commit c01ceeaa6a
15 changed files with 241 additions and 105 deletions

View File

@ -192,7 +192,7 @@ func (g *Game) renderLevel(screen *ebiten.Image) {
if scaleLater { if scaleLater {
if g.offscreen != nil { if g.offscreen != nil {
if g.offscreen.Bounds().Size() != screen.Bounds().Size() { if g.offscreen.Bounds().Size() != screen.Bounds().Size() {
g.offscreen.Dispose() g.offscreen.Deallocate()
g.offscreen = nil g.offscreen = nil
} }
} }

View File

@ -342,7 +342,7 @@ func (t *TextBox) Draw(dst *ebiten.Image) {
vw, vh := t.viewSize() vw, vh := t.viewSize()
w, h := t.contentBuf.Bounds().Dx(), t.contentBuf.Bounds().Dy() w, h := t.contentBuf.Bounds().Dx(), t.contentBuf.Bounds().Dy()
if vw > w || vh > h { if vw > w || vh > h {
t.contentBuf.Dispose() t.contentBuf.Deallocate()
t.contentBuf = nil t.contentBuf = nil
} }
} }

View File

@ -87,7 +87,7 @@ func newGameForUI(game Game, transparent bool) *gameForUI {
func (g *gameForUI) NewOffscreenImage(width, height int) *ui.Image { func (g *gameForUI) NewOffscreenImage(width, height int) *ui.Image {
if g.offscreen != nil { if g.offscreen != nil {
g.offscreen.Dispose() g.offscreen.Deallocate()
g.offscreen = nil 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 { func (g *gameForUI) NewScreenImage(width, height int) *ui.Image {
if g.screen != nil { if g.screen != nil {
g.screen.Dispose() g.screen.Deallocate()
g.screen = nil g.screen = nil
} }

View File

@ -963,7 +963,9 @@ func (i *Image) Set(x, y int, clr color.Color) {
// //
// If the image is a sub-image, Dispose does nothing. // 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() { func (i *Image) Dispose() {
i.copyCheck() i.copyCheck()
@ -973,10 +975,33 @@ func (i *Image) Dispose() {
if i.isSubImage() { if i.isSubImage() {
return return
} }
i.image.MarkDisposed() i.image.Deallocate()
i.image = nil 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. // WritePixels replaces the pixels of the image.
// //
// The given pixels are treated as RGBA pre-multiplied alpha values. // The given pixels are treated as RGBA pre-multiplied alpha values.

View File

@ -264,7 +264,7 @@ func TestImageDotByDotInversion(t *testing.T) {
func TestImageWritePixels(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. // 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))) dummyImg := ebiten.NewImageFromImage(image.NewRGBA(image.Rect(0, 0, 16, 16)))
defer dummyImg.Dispose() defer dummyImg.Deallocate()
_, origImg, err := openEbitenImage() _, origImg, err := openEbitenImage()
if err != nil { 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 { type ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string ~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) { func TestImageSubImageSubImage(t *testing.T) {
img := ebiten.NewImage(16, 16) img := ebiten.NewImage(16, 16)
img.Fill(color.White) img.Fill(color.White)
@ -2263,6 +2307,14 @@ func TestImageDrawDisposedImage(t *testing.T) {
dst.DrawImage(src, nil) 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) { func TestImageDrawTrianglesDisposedImage(t *testing.T) {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
@ -2278,6 +2330,16 @@ func TestImageDrawTrianglesDisposedImage(t *testing.T) {
dst.DrawTriangles(vs, is, src, nil) 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 // #1137
func BenchmarkImageDrawOver(b *testing.B) { func BenchmarkImageDrawOver(b *testing.B) {
dst := ebiten.NewImage(16, 16) dst := ebiten.NewImage(16, 16)
@ -4344,3 +4406,58 @@ func TestImageWritePixelAndDispose(t *testing.T) {
t.Errorf("got: %v, want: %v", got, want) 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)
}
}
}
}

View File

@ -542,8 +542,9 @@ func (i *Image) readPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte
return i.backend.restorable.ReadPixels(graphicsDriver, pixels, region.Add(r.Min)) return i.backend.restorable.ReadPixels(graphicsDriver, pixels, region.Add(r.Min))
} }
// MarkDisposed marks the image as disposed. // Deallocate deallocates the internal state.
func (i *Image) MarkDisposed() { // Even after this call, the image is still available as a new cleared image.
func (i *Image) Deallocate() {
backendsM.Lock() backendsM.Lock()
defer backendsM.Unlock() defer backendsM.Unlock()

View File

@ -62,25 +62,25 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) {
// Create img1 and img2 with this size so that the next images are allocated // Create img1 and img2 with this size so that the next images are allocated
// with non-upper-left location. // with non-upper-left location.
img1 := atlas.NewImage(bigSize, 100, atlas.ImageTypeRegular) img1 := atlas.NewImage(bigSize, 100, atlas.ImageTypeRegular)
defer img1.MarkDisposed() defer img1.Deallocate()
// Ensure img1's region is allocated. // Ensure img1's region is allocated.
img1.WritePixels(make([]byte, 4*bigSize*100), image.Rect(0, 0, bigSize, 100)) img1.WritePixels(make([]byte, 4*bigSize*100), image.Rect(0, 0, bigSize, 100))
img2 := atlas.NewImage(100, bigSize, atlas.ImageTypeRegular) 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)) img2.WritePixels(make([]byte, 4*100*bigSize), image.Rect(0, 0, 100, bigSize))
const size = 32 const size = 32
img3 := atlas.NewImage(size/2, size/2, atlas.ImageTypeRegular) 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)) img3.WritePixels(make([]byte, (size/2)*(size/2)*4), image.Rect(0, 0, size/2, size/2))
img4 := atlas.NewImage(size, size, atlas.ImageTypeRegular) img4 := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer img4.MarkDisposed() defer img4.Deallocate()
img5 := atlas.NewImage(size/2, size/2, atlas.ImageTypeRegular) img5 := atlas.NewImage(size/2, size/2, atlas.ImageTypeRegular)
defer img3.MarkDisposed() defer img3.Deallocate()
pix := make([]byte, size*size*4) pix := make([]byte, size*size*4)
for j := 0; j < size; j++ { for j := 0; j < size; j++ {
@ -148,18 +148,18 @@ func TestReputOnSourceBackend(t *testing.T) {
const size = 16 const size = 16
img0 := atlas.NewImage(size, size, atlas.ImageTypeRegular) 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)) img0.WritePixels(make([]byte, 4*size*size), image.Rect(0, 0, size, size))
img1 := atlas.NewImage(size, size, atlas.ImageTypeRegular) 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)) img1.WritePixels(make([]byte, 4*size*size), image.Rect(0, 0, size, size))
if got, want := img1.IsOnSourceBackendForTesting(), true; got != want { if got, want := img1.IsOnSourceBackendForTesting(), true; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
img2 := atlas.NewImage(size, size, atlas.ImageTypeRegular) img2 := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer img2.MarkDisposed() defer img2.Deallocate()
pix := make([]byte, 4*size*size) pix := make([]byte, 4*size*size)
for j := 0; j < size; j++ { for j := 0; j < size; j++ {
for i := 0; i < size; i++ { 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. // Create a volatile image. This should always be on a non-source backend.
img3 := atlas.NewImage(size, size, atlas.ImageTypeVolatile) 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)) img3.WritePixels(make([]byte, 4*size*size), image.Rect(0, 0, size, size))
if got, want := img3.IsOnSourceBackendForTesting(), false; got != want { if got, want := img3.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
@ -298,7 +298,7 @@ func TestReputOnSourceBackend(t *testing.T) {
func TestExtend(t *testing.T) { func TestExtend(t *testing.T) {
const w0, h0 = 100, 100 const w0, h0 = 100, 100
img0 := atlas.NewImage(w0, h0, atlas.ImageTypeRegular) img0 := atlas.NewImage(w0, h0, atlas.ImageTypeRegular)
defer img0.MarkDisposed() defer img0.Deallocate()
p0 := make([]byte, 4*w0*h0) p0 := make([]byte, 4*w0*h0)
for i := 0; i < w0*h0; i++ { for i := 0; i < w0*h0; i++ {
@ -311,7 +311,7 @@ func TestExtend(t *testing.T) {
const w1, h1 = minSourceImageSizeForTesting + 1, 100 const w1, h1 = minSourceImageSizeForTesting + 1, 100
img1 := atlas.NewImage(w1, h1, atlas.ImageTypeRegular) img1 := atlas.NewImage(w1, h1, atlas.ImageTypeRegular)
defer img1.MarkDisposed() defer img1.Deallocate()
p1 := make([]byte, 4*w1*h1) p1 := make([]byte, 4*w1*h1)
for i := 0; i < w1*h1; i++ { for i := 0; i < w1*h1; i++ {
@ -365,9 +365,9 @@ func TestExtend(t *testing.T) {
func TestWritePixelsAfterDrawTriangles(t *testing.T) { func TestWritePixelsAfterDrawTriangles(t *testing.T) {
const w, h = 256, 256 const w, h = 256, 256
src := atlas.NewImage(w, h, atlas.ImageTypeRegular) src := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.Deallocate()
dst := atlas.NewImage(w, h, atlas.ImageTypeRegular) dst := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer dst.MarkDisposed() defer dst.Deallocate()
pix := make([]byte, 4*w*h) pix := make([]byte, 4*w*h)
for i := 0; i < w*h; i++ { for i := 0; i < w*h; i++ {
@ -408,9 +408,9 @@ func TestWritePixelsAfterDrawTriangles(t *testing.T) {
func TestSmallImages(t *testing.T) { func TestSmallImages(t *testing.T) {
const w, h = 4, 8 const w, h = 4, 8
src := atlas.NewImage(w, h, atlas.ImageTypeRegular) src := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.Deallocate()
dst := atlas.NewImage(w, h, atlas.ImageTypeRegular) dst := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer dst.MarkDisposed() defer dst.Deallocate()
pix := make([]byte, 4*w*h) pix := make([]byte, 4*w*h)
for i := 0; i < w*h; i++ { for i := 0; i < w*h; i++ {
@ -448,11 +448,11 @@ func TestSmallImages(t *testing.T) {
func TestLongImages(t *testing.T) { func TestLongImages(t *testing.T) {
const w, h = 1, 6 const w, h = 1, 6
src := atlas.NewImage(w, h, atlas.ImageTypeRegular) src := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.Deallocate()
const dstW, dstH = 256, 256 const dstW, dstH = 256, 256
dst := atlas.NewImage(dstW, dstH, atlas.ImageTypeRegular) dst := atlas.NewImage(dstW, dstH, atlas.ImageTypeRegular)
defer dst.MarkDisposed() defer dst.Deallocate()
pix := make([]byte, 4*w*h) pix := make([]byte, 4*w*h)
for i := 0; i < w*h; i++ { 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. // This tests restorable.Image.ClearPixels is called but WritePixels is not called.
img0 := atlas.NewImage(16, 16, atlas.ImageTypeRegular) img0 := atlas.NewImage(16, 16, atlas.ImageTypeRegular)
img0.EnsureIsolatedFromSourceForTesting(nil) img0.EnsureIsolatedFromSourceForTesting(nil)
defer img0.MarkDisposed() defer img0.Deallocate()
img1 := atlas.NewImage(16, 16, atlas.ImageTypeRegular) img1 := atlas.NewImage(16, 16, atlas.ImageTypeRegular)
img1.EnsureIsolatedFromSourceForTesting(nil) img1.EnsureIsolatedFromSourceForTesting(nil)
defer img1.MarkDisposed() defer img1.Deallocate()
// img0 and img1 should share the same backend in 99.9999% possibility. // img0 and img1 should share the same backend in 99.9999% possibility.
} }
@ -504,12 +504,12 @@ func TestDisposeImmediately(t *testing.T) {
// Issue #1028 // Issue #1028
func TestExtendWithBigImage(t *testing.T) { func TestExtendWithBigImage(t *testing.T) {
img0 := atlas.NewImage(1, 1, atlas.ImageTypeRegular) 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)) img0.WritePixels(make([]byte, 4*1*1), image.Rect(0, 0, 1, 1))
img1 := atlas.NewImage(minSourceImageSizeForTesting+1, minSourceImageSizeForTesting+1, atlas.ImageTypeRegular) 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)) 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 // Issue #1217
func TestMaxImageSize(t *testing.T) { func TestMaxImageSize(t *testing.T) {
img0 := atlas.NewImage(1, 1, atlas.ImageTypeRegular) img0 := atlas.NewImage(1, 1, atlas.ImageTypeRegular)
defer img0.MarkDisposed() defer img0.Deallocate()
paddingSize := img0.PaddingSizeForTesting() paddingSize := img0.PaddingSizeForTesting()
// This tests that a too-big image is allocated correctly. // This tests that a too-big image is allocated correctly.
s := maxImageSizeForTesting - 2*paddingSize s := maxImageSizeForTesting - 2*paddingSize
img1 := atlas.NewImage(s, s, atlas.ImageTypeRegular) 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)) 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. // Though the image size is minimum size of the backend, extending the backend happens due to the paddings.
s := minSourceImageSizeForTesting s := minSourceImageSizeForTesting
img := atlas.NewImage(s, s, atlas.ImageTypeRegular) 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)) 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. // An unmanaged image never belongs to an atlas and doesn't have its paddings.
// TODO: Should we allow such this size for ImageTypeRegular? // TODO: Should we allow such this size for ImageTypeRegular?
img := atlas.NewImage(s, s, atlas.ImageTypeUnmanaged) 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)) 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. // This tests that a too-big image is allocated correctly.
s := maxImageSizeForTesting s := maxImageSizeForTesting
img := atlas.NewImage(s+1, s, atlas.ImageTypeRegular) img := atlas.NewImage(s+1, s, atlas.ImageTypeRegular)
defer img.MarkDisposed() defer img.Deallocate()
defer func() { defer func() {
if err := recover(); err == nil { if err := recover(); err == nil {
@ -565,15 +565,15 @@ func TestMaxImageSizeExceeded(t *testing.T) {
} }
// Issue #1421 // Issue #1421
func TestDisposedAndReputOnSourceBackend(t *testing.T) { func TestDeallocatedAndReputOnSourceBackend(t *testing.T) {
const size = 16 const size = 16
src := atlas.NewImage(size, size, atlas.ImageTypeRegular) src := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.Deallocate()
src2 := atlas.NewImage(size, size, atlas.ImageTypeRegular) src2 := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer src2.MarkDisposed() defer src2.Deallocate()
dst := atlas.NewImage(size, size, atlas.ImageTypeRegular) 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. // Use src as a render target so that src is not on an atlas.
vs := quadVertices(size, size, 0, 0, 1) vs := quadVertices(size, size, 0, 0, 1)
@ -594,11 +594,8 @@ func TestDisposedAndReputOnSourceBackend(t *testing.T) {
} }
} }
// Before PutImagesOnSourceBackendForTesting, dispose the image. // Before PutImagesOnSourceBackendForTesting, deallocate the image.
src.MarkDisposed() src.Deallocate()
// Force to dispose the image.
atlas.FlushDeferredForTesting()
// Confirm that PutImagesOnSourceBackendForTesting doesn't panic. // Confirm that PutImagesOnSourceBackendForTesting doesn't panic.
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting()) atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
@ -609,11 +606,11 @@ func TestImageIsNotReputOnSourceBackendWithoutUsingAsSource(t *testing.T) {
const size = 16 const size = 16
src := atlas.NewImage(size, size, atlas.ImageTypeRegular) src := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.Deallocate()
src2 := atlas.NewImage(size, size, atlas.ImageTypeRegular) src2 := atlas.NewImage(size, size, atlas.ImageTypeRegular)
defer src2.MarkDisposed() defer src2.Deallocate()
dst := atlas.NewImage(size, size, atlas.ImageTypeRegular) 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. // Use src as a render target so that src is not on an atlas.
vs := quadVertices(size, size, 0, 0, 1) 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} { for _, typ := range []atlas.ImageType{atlas.ImageTypeRegular, atlas.ImageTypeVolatile, atlas.ImageTypeUnmanaged} {
const size = 16 const size = 16
img := atlas.NewImage(size, size, typ) img := atlas.NewImage(size, size, typ)
defer img.MarkDisposed() defer img.Deallocate()
pix := make([]byte, 4*size*size) pix := make([]byte, 4*size*size)
for j := 0; j < size; j++ { for j := 0; j < size; j++ {
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
@ -754,11 +751,11 @@ func TestPowerOf2(t *testing.T) {
func TestDestinationCountOverflow(t *testing.T) { func TestDestinationCountOverflow(t *testing.T) {
const w, h = 256, 256 const w, h = 256, 256
src := atlas.NewImage(w, h, atlas.ImageTypeRegular) src := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.Deallocate()
dst0 := atlas.NewImage(w, h, atlas.ImageTypeRegular) dst0 := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer dst0.MarkDisposed() defer dst0.Deallocate()
dst1 := atlas.NewImage(w, h, atlas.ImageTypeRegular) dst1 := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer dst1.MarkDisposed() defer dst1.Deallocate()
vs := quadVertices(w, h, 0, 0, 1) vs := quadVertices(w, h, 0, 0, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
@ -785,14 +782,14 @@ func TestDestinationCountOverflow(t *testing.T) {
func TestIteratingImagesToPutOnSourceBackend(t *testing.T) { func TestIteratingImagesToPutOnSourceBackend(t *testing.T) {
const w, h = 16, 16 const w, h = 16, 16
src := atlas.NewImage(w, h, atlas.ImageTypeRegular) src := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer src.MarkDisposed() defer src.Deallocate()
srcs := make([]*atlas.Image, 10) srcs := make([]*atlas.Image, 10)
for i := range srcs { for i := range srcs {
srcs[i] = atlas.NewImage(w, h, atlas.ImageTypeRegular) srcs[i] = atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer srcs[i].MarkDisposed() defer srcs[i].Deallocate()
} }
dst := atlas.NewImage(w, h, atlas.ImageTypeRegular) dst := atlas.NewImage(w, h, atlas.ImageTypeRegular)
defer dst.MarkDisposed() defer dst.Deallocate()
// Use srcs as detinations once. // Use srcs as detinations once.
vs := quadVertices(w, h, 0, 0, 1) vs := quadVertices(w, h, 0, 0, 1)

View File

@ -45,8 +45,8 @@ func (i *Image) invalidatePixels() {
i.pixels = nil i.pixels = nil
} }
func (i *Image) MarkDisposed() { func (i *Image) Deallocate() {
i.img.MarkDisposed() i.img.Deallocate()
} }
func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error { func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error {

View File

@ -58,7 +58,7 @@ func (m *Mipmap) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, name str
func (m *Mipmap) WritePixels(pix []byte, region image.Rectangle) { func (m *Mipmap) WritePixels(pix []byte, region image.Rectangle) {
m.orig.WritePixels(pix, region) m.orig.WritePixels(pix, region)
m.disposeMipmaps() m.deallocateMipmaps()
} }
func (m *Mipmap) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error { 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.orig.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, evenOdd)
m.disposeMipmaps() m.deallocateMipmaps()
} }
func (m *Mipmap) setImg(level int, img *buffered.Image) { func (m *Mipmap) setImg(level int, img *buffered.Image) {
@ -203,16 +203,15 @@ func sizeForLevel(x int, level int) int {
return x return x
} }
func (m *Mipmap) MarkDisposed() { func (m *Mipmap) Deallocate() {
m.disposeMipmaps() m.deallocateMipmaps()
m.orig.MarkDisposed() m.orig.Deallocate()
m.orig = nil
} }
func (m *Mipmap) disposeMipmaps() { func (m *Mipmap) deallocateMipmaps() {
for _, img := range m.imgs { for _, img := range m.imgs {
if img != nil { if img != nil {
img.MarkDisposed() img.Deallocate()
} }
} }
for k := range m.imgs { for k := range m.imgs {

View File

@ -45,7 +45,7 @@ func (g *Game) Layout(width, height int) (int, int) {
go func() { go func() {
i := ebiten.NewImage(width, height) i := ebiten.NewImage(width, height)
i.Fill(color.White) i.Fill(color.White)
i.Dispose() i.Deallocate()
close(done) close(done)
}() }()

View File

@ -84,14 +84,14 @@ func (g *Game) Draw(screen *ebiten.Image) {
) )
src0 := ebiten.NewImage(w, h) src0 := ebiten.NewImage(w, h)
defer src0.Dispose() defer src0.Deallocate()
src0.Fill(color.RGBA{0xff, 0xff, 0xff, 0xff}) src0.Fill(color.RGBA{0xff, 0xff, 0xff, 0xff})
src0.Set(0, 0, color.RGBA{0, 0, 0, 0xff}) src0.Set(0, 0, color.RGBA{0, 0, 0, 0xff})
src0.Set(0, 1, 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}) src0.Set(1, 0, color.RGBA{0, 0, 0, 0xff})
src1 := ebiten.NewImage(w, h) src1 := ebiten.NewImage(w, h)
defer src1.Dispose() defer src1.Deallocate()
src1.DrawImage(src0, nil) src1.DrawImage(src0, nil)
screen.Fill(color.RGBA{0xff, 0xff, 0xff, 0xff}) screen.Fill(color.RGBA{0xff, 0xff, 0xff, 0xff})

View File

@ -170,7 +170,7 @@ func (c *context) newOffscreenImage(w, h int) *Image {
func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInterface, forceDraw bool) error { func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInterface, forceDraw bool) error {
if (c.offscreen.imageType == atlas.ImageTypeVolatile) != ui.IsScreenClearedEveryFrame() { if (c.offscreen.imageType == atlas.ImageTypeVolatile) != ui.IsScreenClearedEveryFrame() {
w, h := c.offscreen.width, c.offscreen.height w, h := c.offscreen.width, c.offscreen.height
c.offscreen.MarkDisposed() c.offscreen.Deallocate()
c.offscreen = c.newOffscreenImage(w, h) c.offscreen = c.newOffscreenImage(w, h)
} }
@ -230,21 +230,17 @@ func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFac
ow := int(math.Ceil(c.offscreenWidth)) ow := int(math.Ceil(c.offscreenWidth))
oh := int(math.Ceil(c.offscreenHeight)) oh := int(math.Ceil(c.offscreenHeight))
if c.screen != nil { if c.screen != nil && (c.screen.width != sw || c.screen.height != sh) {
if c.screen.width != sw || c.screen.height != sh { c.screen.Deallocate()
c.screen.MarkDisposed() c.screen = nil
c.screen = nil
}
} }
if c.screen == nil { if c.screen == nil {
c.screen = c.game.NewScreenImage(sw, sh) c.screen = c.game.NewScreenImage(sw, sh)
} }
if c.offscreen != nil { if c.offscreen != nil && (c.offscreen.width != ow || c.offscreen.height != oh) {
if c.offscreen.width != ow || c.offscreen.height != oh { c.offscreen.Deallocate()
c.offscreen.MarkDisposed() c.offscreen = nil
c.offscreen = nil
}
} }
if c.offscreen == nil { if c.offscreen == nil {
c.offscreen = c.newOffscreenImage(ow, oh) c.offscreen = c.newOffscreenImage(ow, oh)

View File

@ -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 { if i.mipmap == nil {
return return
} }
if i.bigOffscreenBuffer != nil { if i.bigOffscreenBuffer != nil {
i.bigOffscreenBuffer.markDisposed() i.bigOffscreenBuffer.deallocate()
i.bigOffscreenBuffer = nil
} }
i.mipmap.MarkDisposed() i.mipmap.Deallocate()
i.mipmap = nil
i.dotsBuffer = nil 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) { 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 { if i.image != nil {
i.image.MarkDisposed() i.image.Deallocate()
i.image = nil
} }
i.dirty = false i.dirty = false
} }

View File

@ -57,14 +57,19 @@ func (s *Shader) Dispose() {
s.shader = nil 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. // Even after Deallocate is called, the shader is still available.
// In this case, the shader's internal state is allocated again. // 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. // 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, // 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. // 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() { func (s *Shader) Deallocate() {
if s.shader == nil {
return
}
s.shader.Deallocate() s.shader.Deallocate()
} }

View File

@ -649,13 +649,13 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
const w, h = 1, 1 const w, h = 1, 1
dst := ebiten.NewImage(w, h) dst := ebiten.NewImage(w, h)
defer dst.Dispose() defer dst.Deallocate()
s, err := ebiten.NewShader([]byte(shader.Shader)) s, err := ebiten.NewShader([]byte(shader.Shader))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer s.Dispose() defer s.Deallocate()
op := &ebiten.DrawRectShaderOptions{} op := &ebiten.DrawRectShaderOptions{}
op.Uniforms = shader.Uniforms op.Uniforms = shader.Uniforms
@ -1588,13 +1588,13 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
const w, h = 1, 1 const w, h = 1, 1
dst := ebiten.NewImage(w, h) dst := ebiten.NewImage(w, h)
defer dst.Dispose() defer dst.Deallocate()
s, err := ebiten.NewShader([]byte(tc.Shader)) s, err := ebiten.NewShader([]byte(tc.Shader))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer s.Dispose() defer s.Deallocate()
op := &ebiten.DrawRectShaderOptions{} op := &ebiten.DrawRectShaderOptions{}
op.Uniforms = tc.Uniforms op.Uniforms = tc.Uniforms
@ -1621,13 +1621,13 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
const w, h = 1, 1 const w, h = 1, 1
dst := ebiten.NewImage(w, h) dst := ebiten.NewImage(w, h)
defer dst.Dispose() defer dst.Deallocate()
s, err := ebiten.NewShader([]byte(shader)) s, err := ebiten.NewShader([]byte(shader))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer s.Dispose() defer s.Deallocate()
op := &ebiten.DrawRectShaderOptions{} op := &ebiten.DrawRectShaderOptions{}
op.Uniforms = map[string]any{ op.Uniforms = map[string]any{
@ -1692,13 +1692,13 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
const w, h = 1, 1 const w, h = 1, 1
dst := ebiten.NewImage(w, h) dst := ebiten.NewImage(w, h)
defer dst.Dispose() defer dst.Deallocate()
s, err := ebiten.NewShader([]byte(shader)) s, err := ebiten.NewShader([]byte(shader))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer s.Dispose() defer s.Deallocate()
op := &ebiten.DrawRectShaderOptions{} op := &ebiten.DrawRectShaderOptions{}
dst.DrawRectShader(w, h, s, op) 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{ src0 := ebiten.NewImageWithOptions(image.Rect(0, 0, 20, 4000), &ebiten.NewImageOptions{
Unmanaged: true, Unmanaged: true,
}).SubImage(image.Rect(4, 1025, 6, 1028)).(*ebiten.Image) }).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{ src1 := ebiten.NewImageWithOptions(image.Rect(0, 0, 4000, 20), &ebiten.NewImageOptions{
Unmanaged: true, Unmanaged: true,
}).SubImage(image.Rect(2047, 7, 2049, 10)).(*ebiten.Image) }).SubImage(image.Rect(2047, 7, 2049, 10)).(*ebiten.Image)
defer src1.Dispose() defer src1.Deallocate()
src0.Fill(color.RGBA{0x10, 0x20, 0x30, 0xff}) src0.Fill(color.RGBA{0x10, 0x20, 0x30, 0xff})
src1.Fill(color.RGBA{0x30, 0x20, 0x10, 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer shader.Dispose() defer shader.Deallocate()
dst := ebiten.NewImage(2, 3) dst := ebiten.NewImage(2, 3)
defer dst.Dispose() defer dst.Deallocate()
op := &ebiten.DrawRectShaderOptions{} op := &ebiten.DrawRectShaderOptions{}
op.Images[0] = src0 op.Images[0] = src0
@ -2137,12 +2137,12 @@ func TestShaderDifferentSourceSizes(t *testing.T) {
src0 := ebiten.NewImageWithOptions(image.Rect(0, 0, 20, 4000), &ebiten.NewImageOptions{ src0 := ebiten.NewImageWithOptions(image.Rect(0, 0, 20, 4000), &ebiten.NewImageOptions{
Unmanaged: true, Unmanaged: true,
}).SubImage(image.Rect(4, 1025, 7, 1029)).(*ebiten.Image) // 3x4 }).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{ src1 := ebiten.NewImageWithOptions(image.Rect(0, 0, 4000, 20), &ebiten.NewImageOptions{
Unmanaged: true, Unmanaged: true,
}).SubImage(image.Rect(2047, 7, 2049, 10)).(*ebiten.Image) // 2x3 }).SubImage(image.Rect(2047, 7, 2049, 10)).(*ebiten.Image) // 2x3
defer src1.Dispose() defer src1.Deallocate()
src0.Fill(color.RGBA{0x10, 0x20, 0x30, 0xff}) src0.Fill(color.RGBA{0x10, 0x20, 0x30, 0xff})
src1.Fill(color.RGBA{0x30, 0x20, 0x10, 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer shader.Dispose() defer shader.Deallocate()
dst := ebiten.NewImage(3, 4) dst := ebiten.NewImage(3, 4)
defer dst.Dispose() defer dst.Deallocate()
op := &ebiten.DrawTrianglesShaderOptions{} op := &ebiten.DrawTrianglesShaderOptions{}
op.Images[0] = src0 op.Images[0] = src0