diff --git a/internal/mipmap/mipmap.go b/internal/mipmap/mipmap.go index 42f51120a..b51820bee 100644 --- a/internal/mipmap/mipmap.go +++ b/internal/mipmap/mipmap.go @@ -150,7 +150,7 @@ func (m *Mipmap) DrawImage(src *Mipmap, bounds image.Rectangle, geom GeoM, color if level == 0 { vs := quadVertices(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, a, b, c, d, tx, ty, cr, cg, cb, ca, screen) is := graphics.QuadIndices() - m.orig.DrawTriangles(src.orig, vs, is, colorm, mode, filter, driver.AddressClampToZero) + m.orig.DrawTriangles(src.orig, vs, is, colorm, mode, filter, driver.AddressClampToZero, nil, nil) } else if buf := src.level(bounds, level); buf != nil { w, h := sizeForLevel(bounds.Dx(), bounds.Dy(), level) s := pow2(level) @@ -160,7 +160,7 @@ func (m *Mipmap) DrawImage(src *Mipmap, bounds image.Rectangle, geom GeoM, color d *= s vs := quadVertices(0, 0, w, h, a, b, c, d, tx, ty, cr, cg, cb, ca, false) is := graphics.QuadIndices() - m.orig.DrawTriangles(buf, vs, is, colorm, mode, filter, driver.AddressClampToZero) + m.orig.DrawTriangles(buf, vs, is, colorm, mode, filter, driver.AddressClampToZero, nil, nil) } m.disposeMipmaps() } @@ -183,7 +183,7 @@ func (m *Mipmap) DrawTriangles(src *Mipmap, vertices []float32, indices []uint16 vertices[i*n+11] *= ca } } - m.orig.DrawTriangles(src.orig, vertices, indices, colorm, mode, filter, address) + m.orig.DrawTriangles(src.orig, vertices, indices, colorm, mode, filter, address, nil, nil) m.disposeMipmaps() } @@ -246,7 +246,7 @@ func (m *Mipmap) level(r image.Rectangle, level int) *shareable.Image { return nil } s := shareable.NewImage(w2, h2, m.volatile) - s.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressClampToZero) + s.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressClampToZero, nil, nil) imgs[level] = s return imgs[level] diff --git a/internal/shareable/image.go b/internal/shareable/image.go index 54a1341b8..995cf6b87 100644 --- a/internal/shareable/image.go +++ b/internal/shareable/image.go @@ -282,7 +282,7 @@ func (i *Image) region() (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 []interface{}) { backendsM.Lock() // Do not use defer for performance. @@ -316,7 +316,36 @@ 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, nil, nil) + var s *restorable.Shader + if shader != nil { + s = shader.shader + } + + firstImage := true + us := make([]interface{}, len(uniforms)) + for i := 0; i < len(uniforms); i++ { + switch v := us[i].(type) { + case *Image: + us[i] = v.backend.restorable + if !firstImage { + i++ + pos := us[i].([]float32) + pos[0] += oxf + pos[1] += oyf + i++ + region := us[i].([]float32) + region[0] += oxf + region[1] += oyf + region[2] += oxf + region[3] += oyf + } + firstImage = false + default: + us[i] = v + } + } + + i.backend.restorable.DrawTriangles(img.backend.restorable, vertices, indices, colorm, mode, filter, address, s, us) i.nonUpdatedCount = 0 delete(imagesToMakeShared, i) diff --git a/internal/shareable/image_test.go b/internal/shareable/image_test.go index 0cef272b6..a7b394214 100644 --- a/internal/shareable/image_test.go +++ b/internal/shareable/image_test.go @@ -89,7 +89,7 @@ func TestEnsureNotShared(t *testing.T) { // img4.ensureNotShared() should be called. vs := quadVertices(size/2, size/2, size/4, size/4, 1) is := graphics.QuadIndices() - img4.DrawTriangles(img3, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + img4.DrawTriangles(img3, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) want := false if got := img4.IsSharedForTesting(); got != want { t.Errorf("got: %v, want: %v", got, want) @@ -119,7 +119,7 @@ func TestEnsureNotShared(t *testing.T) { // Check further drawing doesn't cause panic. // This bug was fixed by 03dcd948. - img4.DrawTriangles(img3, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + img4.DrawTriangles(img3, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) } func TestReshared(t *testing.T) { @@ -159,7 +159,7 @@ func TestReshared(t *testing.T) { // Use img1 as a render target. vs := quadVertices(size, size, 0, 0, 1) is := graphics.QuadIndices() - img1.DrawTriangles(img2, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + img1.DrawTriangles(img2, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) if got, want := img1.IsSharedForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -169,7 +169,7 @@ func TestReshared(t *testing.T) { if err := MakeImagesSharedForTesting(); err != nil { t.Fatal(err) } - img0.DrawTriangles(img1, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + img0.DrawTriangles(img1, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) if got, want := img1.IsSharedForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -196,7 +196,7 @@ func TestReshared(t *testing.T) { } } - img0.DrawTriangles(img1, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + img0.DrawTriangles(img1, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) if got, want := img1.IsSharedForTesting(), true; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -224,7 +224,7 @@ func TestReshared(t *testing.T) { if err := MakeImagesSharedForTesting(); err != nil { t.Fatal(err) } - img0.DrawTriangles(img3, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) + img0.DrawTriangles(img3, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero, nil, nil) if got, want := img3.IsSharedForTesting(), false; got != want { t.Errorf("got: %v, want: %v", got, want) } @@ -319,7 +319,7 @@ func TestReplacePixelsAfterDrawTriangles(t *testing.T) { vs := quadVertices(w, h, 0, 0, 1) 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) dst.ReplacePixels(pix) pix, err := dst.Pixels(0, 0, w, h) @@ -361,7 +361,7 @@ func TestSmallImages(t *testing.T) { vs := quadVertices(w, h, 0, 0, 1) 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) pix, err := dst.Pixels(0, 0, w, h) if err != nil { @@ -403,7 +403,7 @@ func TestLongImages(t *testing.T) { const scale = 120 vs := quadVertices(w, h, 0, 0, scale) 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) pix, err := dst.Pixels(0, 0, dstW, dstH) if err != nil { diff --git a/internal/shareable/shader.go b/internal/shareable/shader.go new file mode 100644 index 000000000..4c4025994 --- /dev/null +++ b/internal/shareable/shader.go @@ -0,0 +1,52 @@ +// 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 shareable + +import ( + "runtime" + + "github.com/hajimehoshi/ebiten/internal/restorable" + "github.com/hajimehoshi/ebiten/internal/shaderir" +) + +type Shader struct { + shader *restorable.Shader +} + +func NewShader(program *shaderir.Program) *Shader { + s := &Shader{ + shader: restorable.NewShader(program), + } + runtime.SetFinalizer(s, (*Shader).MarkDisposed) + return s +} + +// MarkDisposed marks the shader as disposed. The actual operation is deferred. +// MarkDisposed can be called from finalizers. +// +// A function from finalizer must not be blocked, but disposing operation can be blocked. +// Defer this operation until it becomes safe. (#913) +func (s *Shader) MarkDisposed() { + deferredM.Lock() + deferred = append(deferred, func() { + s.dispose() + }) + deferredM.Unlock() +} + +func (s *Shader) dispose() { + s.shader.Dispose() + s.shader = nil +}