diff --git a/internal/atlas/image.go b/internal/atlas/image.go index 45349df76..cde31835e 100644 --- a/internal/atlas/image.go +++ b/internal/atlas/image.go @@ -817,11 +817,6 @@ func BeginFrame(graphicsDriver graphicsdriver.Graphics) error { return err } - // Restore images first before other image manipulations (#2075). - if err := restorable.RestoreIfNeeded(graphicsDriver); err != nil { - return err - } - flushDeferred() putImagesOnSourceBackend(graphicsDriver) diff --git a/internal/buffered/image.go b/internal/buffered/image.go index 54fdade6c..2f42eb9a2 100644 --- a/internal/buffered/image.go +++ b/internal/buffered/image.go @@ -21,7 +21,6 @@ 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" ) type Image struct { @@ -50,14 +49,6 @@ func (i *Image) Deallocate() { } func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error { - // If restorable.AlwaysReadPixelsFromGPU() returns false, the pixel data is cached in the restorable package. - if !restorable.AlwaysReadPixelsFromGPU() { - if err := i.img.ReadPixels(graphicsDriver, pixels, region); err != nil { - return err - } - return nil - } - if i.pixels == nil { pix := make([]byte, 4*i.width*i.height) if err := i.img.ReadPixels(graphicsDriver, pix, image.Rect(0, 0, i.width, i.height)); err != nil { diff --git a/internal/restorable/export_test.go b/internal/restorable/export_test.go index 10071b879..aad181758 100644 --- a/internal/restorable/export_test.go +++ b/internal/restorable/export_test.go @@ -16,14 +16,8 @@ package restorable import ( "image" - - "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" ) -func ResolveStaleImages(graphicsDriver graphicsdriver.Graphics) error { - return resolveStaleImages(graphicsDriver, false) -} - func AppendRegionRemovingDuplicates(regions *[]image.Rectangle, region image.Rectangle) { appendRegionRemovingDuplicates(regions, region) } diff --git a/internal/restorable/image.go b/internal/restorable/image.go index 088181834..749fa5074 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -219,7 +219,7 @@ func (i *Image) makeStale(rect image.Rectangle) { i.stale = true // If ReadPixels always reads pixels from GPU, staleRegions are never used. - if AlwaysReadPixelsFromGPU() { + if alwaysReadPixelsFromGPU() { return } @@ -278,36 +278,7 @@ func (i *Image) WritePixels(pixels *graphics.ManagedBytes, region image.Rectangl } // Even if the image is already stale, call makeStale to extend the stale region. - if !needsRestoring() || !i.needsRestoring() || i.stale { - i.makeStale(region) - return - } - - if region.Eq(image.Rect(0, 0, w, h)) { - if pixels != nil { - // Clone a ManagedBytes as the package graphicscommand has a different lifetime management. - i.basePixels.AddOrReplace(pixels.Clone(), image.Rect(0, 0, w, h)) - } else { - i.basePixels.Clear(image.Rect(0, 0, w, h)) - } - i.clearDrawTrianglesHistory() - i.stale = false - i.staleRegions = i.staleRegions[:0] - return - } - - // Records for DrawTriangles cannot come before records for WritePixels. - if len(i.drawTrianglesHistory) > 0 { - i.makeStale(region) - return - } - - if pixels != nil { - // Clone a ManagedBytes as the package graphicscommand has a different lifetime management. - i.basePixels.AddOrReplace(pixels.Clone(), region) - } else { - i.basePixels.Clear(region) - } + i.makeStale(region) } // DrawTriangles draws triangles with the given image. @@ -328,24 +299,8 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [ } theImages.makeStaleIfDependingOn(i) - // TODO: Add tests to confirm this logic. - var srcstale bool - for _, src := range srcs { - if src == nil { - continue - } - if src.stale || src.imageType == ImageTypeVolatile { - srcstale = true - break - } - } - // Even if the image is already stale, call makeStale to extend the stale region. - if srcstale || !needsRestoring() || !i.needsRestoring() || i.stale { - i.makeStale(dstRegion) - } else { - i.appendDrawTrianglesHistory(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule) - } + i.makeStale(dstRegion) var imgs [graphics.ShaderImageCount]*graphicscommand.Image for i, src := range srcs { @@ -362,7 +317,7 @@ func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageCount]*Imag if i.stale || !i.needsRestoring() { panic("restorable: an image must not be stale or need restoring at appendDrawTrianglesHistory") } - if AlwaysReadPixelsFromGPU() { + if alwaysReadPixelsFromGPU() { panic("restorable: appendDrawTrianglesHistory must not be called when AlwaysReadPixelsFromGPU() returns true") } @@ -408,7 +363,7 @@ func (i *Image) readPixelsFromGPUIfNeeded(graphicsDriver graphicsdriver.Graphics } func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error { - if AlwaysReadPixelsFromGPU() { + if alwaysReadPixelsFromGPU() { if err := i.image.ReadPixels(graphicsDriver, []graphicsdriver.PixelsArgs{ { Pixels: pixels, @@ -504,20 +459,6 @@ func (i *Image) readPixelsFromGPU(graphicsDriver graphicsdriver.Graphics) error return nil } -// resolveStale resolves the image's 'stale' state. -func (i *Image) resolveStale(graphicsDriver graphicsdriver.Graphics) error { - if !needsRestoring() { - return nil - } - if !i.needsRestoring() { - return nil - } - if !i.stale { - return nil - } - return i.readPixelsFromGPU(graphicsDriver) -} - // dependsOn reports whether the image depends on target. func (i *Image) dependsOn(target *Image) bool { for _, c := range i.drawTrianglesHistory { diff --git a/internal/restorable/images.go b/internal/restorable/images.go index 4127380b9..2560fcd69 100644 --- a/internal/restorable/images.go +++ b/internal/restorable/images.go @@ -20,22 +20,8 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" ) -// forceRestoring reports whether restoring forcely happens or not. -var forceRestoring = false - -// needsRestoring reports whether restoring process works or not. -func needsRestoring() bool { - return forceRestoring -} - -// AlwaysReadPixelsFromGPU reports whether ReadPixels always reads pixels from GPU or not. -func AlwaysReadPixelsFromGPU() bool { - return !needsRestoring() -} - -// EnableRestoringForTesting forces to enable restoring for testing. -func EnableRestoringForTesting() { - forceRestoring = true +func alwaysReadPixelsFromGPU() bool { + return true } // images is a set of Image objects. @@ -60,56 +46,10 @@ func SwapBuffers(graphicsDriver graphicsdriver.Graphics) error { } graphicscommand.LogImagesInfo(imgs) } - return resolveStaleImages(graphicsDriver, true) -} - -// resolveStaleImages flushes the queued draw commands and resolves all stale images. -// If endFrame is true, the current screen might be used to present when flushing the commands. -func resolveStaleImages(graphicsDriver graphicsdriver.Graphics, endFrame bool) error { - if err := graphicscommand.FlushCommands(graphicsDriver, endFrame); err != nil { + if err := graphicscommand.FlushCommands(graphicsDriver, true); err != nil { return err } - if !needsRestoring() { - return nil - } - return theImages.resolveStaleImages(graphicsDriver) -} - -// RestoreIfNeeded restores the images. -// -// Restoring means to make all *graphicscommand.Image objects have their textures and framebuffers. -func RestoreIfNeeded(graphicsDriver graphicsdriver.Graphics) error { - if !needsRestoring() { - return nil - } - - if !forceRestoring { - var r bool - - // As isInvalidated() is expensive, call this only for one image. - // This assumes that if there is one image that is invalidated, all images are invalidated. - for img := range theImages.images { - // The screen image might not have a texture. Skip this. - if img.imageType == ImageTypeScreen { - continue - } - var err error - r, err = img.isInvalidated(graphicsDriver) - if err != nil { - return err - } - break - } - - if !r { - return nil - } - } - - if err := graphicscommand.ResetGraphicsDriverState(graphicsDriver); err != nil { - return err - } - return theImages.restore(graphicsDriver) + return nil } // DumpImages dumps all the current images to the specified directory. @@ -144,17 +84,6 @@ func (i *images) removeShader(shader *Shader) { delete(i.shaders, shader) } -// resolveStaleImages resolves stale images. -func (i *images) resolveStaleImages(graphicsDriver graphicsdriver.Graphics) error { - i.lastTarget = nil - for img := range i.images { - if err := img.resolveStale(graphicsDriver); err != nil { - return err - } - } - return nil -} - // makeStaleIfDependingOn makes all the images stale that depend on target. // // When target is modified, all images depending on target can't be restored with target. @@ -182,83 +111,6 @@ func (i *images) makeStaleIfDependingOnShader(shader *Shader) { } } -// restore restores the images. -// -// Restoring means to make all *graphicscommand.Image objects have their textures and framebuffers. -func (i *images) restore(graphicsDriver graphicsdriver.Graphics) error { - if !needsRestoring() { - panic("restorable: restore cannot be called when restoring is disabled") - } - - // Dispose all the shaders ahead of restoring. A current shader ID and a new shader ID can be duplicated. - for s := range i.shaders { - s.shader.Dispose() - s.shader = nil - } - for s := range i.shaders { - s.restore() - } - - // Dispose all the images ahead of restoring. A current texture ID and a new texture ID can be duplicated. - // TODO: Write a test to confirm that ID duplication never happens. - for i := range i.images { - i.image.Dispose() - i.image = nil - } - - // Let's do topological sort based on dependencies of drawing history. - // It is assured that there are not loops since cyclic drawing makes images stale. - type edge struct { - source *Image - target *Image - } - images := map[*Image]struct{}{} - for i := range i.images { - images[i] = struct{}{} - } - edges := map[edge]struct{}{} - for t := range images { - for s := range t.dependingImages() { - edges[edge{source: s, target: t}] = struct{}{} - } - } - - var sorted []*Image - for len(images) > 0 { - // current represents images that have no incoming edges. - current := map[*Image]struct{}{} - for i := range images { - current[i] = struct{}{} - } - for e := range edges { - if _, ok := current[e.target]; ok { - delete(current, e.target) - } - } - for i := range current { - delete(images, i) - sorted = append(sorted, i) - } - removed := []edge{} - for e := range edges { - if _, ok := current[e.source]; ok { - removed = append(removed, e) - } - } - for _, e := range removed { - delete(edges, e) - } - } - - for _, img := range sorted { - if err := img.restore(graphicsDriver); err != nil { - return err - } - } - - return nil -} - var graphicsDriverInitialized bool // InitializeGraphicsDriverState initializes the graphics driver state. diff --git a/internal/restorable/images_test.go b/internal/restorable/images_test.go deleted file mode 100644 index 46b3a4b25..000000000 --- a/internal/restorable/images_test.go +++ /dev/null @@ -1,1143 +0,0 @@ -// Copyright 2017 The Ebiten Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package restorable_test - -import ( - "image" - "image/color" - "testing" - - "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" -) - -func TestMain(m *testing.M) { - restorable.EnableRestoringForTesting() - etesting.MainWithRunLoop(m) -} - -func pixelsToColor(p *restorable.Pixels, i, j, imageWidth, imageHeight int) color.RGBA { - var pix [4]byte - p.ReadPixels(pix[:], image.Rect(i, j, i+1, j+1), imageWidth, imageHeight) - return color.RGBA{R: pix[0], G: pix[1], B: pix[2], A: pix[3]} -} - -func bytesToManagedBytes(src []byte) *graphics.ManagedBytes { - if len(src) == 0 { - panic("restorable: len(src) must be > 0") - } - return graphics.NewManagedBytes(len(src), func(dst []byte) { - copy(dst, src) - }) -} - -func abs(x int) int { - if x < 0 { - return -x - } - return x -} - -// sameColors compares c1 and c2 and returns a boolean value indicating -// if the two colors are (almost) same. -// -// Pixels read from GPU might include errors (#492), and -// sameColors considers such errors as delta. -func sameColors(c1, c2 color.RGBA, delta int) bool { - return abs(int(c1.R)-int(c2.R)) <= delta && - abs(int(c1.G)-int(c2.G)) <= delta && - abs(int(c1.B)-int(c2.B)) <= delta && - abs(int(c1.A)-int(c2.A)) <= delta -} - -func TestRestore(t *testing.T) { - img0 := restorable.NewImage(1, 1, restorable.ImageTypeRegular) - defer img0.Dispose() - - clr0 := color.RGBA{A: 0xff} - img0.WritePixels(bytesToManagedBytes([]byte{clr0.R, clr0.G, clr0.B, clr0.A}), image.Rect(0, 0, 1, 1)) - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - want := clr0 - got := pixelsToColor(img0.BasePixelsForTesting(), 0, 0, 1, 1) - if !sameColors(got, want, 1) { - t.Errorf("got %v, want %v", got, want) - } -} - -func TestRestoreWithoutDraw(t *testing.T) { - img0 := restorable.NewImage(1024, 1024, restorable.ImageTypeRegular) - defer img0.Dispose() - - // If there is no drawing command on img0, img0 is cleared when restored. - - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - - for j := 0; j < 1024; j++ { - for i := 0; i < 1024; i++ { - want := color.RGBA{} - got := pixelsToColor(img0.BasePixelsForTesting(), i, j, 1024, 1024) - if !sameColors(got, want, 0) { - t.Errorf("got %v, want %v", got, want) - } - } - } -} - -func quadVertices(sw, sh, x, y int) []float32 { - dx0 := float32(x) - dy0 := float32(y) - dx1 := float32(x + sw) - dy1 := float32(y + sh) - sx0 := float32(0) - sy0 := float32(0) - sx1 := float32(sw) - sy1 := float32(sh) - return []float32{ - dx0, dy0, sx0, sy0, 1, 1, 1, 1, - dx1, dy0, sx1, sy0, 1, 1, 1, 1, - dx0, dy1, sx0, sy1, 1, 1, 1, 1, - dx1, dy1, sx1, sy1, 1, 1, 1, 1, - } -} - -func TestRestoreChain(t *testing.T) { - const num = 10 - imgs := []*restorable.Image{} - for i := 0; i < num; i++ { - img := restorable.NewImage(1, 1, restorable.ImageTypeRegular) - imgs = append(imgs, img) - } - defer func() { - for _, img := range imgs { - img.Dispose() - } - }() - clr := color.RGBA{A: 0xff} - imgs[0].WritePixels(bytesToManagedBytes([]byte{clr.R, clr.G, clr.B, clr.A}), image.Rect(0, 0, 1, 1)) - for i := 0; i < num-1; i++ { - vs := quadVertices(1, 1, 0, 0) - is := graphics.QuadIndices() - dr := image.Rect(0, 0, 1, 1) - imgs[i+1].DrawTriangles([graphics.ShaderImageCount]*restorable.Image{imgs[i]}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - } - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - want := clr - for i, img := range imgs { - got := pixelsToColor(img.BasePixelsForTesting(), 0, 0, 1, 1) - if !sameColors(got, want, 1) { - t.Errorf("%d: got %v, want %v", i, got, want) - } - } -} - -func TestRestoreChain2(t *testing.T) { - const ( - num = 10 - w = 1 - h = 1 - ) - imgs := []*restorable.Image{} - for i := 0; i < num; i++ { - img := restorable.NewImage(w, h, restorable.ImageTypeRegular) - imgs = append(imgs, img) - } - defer func() { - for _, img := range imgs { - img.Dispose() - } - }() - - clr0 := color.RGBA{R: 0xff, A: 0xff} - imgs[0].WritePixels(bytesToManagedBytes([]byte{clr0.R, clr0.G, clr0.B, clr0.A}), image.Rect(0, 0, w, h)) - clr7 := color.RGBA{G: 0xff, A: 0xff} - imgs[7].WritePixels(bytesToManagedBytes([]byte{clr7.R, clr7.G, clr7.B, clr7.A}), image.Rect(0, 0, w, h)) - clr8 := color.RGBA{B: 0xff, A: 0xff} - imgs[8].WritePixels(bytesToManagedBytes([]byte{clr8.R, clr8.G, clr8.B, clr8.A}), image.Rect(0, 0, w, h)) - - is := graphics.QuadIndices() - dr := image.Rect(0, 0, w, h) - imgs[8].DrawTriangles([graphics.ShaderImageCount]*restorable.Image{imgs[7]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - imgs[9].DrawTriangles([graphics.ShaderImageCount]*restorable.Image{imgs[8]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - for i := 0; i < 7; i++ { - imgs[i+1].DrawTriangles([graphics.ShaderImageCount]*restorable.Image{imgs[i]}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - } - - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - for i, img := range imgs { - want := clr0 - if i == 8 || i == 9 { - want = clr7 - } - got := pixelsToColor(img.BasePixelsForTesting(), 0, 0, w, h) - if !sameColors(got, want, 1) { - t.Errorf("%d: got %v, want %v", i, got, want) - } - } -} - -func TestRestoreOverrideSource(t *testing.T) { - const ( - w = 1 - h = 1 - ) - img0 := restorable.NewImage(w, h, restorable.ImageTypeRegular) - img1 := restorable.NewImage(w, h, restorable.ImageTypeRegular) - img2 := restorable.NewImage(w, h, restorable.ImageTypeRegular) - img3 := restorable.NewImage(w, h, restorable.ImageTypeRegular) - defer func() { - img3.Dispose() - img2.Dispose() - img1.Dispose() - img0.Dispose() - }() - clr0 := color.RGBA{A: 0xff} - clr1 := color.RGBA{B: 0x01, A: 0xff} - 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.ShaderImageCount]*restorable.Image{img1}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - img3.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img2}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - img0.WritePixels(bytesToManagedBytes([]byte{clr1.R, clr1.G, clr1.B, clr1.A}), image.Rect(0, 0, w, h)) - img1.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img0}, quadVertices(w, h, 0, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - testCases := []struct { - name string - want color.RGBA - got color.RGBA - }{ - { - "0", - clr1, - pixelsToColor(img0.BasePixelsForTesting(), 0, 0, w, h), - }, - { - "1", - clr1, - pixelsToColor(img1.BasePixelsForTesting(), 0, 0, w, h), - }, - { - "2", - clr0, - pixelsToColor(img2.BasePixelsForTesting(), 0, 0, w, h), - }, - { - "3", - clr0, - pixelsToColor(img3.BasePixelsForTesting(), 0, 0, w, h), - }, - } - for _, c := range testCases { - if !sameColors(c.got, c.want, 1) { - t.Errorf("%s: got %v, want %v", c.name, c.got, c.want) - } - } -} - -func TestRestoreComplexGraph(t *testing.T) { - const ( - w = 4 - h = 1 - ) - // 0 -> 3 - // 1 -> 3 - // 1 -> 4 - // 2 -> 4 - // 2 -> 7 - // 3 -> 5 - // 3 -> 6 - // 3 -> 7 - // 4 -> 6 - base := image.NewRGBA(image.Rect(0, 0, w, h)) - base.Pix[0] = 0xff - base.Pix[1] = 0xff - base.Pix[2] = 0xff - base.Pix[3] = 0xff - img0 := newImageFromImage(base) - img1 := newImageFromImage(base) - img2 := newImageFromImage(base) - img3 := restorable.NewImage(w, h, restorable.ImageTypeRegular) - img4 := restorable.NewImage(w, h, restorable.ImageTypeRegular) - img5 := restorable.NewImage(w, h, restorable.ImageTypeRegular) - img6 := restorable.NewImage(w, h, restorable.ImageTypeRegular) - img7 := restorable.NewImage(w, h, restorable.ImageTypeRegular) - defer func() { - img7.Dispose() - img6.Dispose() - img5.Dispose() - img4.Dispose() - img3.Dispose() - img2.Dispose() - img1.Dispose() - img0.Dispose() - }() - is := graphics.QuadIndices() - dr := image.Rect(0, 0, w, h) - vs := quadVertices(w, h, 0, 0) - img3.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - vs = quadVertices(w, h, 1, 0) - img3.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - vs = quadVertices(w, h, 1, 0) - img4.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img1}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - vs = quadVertices(w, h, 2, 0) - img4.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img2}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - vs = quadVertices(w, h, 0, 0) - img5.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - vs = quadVertices(w, h, 0, 0) - img6.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - vs = quadVertices(w, h, 1, 0) - img6.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img4}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - vs = quadVertices(w, h, 0, 0) - img7.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img2}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - vs = quadVertices(w, h, 2, 0) - img7.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img3}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - testCases := []struct { - name string - out string - image *restorable.Image - }{ - { - "0", - "*---", - img0, - }, - { - "1", - "*---", - img1, - }, - { - "2", - "*---", - img2, - }, - { - "3", - "**--", - img3, - }, - { - "4", - "-**-", - img4, - }, - { - "5", - "**--", - img5, - }, - { - "6", - "****", - img6, - }, - { - "7", - "*-**", - img7, - }, - } - for _, c := range testCases { - for i := 0; i < 4; i++ { - want := color.RGBA{} - if c.out[i] == '*' { - want = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff} - } - got := pixelsToColor(c.image.BasePixelsForTesting(), i, 0, w, h) - if !sameColors(got, want, 1) { - t.Errorf("%s[%d]: got %v, want %v", c.name, i, got, want) - } - } - } -} - -func newImageFromImage(rgba *image.RGBA) *restorable.Image { - s := rgba.Bounds().Size() - img := restorable.NewImage(s.X, s.Y, restorable.ImageTypeRegular) - img.WritePixels(bytesToManagedBytes(rgba.Pix), image.Rect(0, 0, s.X, s.Y)) - return img -} - -func TestRestoreRecursive(t *testing.T) { - const ( - w = 4 - h = 1 - ) - base := image.NewRGBA(image.Rect(0, 0, w, h)) - base.Pix[0] = 0xff - base.Pix[1] = 0xff - base.Pix[2] = 0xff - base.Pix[3] = 0xff - - img0 := newImageFromImage(base) - img1 := restorable.NewImage(w, h, restorable.ImageTypeRegular) - defer func() { - img1.Dispose() - img0.Dispose() - }() - is := graphics.QuadIndices() - dr := image.Rect(0, 0, w, h) - img1.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img0}, quadVertices(w, h, 1, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - img0.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img1}, quadVertices(w, h, 1, 0), is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - testCases := []struct { - name string - out string - image *restorable.Image - }{ - { - "0", - "*-*-", - img0, - }, - { - "1", - "-*--", - img1, - }, - } - for _, c := range testCases { - for i := 0; i < 4; i++ { - want := color.RGBA{} - if c.out[i] == '*' { - want = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff} - } - got := pixelsToColor(c.image.BasePixelsForTesting(), i, 0, w, h) - if !sameColors(got, want, 1) { - t.Errorf("%s[%d]: got %v, want %v", c.name, i, got, want) - } - } - } -} - -func TestWritePixels(t *testing.T) { - img := restorable.NewImage(17, 31, restorable.ImageTypeRegular) - defer img.Dispose() - - pix := make([]byte, 4*4*4) - for i := range pix { - pix[i] = 0xff - } - img.WritePixels(bytesToManagedBytes(pix), image.Rect(5, 7, 9, 11)) - // Check the region (5, 7)-(9, 11). Outside state is indeterminate. - pix = make([]byte, 4*4*4) - for i := range pix { - pix[i] = 0 - } - if err := img.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(5, 7, 9, 11)); err != nil { - t.Fatal(err) - } - for j := 7; j < 11; j++ { - for i := 5; i < 9; i++ { - idx := 4 * ((j-7)*4 + i - 5) - got := color.RGBA{R: pix[idx], G: pix[idx+1], B: pix[idx+2], A: pix[idx+3]} - want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff} - if got != want { - t.Errorf("(%d, %d): got: %v, want: %v", i, j, got, want) - } - } - } - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := img.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(5, 7, 9, 11)); err != nil { - t.Fatal(err) - } - for j := 7; j < 11; j++ { - for i := 5; i < 9; i++ { - idx := 4 * ((j-7)*4 + i - 5) - got := color.RGBA{R: pix[idx], G: pix[idx+1], B: pix[idx+2], A: pix[idx+3]} - want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff} - if got != want { - t.Errorf("(%d, %d): got: %v, want: %v", i, j, got, want) - } - } - } -} - -func TestDrawTrianglesAndWritePixels(t *testing.T) { - base := image.NewRGBA(image.Rect(0, 0, 1, 1)) - base.Pix[0] = 0xff - base.Pix[1] = 0 - base.Pix[2] = 0 - base.Pix[3] = 0xff - img0 := newImageFromImage(base) - defer img0.Dispose() - img1 := restorable.NewImage(2, 1, restorable.ImageTypeRegular) - defer img1.Dispose() - - vs := quadVertices(1, 1, 0, 0) - is := graphics.QuadIndices() - dr := image.Rect(0, 0, 2, 1) - img1.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - 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 { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - var pix [4]byte - if err := img1.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix[:], image.Rect(0, 0, 1, 1)); err != nil { - t.Fatal(err) - } - got := color.RGBA{R: pix[0], G: pix[1], B: pix[2], A: pix[3]} - want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff} - if !sameColors(got, want, 1) { - t.Errorf("got: %v, want: %v", got, want) - } -} - -func TestDispose(t *testing.T) { - base0 := image.NewRGBA(image.Rect(0, 0, 1, 1)) - img0 := newImageFromImage(base0) - defer img0.Dispose() - - base1 := image.NewRGBA(image.Rect(0, 0, 1, 1)) - img1 := newImageFromImage(base1) - - base2 := image.NewRGBA(image.Rect(0, 0, 1, 1)) - base2.Pix[0] = 0xff - base2.Pix[1] = 0xff - base2.Pix[2] = 0xff - base2.Pix[3] = 0xff - img2 := newImageFromImage(base2) - defer img2.Dispose() - - is := graphics.QuadIndices() - dr := image.Rect(0, 0, 1, 1) - img1.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img2}, quadVertices(1, 1, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - img0.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img1}, quadVertices(1, 1, 0, 0), is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - img1.Dispose() - - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - var pix [4]byte - if err := img0.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix[:], image.Rect(0, 0, 1, 1)); err != nil { - t.Fatal(err) - } - got := color.RGBA{R: pix[0], G: pix[1], B: pix[2], A: pix[3]} - want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff} - if !sameColors(got, want, 1) { - t.Errorf("got: %v, want: %v", got, want) - } -} - -func TestWritePixelsPart(t *testing.T) { - pix := make([]uint8, 4*2*2) - for i := range pix { - pix[i] = 0xff - } - - img := restorable.NewImage(4, 4, restorable.ImageTypeRegular) - // This doesn't make the image stale. Its base pixels are available. - img.WritePixels(bytesToManagedBytes(pix), image.Rect(1, 1, 3, 3)) - - cases := []struct { - i int - j int - want color.RGBA - }{ - { - i: 0, - j: 0, - want: color.RGBA{}, - }, - { - i: 3, - j: 0, - want: color.RGBA{}, - }, - { - i: 0, - j: 1, - want: color.RGBA{}, - }, - { - i: 1, - j: 1, - want: color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}, - }, - { - i: 3, - j: 1, - want: color.RGBA{}, - }, - { - i: 0, - j: 2, - want: color.RGBA{}, - }, - { - i: 2, - j: 2, - want: color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}, - }, - { - i: 3, - j: 2, - want: color.RGBA{}, - }, - { - i: 0, - j: 3, - want: color.RGBA{}, - }, - { - i: 3, - j: 3, - want: color.RGBA{}, - }, - } - for _, c := range cases { - got := pixelsToColor(img.BasePixelsForTesting(), c.i, c.j, 4, 4) - want := c.want - if got != want { - t.Errorf("base pixel (%d, %d): got %v, want %v", c.i, c.j, got, want) - } - } -} - -func TestWritePixelsOnly(t *testing.T) { - const w, h = 128, 128 - img0 := restorable.NewImage(w, h, restorable.ImageTypeRegular) - defer img0.Dispose() - img1 := restorable.NewImage(1, 1, restorable.ImageTypeRegular) - defer img1.Dispose() - - for i := 0; i < w*h; i += 5 { - img0.WritePixels(bytesToManagedBytes([]byte{1, 2, 3, 4}), image.Rect(i%w, i/w, i%w+1, i/w+1)) - } - - vs := quadVertices(1, 1, 0, 0) - is := graphics.QuadIndices() - dr := image.Rect(0, 0, 1, 1) - img1.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{img0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - img0.WritePixels(bytesToManagedBytes([]byte{5, 6, 7, 8}), image.Rect(0, 0, 1, 1)) - - // BasePixelsForTesting is available without GPU accessing. - for j := 0; j < h; j++ { - for i := 0; i < w; i++ { - idx := j*w + i - var want color.RGBA - switch { - case idx == 0: - want = color.RGBA{R: 5, G: 6, B: 7, A: 8} - case idx%5 == 0: - want = color.RGBA{R: 1, G: 2, B: 3, A: 4} - } - got := pixelsToColor(img0.BasePixelsForTesting(), i, j, w, h) - if !sameColors(got, want, 0) { - t.Errorf("got %v, want %v", got, want) - } - } - } - - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - want := color.RGBA{R: 1, G: 2, B: 3, A: 4} - got := pixelsToColor(img1.BasePixelsForTesting(), 0, 0, w, h) - if !sameColors(got, want, 0) { - t.Errorf("got %v, want %v", got, want) - } -} - -// TODO: How about volatile/screen images? - -// Issue #793 -func TestReadPixelsFromVolatileImage(t *testing.T) { - const w, h = 16, 16 - dst := restorable.NewImage(w, h, restorable.ImageTypeVolatile) - src := restorable.NewImage(w, h, restorable.ImageTypeRegular) - - // First, make sure that dst has pixels - dst.WritePixels(bytesToManagedBytes(make([]byte, 4*w*h)), image.Rect(0, 0, w, h)) - - // Second, draw src to dst. If the implementation is correct, dst becomes stale. - pix := make([]byte, 4*w*h) - for i := range pix { - pix[i] = 0xff - } - src.WritePixels(bytesToManagedBytes(pix), image.Rect(0, 0, w, h)) - vs := quadVertices(1, 1, 0, 0) - is := graphics.QuadIndices() - dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - - // Read the pixels. If the implementation is correct, dst tries to read its pixels from GPU due to being - // stale. - want := byte(0xff) - - var result [4]byte - if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), result[:], image.Rect(0, 0, 1, 1)); err != nil { - t.Fatal(err) - } - got := result[0] - if got != want { - t.Errorf("got: %v, want: %v", got, want) - } -} - -func TestAllowWritePixelsAfterDrawTriangles(t *testing.T) { - const w, h = 16, 16 - src := restorable.NewImage(w, h, restorable.ImageTypeRegular) - dst := restorable.NewImage(w, h, restorable.ImageTypeRegular) - - vs := quadVertices(w, h, 0, 0) - is := graphics.QuadIndices() - dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - dst.WritePixels(bytesToManagedBytes(make([]byte, 4*w*h)), image.Rect(0, 0, w, h)) - // WritePixels for a whole image doesn't panic. -} - -func TestAllowWritePixelsForPartAfterDrawTriangles(t *testing.T) { - const w, h = 16, 16 - src := restorable.NewImage(w, h, restorable.ImageTypeRegular) - dst := restorable.NewImage(w, h, restorable.ImageTypeRegular) - - pix := make([]byte, 4*w*h) - for i := range pix { - pix[i] = 0xff - } - src.WritePixels(bytesToManagedBytes(pix), image.Rect(0, 0, w, h)) - - vs := quadVertices(w, h, 0, 0) - is := graphics.QuadIndices() - dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - dst.WritePixels(bytesToManagedBytes(make([]byte, 4*2*2)), image.Rect(0, 0, 2, 2)) - // WritePixels for a part of image doesn't panic. - - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - - result := make([]byte, 4*w*h) - if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), result, image.Rect(0, 0, w, h)); err != nil { - t.Fatal(err) - } - for j := 0; j < h; j++ { - for i := 0; i < w; i++ { - got := color.RGBA{R: result[4*(j*w+i)], G: result[4*(j*w+i)+1], B: result[4*(j*w+i)+2], A: result[4*(j*w+i)+3]} - var want color.RGBA - if i >= 2 || j >= 2 { - want = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff} - } - if got != want { - t.Errorf("color at (%d, %d): got: %v, want: %v", i, j, got, want) - } - } - } -} - -func TestExtend(t *testing.T) { - pixAt := func(i, j int) byte { - return byte(17*i + 13*j + 0x40) - } - - const w, h = 16, 16 - orig := restorable.NewImage(w, h, restorable.ImageTypeRegular) - pix := make([]byte, 4*w*h) - for j := 0; j < h; j++ { - for i := 0; i < w; i++ { - idx := j*w + i - v := pixAt(i, j) - pix[4*idx] = v - pix[4*idx+1] = v - pix[4*idx+2] = v - pix[4*idx+3] = v - } - } - - orig.WritePixels(bytesToManagedBytes(pix), image.Rect(0, 0, w, h)) - extended := orig.Extend(w*2, h*2) // After this, orig is already disposed. - - result := make([]byte, 4*(w*2)*(h*2)) - if err := extended.ReadPixels(ui.Get().GraphicsDriverForTesting(), result, image.Rect(0, 0, w*2, h*2)); err != nil { - t.Fatal(err) - } - for j := 0; j < h*2; j++ { - for i := 0; i < w*2; i++ { - got := result[4*(j*(w*2)+i)] - want := byte(0) - if i < w && j < h { - want = pixAt(i, j) - } - if got != want { - t.Errorf("extended.At(%d, %d): got: %v, want: %v", i, j, got, want) - } - } - } -} - -func TestDrawTrianglesAndExtend(t *testing.T) { - pixAt := func(i, j int) byte { - return byte(17*i + 13*j + 0x40) - } - - const w, h = 16, 16 - - src := restorable.NewImage(w, h, restorable.ImageTypeRegular) - pix := make([]byte, 4*w*h) - for j := 0; j < h; j++ { - for i := 0; i < w; i++ { - idx := j*w + i - v := pixAt(i, j) - pix[4*idx] = v - pix[4*idx+1] = v - pix[4*idx+2] = v - pix[4*idx+3] = v - } - } - src.WritePixels(bytesToManagedBytes(pix), image.Rect(0, 0, w, h)) - - orig := restorable.NewImage(w, h, restorable.ImageTypeRegular) - vs := quadVertices(w, h, 0, 0) - is := graphics.QuadIndices() - dr := image.Rect(0, 0, w, h) - orig.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - extended := orig.Extend(w*2, h*2) // After this, orig is already disposed. - - result := make([]byte, 4*(w*2)*(h*2)) - if err := extended.ReadPixels(ui.Get().GraphicsDriverForTesting(), result, image.Rect(0, 0, w*2, h*2)); err != nil { - t.Fatal(err) - } - for j := 0; j < h*2; j++ { - for i := 0; i < w*2; i++ { - got := result[4*(j*(w*2)+i)] - want := byte(0) - if i < w && j < h { - want = pixAt(i, j) - } - if got != want { - t.Errorf("extended.At(%d, %d): got: %v, want: %v", i, j, got, want) - } - } - } -} - -func TestClearPixels(t *testing.T) { - const w, h = 16, 16 - img := restorable.NewImage(w, h, restorable.ImageTypeRegular) - img.WritePixels(bytesToManagedBytes(make([]byte, 4*4*4)), image.Rect(0, 0, 4, 4)) - img.WritePixels(bytesToManagedBytes(make([]byte, 4*4*4)), image.Rect(4, 0, 8, 4)) - img.ClearPixels(image.Rect(0, 0, 4, 4)) - img.ClearPixels(image.Rect(4, 0, 8, 4)) - - // After clearing, the regions will be available again. - img.WritePixels(bytesToManagedBytes(make([]byte, 4*8*4)), image.Rect(0, 0, 8, 4)) -} - -func TestMutateSlices(t *testing.T) { - const w, h = 16, 16 - dst := restorable.NewImage(w, h, restorable.ImageTypeRegular) - src := restorable.NewImage(w, h, restorable.ImageTypeRegular) - pix := make([]byte, 4*w*h) - for i := 0; i < w*h; i++ { - pix[4*i] = byte(i) - pix[4*i+1] = byte(i) - pix[4*i+2] = byte(i) - pix[4*i+3] = 0xff - } - src.WritePixels(bytesToManagedBytes(pix), image.Rect(0, 0, w, h)) - - vs := quadVertices(w, h, 0, 0) - is := make([]uint32, len(graphics.QuadIndices())) - copy(is, graphics.QuadIndices()) - dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - for i := range vs { - vs[i] = 0 - } - for i := range is { - is[i] = 0 - } - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - - srcPix := make([]byte, 4*w*h) - if err := src.ReadPixels(ui.Get().GraphicsDriverForTesting(), srcPix, image.Rect(0, 0, w, h)); err != nil { - t.Fatal(err) - } - dstPix := make([]byte, 4*w*h) - if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), dstPix, image.Rect(0, 0, w, h)); err != nil { - t.Fatal(err) - } - - for j := 0; j < h; j++ { - for i := 0; i < w; i++ { - idx := 4 * (j*w + i) - want := color.RGBA{R: srcPix[idx], G: srcPix[idx+1], B: srcPix[idx+2], A: srcPix[idx+3]} - got := color.RGBA{R: dstPix[idx], G: dstPix[idx+1], B: dstPix[idx+2], A: dstPix[idx+3]} - if !sameColors(got, want, 1) { - t.Errorf("(%d, %d): got %v, want %v", i, j, got, want) - } - } - } -} - -func TestOverlappedPixels(t *testing.T) { - dst := restorable.NewImage(3, 3, restorable.ImageTypeRegular) - - pix0 := make([]byte, 4*2*2) - for j := 0; j < 2; j++ { - for i := 0; i < 2; i++ { - idx := 4 * (j*2 + i) - pix0[idx] = 0xff - pix0[idx+1] = 0 - pix0[idx+2] = 0 - pix0[idx+3] = 0xff - } - } - dst.WritePixels(bytesToManagedBytes(pix0), image.Rect(0, 0, 2, 2)) - - pix1 := make([]byte, 4*2*2) - for j := 0; j < 2; j++ { - for i := 0; i < 2; i++ { - idx := 4 * (j*2 + i) - pix1[idx] = 0 - pix1[idx+1] = 0xff - pix1[idx+2] = 0 - pix1[idx+3] = 0xff - } - } - dst.WritePixels(bytesToManagedBytes(pix1), image.Rect(1, 1, 3, 3)) - - wantColors := []color.RGBA{ - {0xff, 0, 0, 0xff}, - {0xff, 0, 0, 0xff}, - {0, 0, 0, 0}, - - {0xff, 0, 0, 0xff}, - {0, 0xff, 0, 0xff}, - {0, 0xff, 0, 0xff}, - - {0, 0, 0, 0}, - {0, 0xff, 0, 0xff}, - {0, 0xff, 0, 0xff}, - } - - result := make([]byte, 4*3*3) - if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), result, image.Rect(0, 0, 3, 3)); err != nil { - t.Fatal(err) - } - for j := 0; j < 3; j++ { - for i := 0; i < 3; i++ { - idx := 4 * (j*3 + i) - got := color.RGBA{R: result[idx], G: result[idx+1], B: result[idx+2], A: result[idx+3]} - want := wantColors[3*j+i] - if got != want { - t.Errorf("color at (%d, %d): got %v, want: %v", i, j, got, want) - } - } - } - - dst.WritePixels(nil, image.Rect(1, 0, 3, 2)) - - wantColors = []color.RGBA{ - {0xff, 0, 0, 0xff}, - {0, 0, 0, 0}, - {0, 0, 0, 0}, - - {0xff, 0, 0, 0xff}, - {0, 0, 0, 0}, - {0, 0, 0, 0}, - - {0, 0, 0, 0}, - {0, 0xff, 0, 0xff}, - {0, 0xff, 0, 0xff}, - } - if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), result, image.Rect(0, 0, 3, 3)); err != nil { - t.Fatal(err) - } - for j := 0; j < 3; j++ { - for i := 0; i < 3; i++ { - idx := 4 * (j*3 + i) - got := color.RGBA{R: result[idx], G: result[idx+1], B: result[idx+2], A: result[idx+3]} - want := wantColors[3*j+i] - if got != want { - t.Errorf("color at (%d, %d): got %v, want: %v", i, j, got, want) - } - } - } - - pix2 := make([]byte, 4*2*2) - for j := 0; j < 2; j++ { - for i := 0; i < 2; i++ { - idx := 4 * (j*2 + i) - pix2[idx] = 0 - pix2[idx+1] = 0 - pix2[idx+2] = 0xff - pix2[idx+3] = 0xff - } - } - dst.WritePixels(bytesToManagedBytes(pix2), image.Rect(1, 1, 3, 3)) - - wantColors = []color.RGBA{ - {0xff, 0, 0, 0xff}, - {0, 0, 0, 0}, - {0, 0, 0, 0}, - - {0xff, 0, 0, 0xff}, - {0, 0, 0xff, 0xff}, - {0, 0, 0xff, 0xff}, - - {0, 0, 0, 0}, - {0, 0, 0xff, 0xff}, - {0, 0, 0xff, 0xff}, - } - if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), result, image.Rect(0, 0, 3, 3)); err != nil { - t.Fatal(err) - } - for j := 0; j < 3; j++ { - for i := 0; i < 3; i++ { - idx := 4 * (j*3 + i) - got := color.RGBA{R: result[idx], G: result[idx+1], B: result[idx+2], A: result[idx+3]} - want := wantColors[3*j+i] - if got != want { - t.Errorf("color at (%d, %d): got %v, want: %v", i, j, got, want) - } - } - } - - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - - if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), result, image.Rect(0, 0, 3, 3)); err != nil { - t.Fatal(err) - } - for j := 0; j < 3; j++ { - for i := 0; i < 3; i++ { - idx := 4 * (j*3 + i) - got := color.RGBA{R: result[idx], G: result[idx+1], B: result[idx+2], A: result[idx+3]} - want := wantColors[3*j+i] - if got != want { - t.Errorf("color at (%d, %d): got %v, want: %v", i, j, got, want) - } - } - } -} - -// Issue #2324 -func TestDrawTrianglesAndReadPixels(t *testing.T) { - const w, h = 1, 1 - src := restorable.NewImage(w, h, restorable.ImageTypeRegular) - dst := restorable.NewImage(w, h, restorable.ImageTypeRegular) - - src.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.ShaderImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - - 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) - } -} - -func TestWritePixelsAndDrawTriangles(t *testing.T) { - src := restorable.NewImage(1, 1, restorable.ImageTypeRegular) - dst := restorable.NewImage(2, 1, restorable.ImageTypeRegular) - - src.WritePixels(bytesToManagedBytes([]byte{0x80, 0x80, 0x80, 0x80}), image.Rect(0, 0, 1, 1)) - - // Call WritePixels first. - dst.WritePixels(bytesToManagedBytes([]byte{0x40, 0x40, 0x40, 0x40}), image.Rect(0, 0, 1, 1)) - - // Call DrawTriangles at a different region second. - vs := quadVertices(1, 1, 1, 0) - is := graphics.QuadIndices() - dr := image.Rect(1, 0, 2, 1) - dst.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) - - // Get the pixels. - pix := make([]byte, 4*2*1) - if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, 2, 1)); 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: 0x40, G: 0x40, B: 0x40, A: 0x40}); !sameColors(got, want, 1) { - t.Errorf("got: %v, want: %v", got, want) - } - if got, want := (color.RGBA{R: pix[4], G: pix[5], B: pix[6], A: pix[7]}), (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 deleted file mode 100644 index 4aefdf1dd..000000000 --- a/internal/restorable/shader_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2018 The Ebiten Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package restorable_test - -import ( - "image" - "image/color" - "testing" - - "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" -) - -func clearImage(img *restorable.Image, w, h int) { - emptyImage := restorable.NewImage(3, 3, restorable.ImageTypeRegular) - defer emptyImage.Dispose() - - dx0 := float32(0) - dy0 := float32(0) - dx1 := float32(w) - dy1 := float32(h) - sx0 := float32(1) - sy0 := float32(1) - sx1 := float32(2) - sy1 := float32(2) - vs := []float32{ - dx0, dy0, sx0, sy0, 0, 0, 0, 0, - dx1, dy0, sx1, sy0, 0, 0, 0, 0, - dx0, dy1, sx0, sy1, 0, 0, 0, 0, - dx1, dy1, sx1, sy1, 0, 0, 0, 0, - } - is := graphics.QuadIndices() - dr := image.Rect(0, 0, w, h) - img.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{emptyImage}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderImageCount]image.Rectangle{}, restorable.NearestFilterShader, nil, graphicsdriver.FillAll) -} - -func TestShader(t *testing.T) { - img := restorable.NewImage(1, 1, restorable.ImageTypeRegular) - defer img.Dispose() - - s := restorable.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff)) - dr := image.Rect(0, 0, 1, 1) - img.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillAll) - - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - - want := color.RGBA{R: 0xff, A: 0xff} - got := pixelsToColor(img.BasePixelsForTesting(), 0, 0, 1, 1) - if !sameColors(got, want, 1) { - t.Errorf("got %v, want %v", got, want) - } -} - -func TestShaderChain(t *testing.T) { - const num = 10 - imgs := []*restorable.Image{} - for i := 0; i < num; i++ { - img := restorable.NewImage(1, 1, restorable.ImageTypeRegular) - defer img.Dispose() - imgs = append(imgs, img) - } - - imgs[0].WritePixels(bytesToManagedBytes([]byte{0xff, 0, 0, 0xff}), image.Rect(0, 0, 1, 1)) - - 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.ShaderImageCount]*restorable.Image{imgs[i]}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillAll) - } - - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - - for i, img := range imgs { - want := color.RGBA{R: 0xff, A: 0xff} - got := pixelsToColor(img.BasePixelsForTesting(), 0, 0, 1, 1) - if !sameColors(got, want, 1) { - t.Errorf("%d: got %v, want %v", i, got, want) - } - } -} - -func TestShaderMultipleSources(t *testing.T) { - var srcs [graphics.ShaderImageCount]*restorable.Image - for i := range srcs { - srcs[i] = restorable.NewImage(1, 1, restorable.ImageTypeRegular) - } - srcs[0].WritePixels(bytesToManagedBytes([]byte{0x40, 0, 0, 0xff}), image.Rect(0, 0, 1, 1)) - srcs[1].WritePixels(bytesToManagedBytes([]byte{0, 0x80, 0, 0xff}), image.Rect(0, 0, 1, 1)) - srcs[2].WritePixels(bytesToManagedBytes([]byte{0, 0, 0xc0, 0xff}), image.Rect(0, 0, 1, 1)) - - dst := restorable.NewImage(1, 1, restorable.ImageTypeRegular) - - 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.ShaderImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillAll) - - // Clear one of the sources after DrawTriangles. dst should not be affected. - clearImage(srcs[0], 1, 1) - - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - - want := color.RGBA{R: 0x40, G: 0x80, B: 0xc0, A: 0xff} - got := pixelsToColor(dst.BasePixelsForTesting(), 0, 0, 1, 1) - if !sameColors(got, want, 1) { - t.Errorf("got %v, want %v", got, want) - } -} - -func TestShaderMultipleSourcesOnOneTexture(t *testing.T) { - src := restorable.NewImage(3, 1, restorable.ImageTypeRegular) - src.WritePixels(bytesToManagedBytes([]byte{ - 0x40, 0, 0, 0xff, - 0, 0x80, 0, 0xff, - 0, 0, 0xc0, 0xff, - }), image.Rect(0, 0, 3, 1)) - srcs := [graphics.ShaderImageCount]*restorable.Image{src, src, src} - - dst := restorable.NewImage(1, 1, restorable.ImageTypeRegular) - - s := restorable.NewShader(etesting.ShaderProgramImages(3)) - dr := image.Rect(0, 0, 1, 1) - srcRegions := [graphics.ShaderImageCount]image.Rectangle{ - image.Rect(0, 0, 1, 1), - 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.FillAll) - - // Clear one of the sources after DrawTriangles. dst should not be affected. - clearImage(srcs[0], 3, 1) - - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - - want := color.RGBA{R: 0x40, G: 0x80, B: 0xc0, A: 0xff} - got := pixelsToColor(dst.BasePixelsForTesting(), 0, 0, 1, 1) - if !sameColors(got, want, 1) { - t.Errorf("got %v, want %v", got, want) - } -} - -func TestShaderDispose(t *testing.T) { - img := restorable.NewImage(1, 1, restorable.ImageTypeRegular) - defer img.Dispose() - - s := restorable.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff)) - dr := image.Rect(0, 0, 1, 1) - img.DrawTriangles([graphics.ShaderImageCount]*restorable.Image{}, quadVertices(1, 1, 0, 0), graphics.QuadIndices(), graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillAll) - - // Dispose the shader. This should invalidate all the images using this shader i.e., all the images become - // stale. - s.Dispose() - - if err := restorable.ResolveStaleImages(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - if err := restorable.RestoreIfNeeded(ui.Get().GraphicsDriverForTesting()); err != nil { - t.Fatal(err) - } - - want := color.RGBA{R: 0xff, A: 0xff} - got := pixelsToColor(img.BasePixelsForTesting(), 0, 0, 1, 1) - if !sameColors(got, want, 1) { - t.Errorf("got %v, want %v", got, want) - } -}