diff --git a/internal/restorable/image.go b/internal/restorable/image.go index 369e25d56..00508e288 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -76,6 +76,8 @@ type drawTrianglesHistoryItem struct { mode driver.CompositeMode filter driver.Filter address driver.Address + shader *Shader + uniforms map[int]interface{} } // Image represents an image that can be restored when GL context is lost. @@ -339,6 +341,20 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { } } +// convertUniformVariables converts the uniform variables for the lower layer (graphicscommand). +func convertUniformVariables(uniforms map[int]interface{}) map[int]interface{} { + us := map[int]interface{}{} + for k, v := range uniforms { + switch v := v.(type) { + case *Image: + us[k] = v.image + default: + us[k] = v + } + } + return us +} + // DrawTriangles draws triangles with the given image. // // The vertex floats are: @@ -355,7 +371,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { // 9: Color G // 10: Color B // 11: Color Y -func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) { +func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, shader *Shader, uniforms map[int]interface{}) { if i.priority { panic("restorable: DrawTriangles cannot be called on a priority image") } @@ -364,16 +380,25 @@ func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, } theImages.makeStaleIfDependingOn(i) - if img.stale || img.volatile || i.screen || !needsRestoring() || i.volatile { + if (img != nil && (img.stale || img.volatile)) || i.screen || !needsRestoring() || i.volatile { i.makeStale() } else { - i.appendDrawTrianglesHistory(img, vertices, indices, colorm, mode, filter, address) + // TODO: Need to copy uniform variables? + i.appendDrawTrianglesHistory(img, vertices, indices, colorm, mode, filter, address, shader, uniforms) } - i.image.DrawTriangles(img.image, vertices, indices, colorm, mode, filter, address, nil, nil) + var s *graphicscommand.Shader + if shader != nil { + s = shader.shader + } + var gimg *graphicscommand.Image + if img != nil { + gimg = img.image + } + i.image.DrawTriangles(gimg, vertices, indices, colorm, mode, filter, address, s, convertUniformVariables(uniforms)) } // appendDrawTrianglesHistory appends a draw-image history item to the image. -func (i *Image) appendDrawTrianglesHistory(image *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address) { +func (i *Image) appendDrawTrianglesHistory(image *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, shader *Shader, uniforms map[int]interface{}) { if i.stale || i.volatile || i.screen { return } @@ -390,6 +415,7 @@ func (i *Image) appendDrawTrianglesHistory(image *Image, vertices []float32, ind copy(vs, vertices) is := make([]uint16, len(indices)) copy(is, indices) + item := &drawTrianglesHistoryItem{ image: image, vertices: vs, @@ -398,6 +424,8 @@ func (i *Image) appendDrawTrianglesHistory(image *Image, vertices []float32, ind mode: mode, filter: filter, address: address, + shader: shader, + uniforms: uniforms, } i.drawTrianglesHistory = append(i.drawTrianglesHistory, item) } @@ -479,6 +507,11 @@ func (i *Image) dependsOn(target *Image) bool { if c.image == target { return true } + for _, v := range c.uniforms { + if img, ok := v.(*Image); ok && img == target { + return true + } + } } return false } @@ -487,7 +520,14 @@ func (i *Image) dependsOn(target *Image) bool { func (i *Image) dependingImages() map[*Image]struct{} { r := map[*Image]struct{}{} for _, c := range i.drawTrianglesHistory { - r[c.image] = struct{}{} + if c.image != nil { + r[c.image] = struct{}{} + } + for _, v := range c.uniforms { + if img, ok := v.(*Image); ok { + r[img] = struct{}{} + } + } } return r } @@ -533,10 +573,19 @@ func (i *Image) restore() error { i.basePixels.Apply(gimg) for _, c := range i.drawTrianglesHistory { - if c.image.hasDependency() { + if c.image != nil && c.image.hasDependency() { panic("restorable: all dependencies must be already resolved but not") } - gimg.DrawTriangles(c.image.image, c.vertices, c.indices, c.colorm, c.mode, c.filter, c.address, nil, nil) + // TODO: Check the uniform variable's images. + var img *graphicscommand.Image + if c.image != nil { + img = c.image.image + } + var s *graphicscommand.Shader + if c.shader != nil { + s = c.shader.shader + } + gimg.DrawTriangles(img, c.vertices, c.indices, c.colorm, c.mode, c.filter, c.address, s, convertUniformVariables(c.uniforms)) } if len(i.drawTrianglesHistory) > 0 { diff --git a/internal/restorable/images.go b/internal/restorable/images.go index 1b07a4a8e..c56dfa1fa 100644 --- a/internal/restorable/images.go +++ b/internal/restorable/images.go @@ -40,12 +40,14 @@ func EnableRestoringForTesting() { // images is a set of Image objects. type images struct { images map[*Image]struct{} + shaders map[*Shader]struct{} lastTarget *Image } // theImages represents the images for the current process. var theImages = &images{ - images: map[*Image]struct{}{}, + images: map[*Image]struct{}{}, + shaders: map[*Shader]struct{}{}, } // ResolveStaleImages flushes the queued draw commands and resolves @@ -118,12 +120,23 @@ func (i *images) add(img *Image) { i.images[img] = struct{}{} } +func (i *images) addShader(shader *Shader) { + i.shaders[shader] = struct{}{} +} + // remove removes img from the images. func (i *images) remove(img *Image) { i.makeStaleIfDependingOnImpl(img) delete(i.images, img) } +func (i *images) removeShader(shader *Shader) { + // TODO: Do we have to make images stale? + // However, dependencies are determiend by uniform variables... + // ?? + delete(i.shaders, shader) +} + // resolveStaleImages resolves stale images. func (i *images) resolveStaleImages() error { i.lastTarget = nil @@ -165,6 +178,15 @@ func (i *images) restore() error { 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 { diff --git a/internal/restorable/images_test.go b/internal/restorable/images_test.go index 8dabdee93..8c215526c 100644 --- a/internal/restorable/images_test.go +++ b/internal/restorable/images_test.go @@ -131,7 +131,7 @@ func TestRestoreChain(t *testing.T) { for i := 0; i < num-1; i++ { vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() - imgs[i+1].DrawTriangles(imgs[i], vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + imgs[i+1].DrawTriangles(imgs[i], vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) } if err := ResolveStaleImages(); err != nil { t.Fatal(err) @@ -173,10 +173,10 @@ func TestRestoreChain2(t *testing.T) { imgs[8].ReplacePixels([]byte{clr8.R, clr8.G, clr8.B, clr8.A}, 0, 0, w, h) is := graphics.QuadIndices() - imgs[8].DrawTriangles(imgs[7], quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) - imgs[9].DrawTriangles(imgs[8], quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + imgs[8].DrawTriangles(imgs[7], quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) + imgs[9].DrawTriangles(imgs[8], quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) for i := 0; i < 7; i++ { - imgs[i+1].DrawTriangles(imgs[i], quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + imgs[i+1].DrawTriangles(imgs[i], quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) } if err := ResolveStaleImages(); err != nil { @@ -216,10 +216,10 @@ func TestRestoreOverrideSource(t *testing.T) { clr1 := color.RGBA{0x00, 0x00, 0x01, 0xff} img1.ReplacePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, 0, 0, w, h) is := graphics.QuadIndices() - img2.DrawTriangles(img1, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) - img3.DrawTriangles(img2, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img2.DrawTriangles(img1, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) + img3.DrawTriangles(img2, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) img0.ReplacePixels([]byte{clr1.R, clr1.G, clr1.B, clr1.A}, 0, 0, w, h) - img1.DrawTriangles(img0, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img1.DrawTriangles(img0, quadVertices(w, h, 0, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) if err := ResolveStaleImages(); err != nil { t.Fatal(err) } @@ -298,23 +298,23 @@ func TestRestoreComplexGraph(t *testing.T) { }() vs := quadVertices(w, h, 0, 0) is := graphics.QuadIndices() - img3.DrawTriangles(img0, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img3.DrawTriangles(img0, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) vs = quadVertices(w, h, 1, 0) - img3.DrawTriangles(img1, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img3.DrawTriangles(img1, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) vs = quadVertices(w, h, 1, 0) - img4.DrawTriangles(img1, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img4.DrawTriangles(img1, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) vs = quadVertices(w, h, 2, 0) - img4.DrawTriangles(img2, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img4.DrawTriangles(img2, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) vs = quadVertices(w, h, 0, 0) - img5.DrawTriangles(img3, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img5.DrawTriangles(img3, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) vs = quadVertices(w, h, 0, 0) - img6.DrawTriangles(img3, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img6.DrawTriangles(img3, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) vs = quadVertices(w, h, 1, 0) - img6.DrawTriangles(img4, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img6.DrawTriangles(img4, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) vs = quadVertices(w, h, 0, 0) - img7.DrawTriangles(img2, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img7.DrawTriangles(img2, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) vs = quadVertices(w, h, 2, 0) - img7.DrawTriangles(img3, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img7.DrawTriangles(img3, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) if err := ResolveStaleImages(); err != nil { t.Fatal(err) } @@ -406,8 +406,8 @@ func TestRestoreRecursive(t *testing.T) { img0.Dispose() }() is := graphics.QuadIndices() - img1.DrawTriangles(img0, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) - img0.DrawTriangles(img1, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + img1.DrawTriangles(img0, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) + img0.DrawTriangles(img1, quadVertices(w, h, 1, 0), is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) if err := ResolveStaleImages(); err != nil { t.Fatal(err) } @@ -501,7 +501,7 @@ func TestDrawTrianglesAndReplacePixels(t *testing.T) { vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() - img1.DrawTriangles(img0, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + img1.DrawTriangles(img0, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0, 2, 1) if err := ResolveStaleImages(); err != nil { @@ -538,8 +538,8 @@ func TestDispose(t *testing.T) { defer img2.Dispose() is := graphics.QuadIndices() - img1.DrawTriangles(img2, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) - img0.DrawTriangles(img1, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + img1.DrawTriangles(img2, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) + img0.DrawTriangles(img1, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) img1.Dispose() if err := ResolveStaleImages(); err != nil { @@ -647,7 +647,7 @@ func TestReplacePixelsOnly(t *testing.T) { vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() - img1.DrawTriangles(img0, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + img1.DrawTriangles(img0, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) img0.ReplacePixels([]byte{5, 6, 7, 8}, 0, 0, 1, 1) // BasePixelsForTesting is available without GPU accessing. @@ -700,7 +700,7 @@ func TestReadPixelsFromVolatileImage(t *testing.T) { src.ReplacePixels(pix, 0, 0, w, h) vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) // Read the pixels. If the implementation is correct, dst tries to read its pixels from GPU due to being // stale. @@ -721,7 +721,7 @@ func TestAllowReplacePixelsAfterDrawTriangles(t *testing.T) { vs := quadVertices(w, h, 0, 0) is := graphics.QuadIndices() - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) dst.ReplacePixels(make([]byte, 4*w*h), 0, 0, w, h) // ReplacePixels for a whole image doesn't panic. } @@ -739,7 +739,7 @@ func TestDisallowReplacePixelsForPartAfterDrawTriangles(t *testing.T) { vs := quadVertices(w, h, 0, 0) is := graphics.QuadIndices() - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1) } @@ -835,7 +835,7 @@ func TestMutateSlices(t *testing.T) { vs := quadVertices(w, h, 0, 0) is := make([]uint16, len(graphics.QuadIndices())) copy(is, graphics.QuadIndices()) - dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero, nil, nil) for i := range vs { vs[i] = 0 } diff --git a/internal/restorable/shader.go b/internal/restorable/shader.go new file mode 100644 index 000000000..3d3341066 --- /dev/null +++ b/internal/restorable/shader.go @@ -0,0 +1,45 @@ +// Copyright 2020 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 + +import ( + "github.com/hajimehoshi/ebiten/internal/graphicscommand" + "github.com/hajimehoshi/ebiten/internal/shaderir" +) + +type Shader struct { + shader *graphicscommand.Shader + ir *shaderir.Program +} + +func NewShader(program *shaderir.Program) *Shader { + s := &Shader{ + shader: graphicscommand.NewShader(program), + ir: program, + } + theImages.addShader(s) + return s +} + +func (s *Shader) Dispose() { + theImages.removeShader(s) + s.shader.Dispose() + s.shader = nil + s.ir = nil +} + +func (s *Shader) restore() { + s.shader = graphicscommand.NewShader(s.ir) +} diff --git a/internal/restorable/shader_test.go b/internal/restorable/shader_test.go new file mode 100644 index 000000000..509053b15 --- /dev/null +++ b/internal/restorable/shader_test.go @@ -0,0 +1,57 @@ +// 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/internal/driver" + "github.com/hajimehoshi/ebiten/internal/graphics" + "github.com/hajimehoshi/ebiten/internal/graphicscommand" + . "github.com/hajimehoshi/ebiten/internal/restorable" + etesting "github.com/hajimehoshi/ebiten/internal/testing" +) + +func TestShader(t *testing.T) { + if !graphicscommand.IsShaderAvailable() { + t.Skip("shader is not available on this environment") + } + + img := newImageFromImage(image.NewRGBA(image.Rect(0, 0, 1, 1))) + defer img.Dispose() + + ir := etesting.ShaderProgramFill(0xff, 0, 0, 0xff) + s := NewShader(&ir) + is := graphics.QuadIndices() + us := map[int]interface{}{ + 0: []float32{1, 1}, + } + img.DrawTriangles(nil, quadVertices(1, 1, 0, 0), is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, s, us) + + if err := ResolveStaleImages(); err != nil { + t.Fatal(err) + } + if err := RestoreIfNeeded(); err != nil { + t.Fatal(err) + } + + want := color.RGBA{0xff, 0, 0, 0xff} + got := pixelsToColor(img.BasePixelsForTesting(), 0, 0) + if !sameColors(got, want, 1) { + t.Errorf("got %v, want %v", got, want) + } +} diff --git a/internal/shareable/image.go b/internal/shareable/image.go index 6670b0fba..54a1341b8 100644 --- a/internal/shareable/image.go +++ b/internal/shareable/image.go @@ -214,7 +214,7 @@ func (i *Image) ensureNotShared() { dx1, dy1, sx1, sy1, sx0, sy0, sx1, sy1, 1, 1, 1, 1, } is := graphics.QuadIndices() - newImg.DrawTriangles(i.backend.restorable, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + newImg.DrawTriangles(i.backend.restorable, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) i.dispose(false) i.backend = &backend{ @@ -316,7 +316,7 @@ func (i *Image) DrawTriangles(img *Image, vertices []float32, indices []uint16, vertices[i*graphics.VertexFloatNum+7] += oyf } - i.backend.restorable.DrawTriangles(img.backend.restorable, vertices, indices, colorm, mode, filter, address) + i.backend.restorable.DrawTriangles(img.backend.restorable, vertices, indices, colorm, mode, filter, address, nil, nil) i.nonUpdatedCount = 0 delete(imagesToMakeShared, i)