Skip to content

Commit

Permalink
ebiten: replace (*Image).Dispose with Deallocate
Browse files Browse the repository at this point in the history
Closes #2808
  • Loading branch information
hajimehoshi committed Nov 3, 2023
1 parent 8a44ef4 commit c01ceea
Show file tree
Hide file tree
Showing 15 changed files with 241 additions and 105 deletions.
2 changes: 1 addition & 1 deletion examples/isometric/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/ui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
4 changes: 2 additions & 2 deletions gameforui.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
}

Expand Down
29 changes: 27 additions & 2 deletions image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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.
Expand Down
119 changes: 118 additions & 1 deletion image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
}
}
}
5 changes: 3 additions & 2 deletions internal/atlas/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down

0 comments on commit c01ceea

Please sign in to comment.