diff --git a/internal/shareable/shareable.go b/internal/shareable/shareable.go index 7cc700277..7e3fb8742 100644 --- a/internal/shareable/shareable.go +++ b/internal/shareable/shareable.go @@ -22,10 +22,40 @@ import ( "github.com/hajimehoshi/ebiten/internal/affine" "github.com/hajimehoshi/ebiten/internal/graphics" + "github.com/hajimehoshi/ebiten/internal/hooks" "github.com/hajimehoshi/ebiten/internal/packing" "github.com/hajimehoshi/ebiten/internal/restorable" ) +func init() { + hooks.AppendHookOnBeforeUpdate(func() error { + makeImagesShared() + return nil + }) +} + +// MaxCountForShare represents the time duration when the image can become shared. +// +// This value is expoted for testing. +const MaxCountForShare = 10 + +func makeImagesShared() { + backendsM.Lock() + defer backendsM.Unlock() + + for i := range imagesToMakeShared { + i.nonUpdatedCount++ + if i.nonUpdatedCount >= MaxCountForShare { + i.makeShared() + } + delete(imagesToMakeShared, i) + } +} + +func MakeImagesSharedForTesting() { + makeImagesShared() +} + const ( initSize = 1024 maxSize = 4096 @@ -84,6 +114,8 @@ var ( // theBackends is a set of actually shared images. theBackends = []*backend{} + + imagesToMakeShared = map[*Image]struct{}{} ) type Image struct { @@ -93,8 +125,17 @@ type Image struct { backend *backend - node *packing.Node - countForShare int + node *packing.Node + + // nonUpdatedCount represents how long the image is kept not modified with DrawTriangles. + // In the current implementation, if an image is being modified by DrawTriangles, the image is separated from + // a shared (restorable) image by ensureNotShared. + // + // nonUpdatedCount is increased every frame if the image is not modified, or set to 0 if the image id + // modified. + // + // ReplacePixels doesn't affect this value since ReplacePixels can be done on shared images. + nonUpdatedCount int neverShared bool } @@ -140,7 +181,7 @@ func (i *Image) ensureNotShared() { } } -func (i *Image) forceShared() { +func (i *Image) makeShared() { if i.backend == nil { i.allocate(true) return @@ -151,7 +192,7 @@ func (i *Image) forceShared() { } if !i.shareable() { - panic("shareable: forceShared cannot be called on a non-shareable image") + panic("shareable: makeShared cannot be called on a non-shareable image") } newI := NewImage(i.width, i.height) @@ -167,7 +208,7 @@ func (i *Image) forceShared() { } newI.replacePixels(pixels) newI.moveTo(i) - i.countForShare = 0 + i.nonUpdatedCount = 0 } func (i *Image) region() (x, y, width, height int) { @@ -207,8 +248,6 @@ func (i *Image) PutVertex(dest []float32, dx, dy, sx, sy float32, bx0, by0, bx1, i.backend.restorable.PutVertex(dest, dx, dy, sx+oxf, sy+oyf, bx0+oxf, by0+oyf, bx1+oxf, by1+oyf, cr, cg, cb, ca) } -const MaxCountForShare = 10 - func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) { backendsM.Lock() defer backendsM.Unlock() @@ -233,17 +272,12 @@ func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, i.backend.restorable.DrawTriangles(img.backend.restorable, vertices, indices, colorm, mode, filter, address) - i.countForShare = 0 + i.nonUpdatedCount = 0 + delete(imagesToMakeShared, i) - // TODO: Reusing shared images is temporarily suspended for performance. See #661. - // - // if !img.isShared() && img.shareable() { - // img.countForShare++ - // if img.countForShare >= MaxCountForShare { - // img.forceShared() - // img.countForShare = 0 - // } - // } + if !img.isShared() && img.shareable() { + imagesToMakeShared[img] = struct{}{} + } } // Fill fills the image with a color. This affects not only the (0, 0)-(width, height) region but also the whole diff --git a/internal/shareable/shareable_test.go b/internal/shareable/shareable_test.go index 03fcc139b..385300f6b 100644 --- a/internal/shareable/shareable_test.go +++ b/internal/shareable/shareable_test.go @@ -112,7 +112,7 @@ func TestEnsureNotShared(t *testing.T) { img4.DrawTriangles(img3, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) } -func Disabled_TestReshared(t *testing.T) { +func TestReshared(t *testing.T) { const size = 16 img0 := NewImage(size, size) @@ -122,8 +122,7 @@ func Disabled_TestReshared(t *testing.T) { img1 := NewImage(size, size) defer img1.Dispose() img1.ReplacePixels(make([]byte, 4*size*size)) - want := true - if got := img1.IsSharedForTesting(); got != want { + if got, want := img1.IsSharedForTesting(), true; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -144,8 +143,7 @@ func Disabled_TestReshared(t *testing.T) { img3.MakeVolatile() defer img3.Dispose() img1.ReplacePixels(make([]byte, 4*size*size)) - want = false - if got := img3.IsSharedForTesting(); got != want { + if got, want := img3.IsSharedForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -153,19 +151,19 @@ func Disabled_TestReshared(t *testing.T) { vs := img2.QuadVertices(0, 0, size, size, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphics.QuadIndices() img1.DrawTriangles(img2, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) - want = false - if got := img1.IsSharedForTesting(); got != want { + if got, want := img1.IsSharedForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } // Use img1 as a render source. - for i := 0; i < MaxCountForShare-1; i++ { + for i := 0; i < MaxCountForShare; i++ { + MakeImagesSharedForTesting() img0.DrawTriangles(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) - want := false - if got := img1.IsSharedForTesting(); got != want { + if got, want := img1.IsSharedForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } } + MakeImagesSharedForTesting() for j := 0; j < size; j++ { for i := 0; i < size; i++ { @@ -179,8 +177,7 @@ func Disabled_TestReshared(t *testing.T) { } img0.DrawTriangles(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) - want = true - if got := img1.IsSharedForTesting(); got != want { + if got, want := img1.IsSharedForTesting(), true; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -197,9 +194,9 @@ func Disabled_TestReshared(t *testing.T) { // Use img3 as a render source. img3 never uses a shared texture. for i := 0; i < MaxCountForShare*2; i++ { + MakeImagesSharedForTesting() img0.DrawTriangles(img3, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) - want := false - if got := img3.IsSharedForTesting(); got != want { + if got, want := img3.IsSharedForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } }