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/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 {
@ -94,7 +126,16 @@ type Image struct {
backend *backend
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
}
@ -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

View File

@ -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)
}
}