From 690c3cf9819456cbac206bd60ba8020c938f683f Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Fri, 10 Aug 2018 03:33:28 +0900 Subject: [PATCH] graphics: Embed 'scale' part of the color matrix If the color matrix includes only 'scale' part, they are embedded into vertices in order to reduce draw calls. Fixes #662 --- image.go | 19 ++++++++-- internal/affine/colorm.go | 24 ++++++++++++ internal/graphics/program.go | 5 +++ internal/graphics/shader.go | 7 +++- internal/graphicsutil/vertices.go | 56 ++++++++++++++++++---------- internal/restorable/image.go | 3 +- internal/restorable/images_test.go | 32 ++++++++-------- internal/shareable/shareable.go | 8 ++-- internal/shareable/shareable_test.go | 4 +- 9 files changed, 111 insertions(+), 47 deletions(-) diff --git a/image.go b/image.go index 1557e33d2..cd3f412f9 100644 --- a/image.go +++ b/image.go @@ -308,7 +308,7 @@ func (i *Image) drawImage(img *Image, options *DrawImageOptions) { } else { s = shareable.NewImage(w2, h2) } - vs := src.QuadVertices(0, 0, w, h, 0.5, 0, 0, 0.5, 0, 0) + vs := src.QuadVertices(0, 0, w, h, 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() s.DrawImage(src, vs, is, options.ColorM.impl, opengl.CompositeModeCopy, graphics.FilterLinear) img.shareableImages = append(img.shareableImages, s) @@ -318,9 +318,22 @@ func (i *Image) drawImage(img *Image, options *DrawImageOptions) { if level < len(img.shareableImages) { src := img.shareableImages[level] - vs := src.QuadVertices(sx0, sy0, sx1, sy1, a, b, c, d, tx, ty) + colorm := options.ColorM.impl + cr, cg, cb, ca := float32(1), float32(1), float32(1), float32(1) + if colorm.ScaleOnly() { + body, _ := colorm.UnsafeElements() + cr = body[0] + cg = body[5] + cb = body[10] + ca = body[15] + } + vs := src.QuadVertices(sx0, sy0, sx1, sy1, a, b, c, d, tx, ty, cr, cg, cb, ca) is := graphicsutil.QuadIndices() - i.shareableImages[0].DrawImage(src, vs, is, options.ColorM.impl, mode, filter) + + if colorm.ScaleOnly() { + colorm = nil + } + i.shareableImages[0].DrawImage(src, vs, is, colorm, mode, filter) } i.disposeMipmaps() } diff --git a/internal/affine/colorm.go b/internal/affine/colorm.go index 81d506d74..479affcea 100644 --- a/internal/affine/colorm.go +++ b/internal/affine/colorm.go @@ -63,6 +63,30 @@ func (c *ColorM) isInited() bool { return c != nil && (c.body != nil || c.translate != nil) } +func (c *ColorM) ScaleOnly() bool { + if c == nil { + return true + } + if c.body != nil { + for i, e := range c.body { + if i == 0 || i == 5 || i == 10 || i == 15 { + continue + } + if e != 0 { + return false + } + } + } + if c.translate != nil { + for _, e := range c.translate { + if e != 0 { + return false + } + } + } + return true +} + func (c *ColorM) Apply(clr color.Color) color.Color { if !c.isInited() { return clr diff --git a/internal/graphics/program.go b/internal/graphics/program.go index 330d2c5e6..61f999e68 100644 --- a/internal/graphics/program.go +++ b/internal/graphics/program.go @@ -94,6 +94,11 @@ var ( dataType: opengl.Float, num: 4, }, + { + name: "color_scale", + dataType: opengl.Float, + num: 4, + }, }, } ) diff --git a/internal/graphics/shader.go b/internal/graphics/shader.go index 2fd0efa4a..d6543d848 100644 --- a/internal/graphics/shader.go +++ b/internal/graphics/shader.go @@ -50,14 +50,17 @@ const ( uniform mat4 projection_matrix; attribute vec2 vertex; attribute vec4 tex_coord; +attribute vec4 color_scale; varying vec2 varying_tex_coord; varying vec2 varying_tex_coord_min; varying vec2 varying_tex_coord_max; +varying vec4 varying_color_scale; void main(void) { varying_tex_coord = vec2(tex_coord[0], tex_coord[1]); varying_tex_coord_min = vec2(min(tex_coord[0], tex_coord[2]), min(tex_coord[1], tex_coord[3])); varying_tex_coord_max = vec2(max(tex_coord[0], tex_coord[2]), max(tex_coord[1], tex_coord[3])); + varying_color_scale = color_scale; gl_Position = projection_matrix * vec4(vertex, 0, 1); } ` @@ -85,6 +88,7 @@ uniform highp float scale; varying highp vec2 varying_tex_coord; varying highp vec2 varying_tex_coord_min; varying highp vec2 varying_tex_coord_max; +varying highp vec4 varying_color_scale; void main(void) { highp vec2 pos = varying_tex_coord; @@ -147,8 +151,9 @@ void main(void) { if (0.0 < color.a) { color.rgb /= color.a; } - // Apply the color matrix + // Apply the color matrix or scale. color = (color_matrix_body * color) + color_matrix_translation; + color *= varying_color_scale; color = clamp(color, 0.0, 1.0); // Premultiply alpha color.rgb *= color.a; diff --git a/internal/graphicsutil/vertices.go b/internal/graphicsutil/vertices.go index 9eb8e5744..f57895212 100644 --- a/internal/graphicsutil/vertices.go +++ b/internal/graphicsutil/vertices.go @@ -47,7 +47,7 @@ func isPowerOf2(x int) bool { return (x & (x - 1)) == 0 } -func QuadVertices(width, height int, sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32) []float32 { +func QuadVertices(width, height int, sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32, cr, cg, cb, ca float32) []float32 { if !isPowerOf2(width) { panic("not reached") } @@ -65,13 +65,13 @@ func QuadVertices(width, height int, sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty wf := float32(width) hf := float32(height) u0, v0, u1, v1 := float32(sx0)/wf, float32(sy0)/hf, float32(sx1)/wf, float32(sy1)/hf - return quadVerticesImpl(float32(sx1-sx0), float32(sy1-sy0), u0, v0, u1, v1, a, b, c, d, tx, ty) + return quadVerticesImpl(float32(sx1-sx0), float32(sy1-sy0), u0, v0, u1, v1, a, b, c, d, tx, ty, cr, cg, cb, ca) } -func quadVerticesImpl(x, y, u0, v0, u1, v1, a, b, c, d, tx, ty float32) []float32 { +func quadVerticesImpl(x, y, u0, v0, u1, v1, a, b, c, d, tx, ty, cr, cg, cb, ca float32) []float32 { // Specifying a range explicitly here is redundant but this helps optimization // to eliminate boundry checks. - vs := theVerticesBackend.sliceForOneQuad()[0:24] + vs := theVerticesBackend.sliceForOneQuad()[0:40] ax, by, cx, dy := a*x, b*y, c*x, d*y @@ -86,28 +86,44 @@ func quadVerticesImpl(x, y, u0, v0, u1, v1, a, b, c, d, tx, ty float32) []float3 vs[3] = v0 vs[4] = u1 vs[5] = v1 + vs[6] = cr + vs[7] = cg + vs[8] = cb + vs[9] = ca // and the same for the other three coordinates - vs[6] = ax + tx - vs[7] = cx + ty - vs[8] = u1 - vs[9] = v0 - vs[10] = u0 - vs[11] = v1 - - vs[12] = by + tx - vs[13] = dy + ty + vs[10] = ax + tx + vs[11] = cx + ty + vs[12] = u1 + vs[13] = v0 vs[14] = u0 vs[15] = v1 - vs[16] = u1 - vs[17] = v0 + vs[16] = cr + vs[17] = cg + vs[18] = cb + vs[19] = ca - vs[18] = ax + by + tx - vs[19] = cx + dy + ty - vs[20] = u1 - vs[21] = v1 + vs[20] = by + tx + vs[21] = dy + ty vs[22] = u0 - vs[23] = v0 + vs[23] = v1 + vs[24] = u1 + vs[25] = v0 + vs[26] = cr + vs[27] = cg + vs[28] = cb + vs[29] = ca + + vs[30] = ax + by + tx + vs[31] = cx + dy + ty + vs[32] = u1 + vs[33] = v1 + vs[34] = u0 + vs[35] = v0 + vs[36] = cr + vs[37] = cg + vs[38] = cb + vs[39] = ca return vs } diff --git a/internal/restorable/image.go b/internal/restorable/image.go index 63651d431..d0b24870f 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -163,7 +163,8 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { colorm := (*affine.ColorM)(nil).Scale(0, 0, 0, 0) vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, float32(width)/float32(w), 0, 0, float32(height)/float32(h), - float32(x), float32(y)) + float32(x), float32(y), + 1, 1, 1, 1) is := graphicsutil.QuadIndices() i.image.DrawImage(dummyImage.image, vs, is, colorm, opengl.CompositeModeCopy, graphics.FilterNearest) } diff --git a/internal/restorable/images_test.go b/internal/restorable/images_test.go index 4aefae02c..40fbb49e9 100644 --- a/internal/restorable/images_test.go +++ b/internal/restorable/images_test.go @@ -116,7 +116,7 @@ func TestRestoreChain(t *testing.T) { fill(imgs[0], clr.R, clr.G, clr.B, clr.A) for i := 0; i < num-1; i++ { w, h := imgs[i].Size() - vs := graphicsutil.QuadVertices(w, h, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0) + vs := graphicsutil.QuadVertices(w, h, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() imgs[i+1].DrawImage(imgs[i], vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest) } @@ -158,7 +158,7 @@ func TestRestoreChain2(t *testing.T) { clr8 := color.RGBA{0x00, 0x00, 0xff, 0xff} fill(imgs[8], clr8.R, clr8.G, clr8.B, clr8.A) - vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0) + vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() imgs[8].DrawImage(imgs[7], vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest) imgs[9].DrawImage(imgs[8], vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest) @@ -204,7 +204,7 @@ func TestRestoreOverrideSource(t *testing.T) { clr0 := color.RGBA{0x00, 0x00, 0x00, 0xff} clr1 := color.RGBA{0x00, 0x00, 0x01, 0xff} fill(img1, clr0.R, clr0.G, clr0.B, clr0.A) - vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0) + vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() img2.DrawImage(img1, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) img3.DrawImage(img2, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) @@ -289,24 +289,24 @@ func TestRestoreComplexGraph(t *testing.T) { img1.Dispose() img0.Dispose() }() - vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0) + vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() img3.DrawImage(img0, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) - vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0) + vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1) img3.DrawImage(img1, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) - vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0) + vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1) img4.DrawImage(img1, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) - vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 2, 0) + vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 2, 0, 1, 1, 1, 1) img4.DrawImage(img2, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) - vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0) + vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) img5.DrawImage(img3, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) - vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0) + vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) img6.DrawImage(img3, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) - vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0) + vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1) img6.DrawImage(img4, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) - vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0) + vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) img7.DrawImage(img2, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) - vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 2, 0) + vs = graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 2, 0, 1, 1, 1, 1) img7.DrawImage(img3, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) ResolveStaleImages() if err := Restore(); err != nil { @@ -397,7 +397,7 @@ func TestRestoreRecursive(t *testing.T) { img1.Dispose() img0.Dispose() }() - vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0) + vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() img1.DrawImage(img0, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) img0.DrawImage(img1, vs, is, nil, opengl.CompositeModeSourceOver, graphics.FilterNearest) @@ -485,7 +485,7 @@ func TestDrawImageAndReplacePixels(t *testing.T) { img1 := NewImage(2, 1, false) defer img1.Dispose() - vs := graphicsutil.QuadVertices(1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0) + vs := graphicsutil.QuadVertices(1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() img1.DrawImage(img0, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest) img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff}, 1, 0, 1, 1) @@ -517,7 +517,7 @@ func TestDispose(t *testing.T) { img2 := newImageFromImage(base2) defer img2.Dispose() - vs := graphicsutil.QuadVertices(1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0) + vs := graphicsutil.QuadVertices(1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() img1.DrawImage(img2, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest) img0.DrawImage(img1, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest) @@ -545,7 +545,7 @@ func TestDoubleResolve(t *testing.T) { base.Pix[3] = 0xff img1 := newImageFromImage(base) - vs := graphicsutil.QuadVertices(1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0) + vs := graphicsutil.QuadVertices(1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() img0.DrawImage(img1, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest) img0.ReplacePixels([]uint8{0x00, 0xff, 0x00, 0xff}, 1, 1, 1, 1) diff --git a/internal/shareable/shareable.go b/internal/shareable/shareable.go index d116b8c8e..03db8324e 100644 --- a/internal/shareable/shareable.go +++ b/internal/shareable/shareable.go @@ -63,7 +63,7 @@ func (b *backend) TryAlloc(width, height int) (*packing.Node, bool) { newImg := restorable.NewImage(s, s, false) oldImg := b.restorable w, h := oldImg.Size() - vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0) + vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() newImg.DrawImage(oldImg, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest) oldImg.Dispose() @@ -129,7 +129,7 @@ func (i *Image) ensureNotShared() { x, y, w, h := i.region() newImg := restorable.NewImage(w, h, false) vw, vh := i.backend.restorable.Size() - vs := graphicsutil.QuadVertices(vw, vh, x, y, x+w, y+h, 1, 0, 0, 1, 0, 0) + vs := graphicsutil.QuadVertices(vw, vh, x, y, x+w, y+h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() newImg.DrawImage(i.backend.restorable, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest) @@ -184,13 +184,13 @@ func (i *Image) Size() (width, height int) { return i.width, i.height } -func (i *Image) QuadVertices(sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32) []float32 { +func (i *Image) QuadVertices(sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32, cr, cg, cb, ca float32) []float32 { if i.backend == nil { i.allocate(true) } ox, oy, _, _ := i.region() w, h := i.backend.restorable.SizePowerOf2() - return graphicsutil.QuadVertices(w, h, sx0+ox, sy0+oy, sx1+ox, sy1+oy, a, b, c, d, tx, ty) + return graphicsutil.QuadVertices(w, h, sx0+ox, sy0+oy, sx1+ox, sy1+oy, a, b, c, d, tx, ty, cr, cg, cb, ca) } const MaxCountForShare = 10 diff --git a/internal/shareable/shareable_test.go b/internal/shareable/shareable_test.go index 5f6ae44da..b7e19b308 100644 --- a/internal/shareable/shareable_test.go +++ b/internal/shareable/shareable_test.go @@ -86,7 +86,7 @@ func TestEnsureNotShared(t *testing.T) { dy1 = size * 3 / 4 ) // img4.ensureNotShared() should be called. - vs := img3.QuadVertices(0, 0, size/2, size/2, 1, 0, 0, 1, size/4, size/4) + vs := img3.QuadVertices(0, 0, size/2, size/2, 1, 0, 0, 1, size/4, size/4, 1, 1, 1, 1) is := graphicsutil.QuadIndices() img4.DrawImage(img3, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest) want := false @@ -150,7 +150,7 @@ func Disabled_TestReshared(t *testing.T) { } // Use img1 as a render target. - vs := img2.QuadVertices(0, 0, size, size, 1, 0, 0, 1, 0, 0) + vs := img2.QuadVertices(0, 0, size, size, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1) is := graphicsutil.QuadIndices() img1.DrawImage(img2, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest) want = false