From 30a2817ab53e096b9069bdaaa8a921456a8ef6c0 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sun, 8 Sep 2024 11:42:02 +0900 Subject: [PATCH] internal/restorable: add Hint to optimize drawImageHistoryItem size --- image.go | 9 +-- internal/atlas/image.go | 14 ++--- internal/atlas/image_test.go | 47 +++++++-------- internal/atlas/shader_test.go | 11 ++-- internal/buffered/image.go | 6 +- internal/buffered/image_test.go | 3 +- internal/mipmap/mipmap.go | 7 ++- internal/restorable/image.go | 39 +++++++++++-- internal/restorable/images_test.go | 91 ++++++++++++++++++++---------- internal/restorable/shader_test.go | 12 ++-- internal/ui/image.go | 15 +++-- 11 files changed, 163 insertions(+), 91 deletions(-) diff --git a/image.go b/image.go index 115020b08..bf2235636 100644 --- a/image.go +++ b/image.go @@ -26,6 +26,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/restorable" "github.com/hajimehoshi/ebiten/v2/internal/shaderir" "github.com/hajimehoshi/ebiten/v2/internal/ui" ) @@ -266,7 +267,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) { }) } - i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, canSkipMipmap(geoM, filter), false) + i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, canSkipMipmap(geoM, filter), false, restorable.HintNone) } // Vertex represents a vertex passed to DrawTriangles. @@ -550,7 +551,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o }) } - i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), filter != builtinshader.FilterLinear, options.AntiAlias) + i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), filter != builtinshader.FilterLinear, options.AntiAlias, restorable.HintNone) } // DrawTrianglesShaderOptions represents options for DrawTrianglesShader. @@ -722,7 +723,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader i.tmpUniforms = i.tmpUniforms[:0] i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms) - i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), true, options.AntiAlias) + i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), true, options.AntiAlias, restorable.HintNone) } // DrawRectShaderOptions represents options for DrawRectShader. @@ -855,7 +856,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR i.tmpUniforms = i.tmpUniforms[:0] i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms) - i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, true, false) + i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, true, false, restorable.HintNone) } // SubImage returns an image representing the portion of the image p visible through r. diff --git a/internal/atlas/image.go b/internal/atlas/image.go index 31a4f83a0..0a395ef1a 100644 --- a/internal/atlas/image.go +++ b/internal/atlas/image.go @@ -285,7 +285,7 @@ func (i *Image) ensureIsolatedFromSource(backends []*backend) { is := graphics.QuadIndices() dr := image.Rect(0, 0, i.width, i.height) - newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion) newI.moveTo(i) } @@ -315,7 +315,7 @@ func (i *Image) putOnSourceBackend() { graphics.QuadVerticesFromDstAndSrc(vs, 0, 0, w, h, 0, 0, w, h, 1, 1, 1, 1) is := graphics.QuadIndices() dr := image.Rect(0, 0, i.width, i.height) - newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion) newI.moveTo(i) i.usedAsSourceCount = 0 @@ -347,7 +347,7 @@ func (i *Image) regionWithPadding() image.Rectangle { // 5: Color G // 6: Color B // 7: Color Y -func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) { +func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint restorable.Hint) { backendsM.Lock() defer backendsM.Unlock() @@ -360,15 +360,15 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice copy(us, uniforms) appendDeferred(func() { - i.drawTriangles(srcs, vs, is, blend, dstRegion, srcRegions, shader, us, fillRule) + i.drawTriangles(srcs, vs, is, blend, dstRegion, srcRegions, shader, us, fillRule, hint) }) return } - i.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule) + i.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint) } -func (i *Image) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) { +func (i *Image) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint restorable.Hint) { backends := make([]*backend, 0, len(srcs)) for _, src := range srcs { if src == nil { @@ -449,7 +449,7 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice imgs[i] = src.backend.restorable } - i.backend.restorable.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader.ensureShader(), uniforms, fillRule) + i.backend.restorable.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader.ensureShader(), uniforms, fillRule, hint) for _, src := range srcs { if src == nil { diff --git a/internal/atlas/image_test.go b/internal/atlas/image_test.go index 1a7d1d300..0a021b95e 100644 --- a/internal/atlas/image_test.go +++ b/internal/atlas/image_test.go @@ -25,6 +25,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/atlas" "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/restorable" t "github.com/hajimehoshi/ebiten/v2/internal/testing" "github.com/hajimehoshi/ebiten/v2/internal/ui" ) @@ -102,7 +103,7 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) { vs := quadVertices(size/2, size/2, size/4, size/4, 1) is := graphics.QuadIndices() dr := image.Rect(0, 0, size, size) - img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := img4.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -110,7 +111,7 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) { // img5 is not allocated now, but is allocated at DrawTriangles. vs = quadVertices(0, 0, size/2, size/2, 1) dr = image.Rect(0, 0, size/2, size/2) - img3.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img5}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img3.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img5}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := img3.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -144,7 +145,7 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) { // Check further drawing doesn't cause panic. // This bug was fixed by 03dcd948. vs = quadVertices(0, 0, size/2, size/2, 1) - img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) } func TestReputOnSourceBackend(t *testing.T) { @@ -188,7 +189,7 @@ func TestReputOnSourceBackend(t *testing.T) { // Render onto img1. The count should not matter. for i := 0; i < 5; i++ { vs := quadVertices(size, size, 0, 0, 1) - img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := img1.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -200,7 +201,7 @@ func TestReputOnSourceBackend(t *testing.T) { for i := 0; i < atlas.BaseCountToPutOnSourceBackend*2; i++ { atlas.PutImagesOnSourceBackendForTesting() vs := quadVertices(size, size, 0, 0, 1) - img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := img1.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -208,7 +209,7 @@ func TestReputOnSourceBackend(t *testing.T) { // Finally, img1 is on a source backend. atlas.PutImagesOnSourceBackendForTesting() vs := quadVertices(size, size, 0, 0, 1) - img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := img1.IsOnSourceBackendForTesting(), true; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -237,7 +238,7 @@ func TestReputOnSourceBackend(t *testing.T) { } vs = quadVertices(size, size, 0, 0, 1) - img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := img1.IsOnSourceBackendForTesting(), true; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -267,7 +268,7 @@ func TestReputOnSourceBackend(t *testing.T) { // Use img1 as a render target again. The count should not matter. for i := 0; i < 5; i++ { vs := quadVertices(size, size, 0, 0, 1) - img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := img1.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -279,7 +280,7 @@ func TestReputOnSourceBackend(t *testing.T) { atlas.PutImagesOnSourceBackendForTesting() img1.WritePixels(make([]byte, 4*size*size), image.Rect(0, 0, size, size)) vs := quadVertices(size, size, 0, 0, 1) - img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := img1.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -288,7 +289,7 @@ func TestReputOnSourceBackend(t *testing.T) { // img1 is not on an atlas due to WritePixels. vs = quadVertices(size, size, 0, 0, 1) - img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := img1.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -297,7 +298,7 @@ func TestReputOnSourceBackend(t *testing.T) { for i := 0; i < atlas.BaseCountToPutOnSourceBackend*2; i++ { atlas.PutImagesOnSourceBackendForTesting() vs := quadVertices(size, size, 0, 0, 1) - img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := img3.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -400,7 +401,7 @@ func TestWritePixelsAfterDrawTriangles(t *testing.T) { vs := quadVertices(w, h, 0, 0, 1) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) dst.WritePixels(pix, image.Rect(0, 0, w, h)) pix = make([]byte, 4*w*h) @@ -447,7 +448,7 @@ func TestSmallImages(t *testing.T) { vs := quadVertices(w, h, 0, 0, 1) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) pix = make([]byte, 4*w*h) ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h)) @@ -494,7 +495,7 @@ func TestLongImages(t *testing.T) { vs := quadVertices(w, h, 0, 0, scale) is := graphics.QuadIndices() dr := image.Rect(0, 0, dstW, dstH) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) pix = make([]byte, 4*dstW*dstH) ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, dstW, dstH)) @@ -610,7 +611,7 @@ func TestDeallocatedAndReputOnSourceBackend(t *testing.T) { vs := quadVertices(size, size, 0, 0, 1) is := graphics.QuadIndices() dr := image.Rect(0, 0, size, size) - src.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + src.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := src.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -619,7 +620,7 @@ func TestDeallocatedAndReputOnSourceBackend(t *testing.T) { for i := 0; i < atlas.BaseCountToPutOnSourceBackend/2; i++ { atlas.PutImagesOnSourceBackendForTesting() vs := quadVertices(size, size, 0, 0, 1) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := src.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -653,7 +654,7 @@ func TestImageIsNotReputOnSourceBackendWithoutUsingAsSource(t *testing.T) { // Call DrawTriangles multiple times. // The number of DrawTriangles doesn't matter as long as these are called in one frame. for i := 0; i < 2; i++ { - src2.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + src2.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) } if got, want := src2.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) @@ -672,7 +673,7 @@ func TestImageIsNotReputOnSourceBackendWithoutUsingAsSource(t *testing.T) { for i := 0; i < atlas.BaseCountToPutOnSourceBackend; i++ { atlas.PutImagesOnSourceBackendForTesting() vs := quadVertices(size, size, 0, 0, 1) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if got, want := src2.IsOnSourceBackendForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -798,14 +799,14 @@ func TestDestinationCountOverflow(t *testing.T) { // Use dst0 as a destination for a while. for i := 0; i < 31; i++ { - dst0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) atlas.PutImagesOnSourceBackendForTesting() } // Use dst0 as a source for a while. // As dst0 is used as a destination too many times (31 is a maximum), dst0's backend should never be a source backend. for i := 0; i < 100; i++ { - dst1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{dst0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{dst0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) atlas.PutImagesOnSourceBackendForTesting() if dst0.IsOnSourceBackendForTesting() { t.Errorf("dst0 cannot be on a source backend: %d", i) @@ -831,7 +832,7 @@ func TestIteratingImagesToPutOnSourceBackend(t *testing.T) { is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) for _, img := range srcs { - img.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) } atlas.PutImagesOnSourceBackendForTesting() @@ -839,7 +840,7 @@ func TestIteratingImagesToPutOnSourceBackend(t *testing.T) { // Check iterating the registered image works correctly. for i := 0; i < 100; i++ { for _, src := range srcs { - dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) } atlas.PutImagesOnSourceBackendForTesting() } @@ -887,7 +888,7 @@ func TestDallocateUnmanagedImageBackends(t *testing.T) { vs := quadVertices(w, h, 0, 0, 1) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) // Get the difference of the number of backends before and after the images are deallocated. c := atlas.BackendCountForTesting() diff --git a/internal/atlas/shader_test.go b/internal/atlas/shader_test.go index 5955806b8..a594585fa 100644 --- a/internal/atlas/shader_test.go +++ b/internal/atlas/shader_test.go @@ -23,6 +23,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/atlas" "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/restorable" etesting "github.com/hajimehoshi/ebiten/v2/internal/testing" "github.com/hajimehoshi/ebiten/v2/internal/ui" ) @@ -37,12 +38,12 @@ func TestShaderFillTwice(t *testing.T) { dr := image.Rect(0, 0, w, h) g := ui.Get().GraphicsDriverForTesting() s0 := atlas.NewShader(etesting.ShaderProgramFill(0xff, 0xff, 0xff, 0xff), "") - dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s0, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s0, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) // Vertices must be recreated (#1755) vs = quadVertices(w, h, 0, 0, 1) s1 := atlas.NewShader(etesting.ShaderProgramFill(0x80, 0x80, 0x80, 0xff), "") - dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s1, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s1, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) pix := make([]byte, 4*w*h) ok, err := dst.ReadPixels(g, pix, image.Rect(0, 0, w, h)) @@ -69,11 +70,11 @@ func TestImageDrawTwice(t *testing.T) { vs := quadVertices(w, h, 0, 0, 1) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) // Vertices must be recreated (#1755) vs = quadVertices(w, h, 0, 0, 1) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) pix := make([]byte, 4*w*h) ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h)) @@ -97,7 +98,7 @@ func TestGCShader(t *testing.T) { vs := quadVertices(w, h, 0, 0, 1) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) // Ensure other objects are GCed, as GC appends deferred functions for collected objects. ensureGC() diff --git a/internal/buffered/image.go b/internal/buffered/image.go index a4c22e10a..62c3e8fab 100644 --- a/internal/buffered/image.go +++ b/internal/buffered/image.go @@ -196,7 +196,7 @@ func (i *Image) WritePixels(pix []byte, region image.Rectangle) { // DrawTriangles draws the src image with the given vertices. // // Copying vertices and indices is the caller's responsibility. -func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) { +func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint restorable.Hint) { for _, src := range srcs { if i == src { panic("buffered: Image.DrawTriangles: source images must be different from the receiver") @@ -218,7 +218,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice imgs[i] = img.img } - i.img.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule) + i.img.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint) // After rendering, the pixel cache is no longer valid. i.pixels = nil @@ -311,7 +311,7 @@ func (i *Image) syncPixelsIfNeeded() { srcs := [graphics.ShaderSrcImageCount]*atlas.Image{whiteImage.img} dr := image.Rect(0, 0, i.width, i.height) blend := graphicsdriver.BlendCopy - i.img.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + i.img.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) // TODO: Use clear if Go 1.21 is available. for pos := range i.dotsBuffer { diff --git a/internal/buffered/image_test.go b/internal/buffered/image_test.go index 4a9db4c25..f36b2717a 100644 --- a/internal/buffered/image_test.go +++ b/internal/buffered/image_test.go @@ -22,6 +22,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/buffered" "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/restorable" t "github.com/hajimehoshi/ebiten/v2/internal/testing" "github.com/hajimehoshi/ebiten/v2/internal/ui" ) @@ -56,7 +57,7 @@ func TestUnsyncedPixels(t *testing.T) { is := graphics.QuadIndices() dr := image.Rect(0, 0, 16, 16) sr := [graphics.ShaderSrcImageCount]image.Rectangle{image.Rect(0, 0, 16, 16)} - dst.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, sr, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, sr, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) // Check the result is correct. var got [4]byte diff --git a/internal/mipmap/mipmap.go b/internal/mipmap/mipmap.go index 2145f2e66..e5f68d62f 100644 --- a/internal/mipmap/mipmap.go +++ b/internal/mipmap/mipmap.go @@ -23,6 +23,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/buffered" "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/restorable" ) func canUseMipmap(imageType atlas.ImageType) bool { @@ -65,7 +66,7 @@ func (m *Mipmap) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byt return m.orig.ReadPixels(graphicsDriver, pixels, region) } -func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Mipmap, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool) { +func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Mipmap, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool, hint restorable.Hint) { if len(indices) == 0 { return } @@ -123,7 +124,7 @@ func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Mipmap, verti imgs[i] = src.orig } - m.orig.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule) + m.orig.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint) m.deallocateMipmaps() } @@ -184,7 +185,7 @@ func (m *Mipmap) level(level int) *buffered.Image { s := buffered.NewImage(w2, h2, m.imageType) dstRegion := image.Rect(0, 0, w2, h2) - s.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.LinearFilterShader, nil, graphicsdriver.FillRuleFillAll) + s.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.LinearFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion) m.setImg(level, s) return m.imgs[level] diff --git a/internal/restorable/image.go b/internal/restorable/image.go index f8cbdf3ef..eb9bab33c 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -109,6 +109,18 @@ const ( ImageTypeVolatile ) +// Hint is a hint to optimize the info to restore the image. +type Hint int + +const ( + // HintNone indicates that there is no hint. + HintNone Hint = iota + + // HintOverwriteDstRegion indicates that the destination region is overwritten. + // HintOverwriteDstRegion helps to reduce the size of the draw-image history. + HintOverwriteDstRegion +) + // Image represents an image that can be restored when GL context is lost. type Image struct { image *graphicscommand.Image @@ -194,7 +206,7 @@ func (i *Image) Extend(width, height int) *Image { graphics.QuadVerticesFromDstAndSrc(vs, 0, 0, float32(sw), float32(sh), 0, 0, float32(sw), float32(sh), 1, 1, 1, 1) is := graphics.QuadIndices() dr := image.Rect(0, 0, sw, sh) - newImg.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + newImg.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, HintOverwriteDstRegion) i.Dispose() return newImg @@ -316,7 +328,7 @@ func (i *Image) WritePixels(pixels *graphics.ManagedBytes, region image.Rectangl // 5: Color G // 6: Color B // 7: Color Y -func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) { +func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint Hint) { if len(vertices) == 0 { return } @@ -338,7 +350,8 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice if srcstale || !needsRestoration() || !i.needsRestoration() || i.stale { i.makeStale(dstRegion) } else { - i.appendDrawTrianglesHistory(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule) + // TODO: Consider overwritten. + i.appendDrawTrianglesHistory(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint) } var imgs [graphics.ShaderSrcImageCount]*graphicscommand.Image @@ -352,7 +365,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice } // appendDrawTrianglesHistory appends a draw-image history item to the image. -func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) { +func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint Hint) { if i.stale || !i.needsRestoration() { panic("restorable: an image must not be stale or need restoration at appendDrawTrianglesHistory") } @@ -360,6 +373,24 @@ func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderSrcImageCount]*I panic("restorable: appendDrawTrianglesHistory must not be called when AlwaysReadPixelsFromGPU() returns true") } + // If the command overwrites the destination region, remove the history items that are in the region. + if hint == HintOverwriteDstRegion { + for idx, c := range i.drawTrianglesHistory { + if c.dstRegion.In(dstRegion) { + i.drawTrianglesHistory[idx] = nil + } + } + var n int + for _, c := range i.drawTrianglesHistory { + if c == nil { + continue + } + i.drawTrianglesHistory[n] = c + n++ + } + i.drawTrianglesHistory = i.drawTrianglesHistory[:n] + } + // TODO: Would it be possible to merge draw image history items? const maxDrawTrianglesHistoryCount = 1024 if len(i.drawTrianglesHistory)+1 > maxDrawTrianglesHistoryCount { diff --git a/internal/restorable/images_test.go b/internal/restorable/images_test.go index 39a099b1a..8c434b119 100644 --- a/internal/restorable/images_test.go +++ b/internal/restorable/images_test.go @@ -140,7 +140,7 @@ func TestRestoreChain(t *testing.T) { vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() dr := image.Rect(0, 0, 1, 1) - imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) } if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { t.Fatal(err) @@ -183,10 +183,10 @@ func TestRestoreChain2(t *testing.T) { is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - imgs[8].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[7]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) - imgs[9].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[8]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + imgs[8].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[7]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) + imgs[9].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[8]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) for i := 0; i < 7; i++ { - imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) } if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { @@ -227,10 +227,10 @@ func TestRestoreOverrideSource(t *testing.T) { img1.WritePixels(bytesToManagedBytes([]byte{clr0.R, clr0.G, clr0.B, clr0.A}), image.Rect(0, 0, w, h)) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - img2.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) - img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img2.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) + img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) img0.WritePixels(bytesToManagedBytes([]byte{clr1.R, clr1.G, clr1.B, clr1.A}), image.Rect(0, 0, w, h)) - img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { t.Fatal(err) } @@ -310,23 +310,23 @@ func TestRestoreComplexGraph(t *testing.T) { is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) vs := quadVertices(w, h, 0, 0) - img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) vs = quadVertices(w, h, 1, 0) - img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img3.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) vs = quadVertices(w, h, 1, 0) - img4.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img4.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) vs = quadVertices(w, h, 2, 0) - img4.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img4.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) vs = quadVertices(w, h, 0, 0) - img5.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img5.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) vs = quadVertices(w, h, 0, 0) - img6.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img6.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) vs = quadVertices(w, h, 1, 0) - img6.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img4}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img6.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img4}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) vs = quadVertices(w, h, 0, 0) - img7.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img7.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) vs = quadVertices(w, h, 2, 0) - img7.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img7.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { t.Fatal(err) } @@ -419,8 +419,8 @@ func TestRestoreRecursive(t *testing.T) { }() is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, quadVertices(w, h, 1, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) - img0.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(w, h, 1, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, quadVertices(w, h, 1, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) + img0.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(w, h, 1, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { t.Fatal(err) } @@ -519,7 +519,7 @@ func TestDrawTrianglesAndWritePixels(t *testing.T) { vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() dr := image.Rect(0, 0, 2, 1) - img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) img1.WritePixels(bytesToManagedBytes([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), image.Rect(0, 0, 2, 1)) if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { @@ -557,8 +557,8 @@ func TestDispose(t *testing.T) { is := graphics.QuadIndices() dr := image.Rect(0, 0, 1, 1) - img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, quadVertices(1, 1, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) - img0.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(1, 1, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img2}, quadVertices(1, 1, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) + img0.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img1}, quadVertices(1, 1, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) img1.Dispose() if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { @@ -667,7 +667,7 @@ func TestWritePixelsOnly(t *testing.T) { vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() dr := image.Rect(0, 0, 1, 1) - img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img1.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) img0.WritePixels(bytesToManagedBytes([]byte{5, 6, 7, 8}), image.Rect(0, 0, 1, 1)) // BasePixelsForTesting is available without GPU accessing. @@ -721,7 +721,7 @@ func TestReadPixelsFromVolatileImage(t *testing.T) { vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) // Read the pixels. If the implementation is correct, dst tries to read its pixels from GPU due to being // stale. @@ -745,7 +745,7 @@ func TestAllowWritePixelsAfterDrawTriangles(t *testing.T) { vs := quadVertices(w, h, 0, 0) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) dst.WritePixels(bytesToManagedBytes(make([]byte, 4*w*h)), image.Rect(0, 0, w, h)) // WritePixels for a whole image doesn't panic. } @@ -764,7 +764,7 @@ func TestAllowWritePixelsForPartAfterDrawTriangles(t *testing.T) { vs := quadVertices(w, h, 0, 0) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) dst.WritePixels(bytesToManagedBytes(make([]byte, 4*2*2)), image.Rect(0, 0, 2, 2)) // WritePixels for a part of image doesn't panic. @@ -858,7 +858,7 @@ func TestDrawTrianglesAndExtend(t *testing.T) { vs := quadVertices(w, h, 0, 0) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - orig.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + orig.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) extended := orig.Extend(w*2, h*2) // After this, orig is already disposed. result := make([]byte, 4*(w*2)*(h*2)) @@ -908,7 +908,7 @@ func TestMutateSlices(t *testing.T) { is := make([]uint32, len(graphics.QuadIndices())) copy(is, graphics.QuadIndices()) dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) for i := range vs { vs[i] = 0 } @@ -1100,7 +1100,7 @@ func TestDrawTrianglesAndReadPixels(t *testing.T) { vs := quadVertices(w, h, 0, 0) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) pix := make([]byte, 4*w*h) if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h)); err != nil { @@ -1124,7 +1124,7 @@ func TestWritePixelsAndDrawTriangles(t *testing.T) { vs := quadVertices(1, 1, 1, 0) is := graphics.QuadIndices() dr := image.Rect(1, 0, 2, 1) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) // Get the pixels. pix := make([]byte, 4*2*1) @@ -1138,3 +1138,36 @@ func TestWritePixelsAndDrawTriangles(t *testing.T) { t.Errorf("got: %v, want: %v", got, want) } } + +func TestOverwriteDstRegion(t *testing.T) { + const w, h = 1, 1 + src0 := restorable.NewImage(w, h, restorable.ImageTypeRegular) + src1 := restorable.NewImage(w, h, restorable.ImageTypeRegular) + dst := restorable.NewImage(w, h, restorable.ImageTypeRegular) + + src0.WritePixels(bytesToManagedBytes([]byte{0x40, 0x40, 0x40, 0x40}), image.Rect(0, 0, 1, 1)) + src1.WritePixels(bytesToManagedBytes([]byte{0x80, 0x80, 0x80, 0x80}), image.Rect(0, 0, 1, 1)) + + vs := quadVertices(w, h, 0, 0) + is := graphics.QuadIndices() + dr := image.Rect(0, 0, w, h) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src0}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) + // This tests that HintOverwriteDstRegion removes the previous DrawTriangles. + // In practice, BlendCopy should be used instead of BlendSourceOver in this case. + dst.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{src1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion) + + if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { + t.Fatal(err) + } + if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { + t.Fatal(err) + } + + pix := make([]byte, 4*w*h) + if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h)); err != nil { + t.Fatal(err) + } + if got, want := (color.RGBA{R: pix[0], G: pix[1], B: pix[2], A: pix[3]}), (color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80}); !sameColors(got, want, 1) { + t.Errorf("got: %v, want: %v", got, want) + } +} diff --git a/internal/restorable/shader_test.go b/internal/restorable/shader_test.go index 05f6fbdb4..2e9901502 100644 --- a/internal/restorable/shader_test.go +++ b/internal/restorable/shader_test.go @@ -46,7 +46,7 @@ func clearImage(img *restorable.Image, w, h int) { } is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{emptyImage}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{emptyImage}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) } func TestShader(t *testing.T) { @@ -55,7 +55,7 @@ func TestShader(t *testing.T) { s := restorable.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff), "") dr := image.Rect(0, 0, 1, 1) - img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll) + img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { t.Fatal(err) @@ -85,7 +85,7 @@ func TestShaderChain(t *testing.T) { s := restorable.NewShader(etesting.ShaderProgramImages(1), "") for i := 0; i < num-1; i++ { dr := image.Rect(0, 0, 1, 1) - imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll) + imgs[i+1].DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{imgs[i]}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) } if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { @@ -117,7 +117,7 @@ func TestShaderMultipleSources(t *testing.T) { s := restorable.NewShader(etesting.ShaderProgramImages(3), "") dr := image.Rect(0, 0, 1, 1) - dst.DrawTriangles(srcs, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles(srcs, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) // Clear one of the sources after DrawTriangles. dst should not be affected. clearImage(srcs[0], 1, 1) @@ -154,7 +154,7 @@ func TestShaderMultipleSourcesOnOneTexture(t *testing.T) { image.Rect(1, 0, 2, 1), image.Rect(2, 0, 3, 1), } - dst.DrawTriangles(srcs, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, srcRegions, s, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles(srcs, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, srcRegions, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) // Clear one of the sources after DrawTriangles. dst should not be affected. clearImage(srcs[0], 3, 1) @@ -179,7 +179,7 @@ func TestShaderDispose(t *testing.T) { s := restorable.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff), "") dr := image.Rect(0, 0, 1, 1) - img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll) + img.DrawTriangles([graphics.ShaderSrcImageCount]*restorable.Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone) // Dispose the shader. This should invalidate all the images using this shader i.e., all the images become // stale. diff --git a/internal/ui/image.go b/internal/ui/image.go index 2b56f0e0f..94a03555f 100644 --- a/internal/ui/image.go +++ b/internal/ui/image.go @@ -23,6 +23,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/mipmap" + "github.com/hajimehoshi/ebiten/v2/internal/restorable" ) // panicOnErrorOnReadingPixels indicates whether reading pixels panics on an error or not. @@ -77,7 +78,7 @@ func (i *Image) Deallocate() { i.mipmap.Deallocate() } -func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool, antialias bool) { +func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool, antialias bool, hint restorable.Hint) { if i.modifyCallback != nil { i.modifyCallback() } @@ -113,7 +114,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice srcMipmaps[i] = src.mipmap } - i.mipmap.DrawTriangles(srcMipmaps, vertices, indices, blend, dstRegion, srcRegions, shader.shader, uniforms, fillRule, canSkipMipmap) + i.mipmap.DrawTriangles(srcMipmaps, vertices, indices, blend, dstRegion, srcRegions, shader.shader, uniforms, fillRule, canSkipMipmap, hint) } func (i *Image) WritePixels(pix []byte, region image.Rectangle) { @@ -183,7 +184,7 @@ func (i *Image) Fill(r, g, b, a float32, region image.Rectangle) { blend = graphicsdriver.BlendSourceOver } // i.lastBlend is updated in DrawTriangles. - i.DrawTriangles(srcs, i.tmpVerticesForFill, is, blend, region, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false) + i.DrawTriangles(srcs, i.tmpVerticesForFill, is, blend, region, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false, restorable.HintOverwriteDstRegion) } type bigOffscreenImage struct { @@ -252,7 +253,7 @@ func (i *bigOffscreenImage) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Im 1, 1, 1, 1) is := graphics.QuadIndices() dstRegion := image.Rect(0, 0, i.region.Dx()*bigOffscreenScale, i.region.Dy()*bigOffscreenScale) - i.image.DrawTriangles(srcs, i.tmpVerticesForCopying, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false) + i.image.DrawTriangles(srcs, i.tmpVerticesForCopying, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false, restorable.HintOverwriteDstRegion) } for idx := 0; idx < len(vertices); idx += graphics.VertexFloatCount { @@ -268,7 +269,7 @@ func (i *bigOffscreenImage) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Im dstRegion.Max.X *= bigOffscreenScale dstRegion.Max.Y *= bigOffscreenScale - i.image.DrawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap, false) + i.image.DrawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap, false, restorable.HintNone) i.dirty = true } @@ -297,10 +298,12 @@ func (i *bigOffscreenImage) flush() { is := graphics.QuadIndices() dstRegion := i.region blend := graphicsdriver.BlendSourceOver + hint := restorable.HintNone if i.blend != graphicsdriver.BlendSourceOver { blend = graphicsdriver.BlendCopy + hint = restorable.HintOverwriteDstRegion } - i.orig.DrawTriangles(srcs, i.tmpVerticesForFlushing, is, blend, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, LinearFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false) + i.orig.DrawTriangles(srcs, i.tmpVerticesForFlushing, is, blend, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, LinearFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false, hint) i.image.clear() i.dirty = false