shareable: Make images shared when possible

Fixes #864
This commit is contained in:
Hajime Hoshi 2019-05-11 23:16:05 +09:00
parent b03c02dd28
commit bc4e35a6c5
2 changed files with 62 additions and 31 deletions

View File

@ -22,10 +22,40 @@ import (
"github.com/hajimehoshi/ebiten/internal/affine" "github.com/hajimehoshi/ebiten/internal/affine"
"github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/graphics"
"github.com/hajimehoshi/ebiten/internal/hooks"
"github.com/hajimehoshi/ebiten/internal/packing" "github.com/hajimehoshi/ebiten/internal/packing"
"github.com/hajimehoshi/ebiten/internal/restorable" "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 ( const (
initSize = 1024 initSize = 1024
maxSize = 4096 maxSize = 4096
@ -84,6 +114,8 @@ var (
// theBackends is a set of actually shared images. // theBackends is a set of actually shared images.
theBackends = []*backend{} theBackends = []*backend{}
imagesToMakeShared = map[*Image]struct{}{}
) )
type Image struct { type Image struct {
@ -93,8 +125,17 @@ type Image struct {
backend *backend backend *backend
node *packing.Node node *packing.Node
countForShare int
// 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 neverShared bool
} }
@ -140,7 +181,7 @@ func (i *Image) ensureNotShared() {
} }
} }
func (i *Image) forceShared() { func (i *Image) makeShared() {
if i.backend == nil { if i.backend == nil {
i.allocate(true) i.allocate(true)
return return
@ -151,7 +192,7 @@ func (i *Image) forceShared() {
} }
if !i.shareable() { 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) newI := NewImage(i.width, i.height)
@ -167,7 +208,7 @@ func (i *Image) forceShared() {
} }
newI.replacePixels(pixels) newI.replacePixels(pixels)
newI.moveTo(i) newI.moveTo(i)
i.countForShare = 0 i.nonUpdatedCount = 0
} }
func (i *Image) region() (x, y, width, height int) { 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) 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) { func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode graphics.CompositeMode, filter graphics.Filter, address graphics.Address) {
backendsM.Lock() backendsM.Lock()
defer backendsM.Unlock() 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.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() {
// imagesToMakeShared[img] = struct{}{}
// if !img.isShared() && img.shareable() { }
// img.countForShare++
// if img.countForShare >= MaxCountForShare {
// img.forceShared()
// img.countForShare = 0
// }
// }
} }
// Fill fills the image with a color. This affects not only the (0, 0)-(width, height) region but also the whole // Fill fills the image with a color. This affects not only the (0, 0)-(width, height) region but also the whole

View File

@ -112,7 +112,7 @@ func TestEnsureNotShared(t *testing.T) {
img4.DrawTriangles(img3, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) 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 const size = 16
img0 := NewImage(size, size) img0 := NewImage(size, size)
@ -122,8 +122,7 @@ func Disabled_TestReshared(t *testing.T) {
img1 := NewImage(size, size) img1 := NewImage(size, size)
defer img1.Dispose() defer img1.Dispose()
img1.ReplacePixels(make([]byte, 4*size*size)) img1.ReplacePixels(make([]byte, 4*size*size))
want := true if got, want := img1.IsSharedForTesting(), true; got != want {
if got := img1.IsSharedForTesting(); got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -144,8 +143,7 @@ func Disabled_TestReshared(t *testing.T) {
img3.MakeVolatile() img3.MakeVolatile()
defer img3.Dispose() defer img3.Dispose()
img1.ReplacePixels(make([]byte, 4*size*size)) img1.ReplacePixels(make([]byte, 4*size*size))
want = false if got, want := img3.IsSharedForTesting(), false; got != want {
if got := img3.IsSharedForTesting(); got != want {
t.Errorf("got: %v, want: %v", 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) vs := img2.QuadVertices(0, 0, size, size, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
img1.DrawTriangles(img2, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) img1.DrawTriangles(img2, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
want = false if got, want := img1.IsSharedForTesting(), false; got != want {
if got := img1.IsSharedForTesting(); got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
// Use img1 as a render source. // 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) img0.DrawTriangles(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
want := false if got, want := img1.IsSharedForTesting(), false; got != want {
if got := img1.IsSharedForTesting(); got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
} }
MakeImagesSharedForTesting()
for j := 0; j < size; j++ { for j := 0; j < size; j++ {
for i := 0; i < size; i++ { 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) img0.DrawTriangles(img1, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
want = true if got, want := img1.IsSharedForTesting(), true; got != want {
if got := img1.IsSharedForTesting(); got != want {
t.Errorf("got: %v, want: %v", 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. // Use img3 as a render source. img3 never uses a shared texture.
for i := 0; i < MaxCountForShare*2; i++ { for i := 0; i < MaxCountForShare*2; i++ {
MakeImagesSharedForTesting()
img0.DrawTriangles(img3, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero) img0.DrawTriangles(img3, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
want := false if got, want := img3.IsSharedForTesting(), false; got != want {
if got := img3.IsSharedForTesting(); got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
} }