From 5da544d78b49ffa57ae039e26ded58cf53261b3a Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Thu, 19 Jul 2018 16:59:22 +0200 Subject: [PATCH] performance optimization, avoid use of stencil buffer when drawing with opaque styles --- canvas.go | 13 ++++++ gradients.go | 8 +++- images.go | 21 ++++++++- paths.go | 120 +++++++++++++++++++++++++++++---------------------- 4 files changed, 107 insertions(+), 55 deletions(-) diff --git a/canvas.go b/canvas.go index 5ff65ff..1d49450 100644 --- a/canvas.go +++ b/canvas.go @@ -360,6 +360,19 @@ func parseStyle(value ...interface{}) drawStyle { return style } +func (s *drawStyle) isOpaque() bool { + if lg := s.linearGradient; lg != nil { + return lg.opaque + } + if rg := s.radialGradient; rg != nil { + return rg.opaque + } + if img := s.image; img != nil { + return img.opaque + } + return s.color.a >= 1 +} + func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) { if lg := style.linearGradient; lg != nil { lg.load() diff --git a/gradients.go b/gradients.go index 51d6fd7..c7a88c9 100644 --- a/gradients.go +++ b/gradients.go @@ -28,6 +28,7 @@ type gradient struct { tex uint32 loaded bool deleted bool + opaque bool } type gradientStop struct { @@ -39,7 +40,7 @@ type gradientStop struct { // the coordinates from where to where the gradient // will apply on the canvas func NewLinearGradient(x0, y0, x1, y1 float64) *LinearGradient { - lg := &LinearGradient{gradient: gradient{from: vec{x0, y0}, to: vec{x1, y1}}} + lg := &LinearGradient{gradient: gradient{from: vec{x0, y0}, to: vec{x1, y1}, opaque: true}} gli.GenTextures(1, &lg.tex) gli.ActiveTexture(gl_TEXTURE0) gli.BindTexture(gl_TEXTURE_2D, lg.tex) @@ -60,7 +61,7 @@ func NewLinearGradient(x0, y0, x1, y1 float64) *LinearGradient { // gradient will apply from the first to the second // circle func NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient { - rg := &RadialGradient{gradient: gradient{from: vec{x0, y0}, to: vec{x1, y1}}, radFrom: r0, radTo: r1} + rg := &RadialGradient{gradient: gradient{from: vec{x0, y0}, to: vec{x1, y1}, opaque: true}, radFrom: r0, radTo: r1} gli.GenTextures(1, &rg.tex) gli.ActiveTexture(gl_TEXTURE0) gli.BindTexture(gl_TEXTURE_2D, rg.tex) @@ -137,6 +138,9 @@ func (g *gradient) colorAt(pos float64) glColor { // right place func (g *gradient) AddColorStop(pos float64, color ...interface{}) { c, _ := parseColor(color...) + if c.a < 1 { + g.opaque = false + } insert := len(g.stops) for i, stop := range g.stops { if stop.pos > pos { diff --git a/images.go b/images.go index bc57504..d071bab 100644 --- a/images.go +++ b/images.go @@ -14,6 +14,7 @@ type Image struct { w, h int tex uint32 deleted bool + opaque bool } var images = make(map[string]*Image) @@ -89,7 +90,20 @@ func getImage(image interface{}) *Image { } func loadImageRGBA(src *image.RGBA) (*Image, error) { - img := &Image{w: src.Bounds().Dx(), h: src.Bounds().Dy()} + img := &Image{w: src.Bounds().Dx(), h: src.Bounds().Dy(), opaque: true} + +checkOpaque: + for y := 0; y < img.h; y++ { + off := src.PixOffset(0, y) + 3 + for x := 0; x < img.w; x++ { + if src.Pix[off] < 255 { + img.opaque = false + break checkOpaque + } + off += 4 + } + } + gli.GenTextures(1, &img.tex) gli.ActiveTexture(gl_TEXTURE0) gli.BindTexture(gl_TEXTURE_2D, img.tex) @@ -155,7 +169,7 @@ func loadImageGray(src *image.Gray) (*Image, error) { } func loadImageConverted(src image.Image) (*Image, error) { - img := &Image{w: src.Bounds().Dx(), h: src.Bounds().Dy()} + img := &Image{w: src.Bounds().Dx(), h: src.Bounds().Dy(), opaque: true} gli.GenTextures(1, &img.tex) gli.ActiveTexture(gl_TEXTURE0) gli.BindTexture(gl_TEXTURE_2D, img.tex) @@ -172,6 +186,9 @@ func loadImageConverted(src image.Image) (*Image, error) { ir, ig, ib, ia := src.At(x, y).RGBA() r, g, b, a := uint8(ir>>8), uint8(ig>>8), uint8(ib>>8), uint8(ia>>8) data = append(data, r, g, b, a) + if a < 255 { + img.opaque = false + } } } gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(img.w), int32(img.h), 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&data[0])) diff --git a/paths.go b/paths.go index a8e524b..49ca4d7 100644 --- a/paths.go +++ b/paths.go @@ -364,41 +364,50 @@ func (cv *Canvas) stroke(path []pathPoint) { gli.BindBuffer(gl_ARRAY_BUFFER, buf) - gli.ColorMask(false, false, false, false) - gli.StencilFunc(gl_ALWAYS, 1, 0xFF) - gli.StencilOp(gl_REPLACE, gl_REPLACE, gl_REPLACE) - gli.StencilMask(0x01) + if cv.state.globalAlpha >= 1 && cv.state.lineAlpha >= 1 && cv.state.stroke.isOpaque() { + vertex := cv.useShader(&cv.state.stroke) - gli.UseProgram(sr.id) - gli.Uniform4f(sr.color, 0, 0, 0, 0) - gli.Uniform2f(sr.canvasSize, float32(cv.fw), float32(cv.fh)) + gli.EnableVertexAttribArray(vertex) + gli.VertexAttribPointer(vertex, 2, gl_FLOAT, false, 0, 0) + gli.DrawArrays(gl_TRIANGLES, 6, int32(len(tris)/2-6)) + gli.DisableVertexAttribArray(vertex) + } else { + gli.ColorMask(false, false, false, false) + gli.StencilFunc(gl_ALWAYS, 1, 0xFF) + gli.StencilOp(gl_REPLACE, gl_REPLACE, gl_REPLACE) + gli.StencilMask(0x01) - gli.EnableVertexAttribArray(sr.vertex) - gli.VertexAttribPointer(sr.vertex, 2, gl_FLOAT, false, 0, 0) - gli.DrawArrays(gl_TRIANGLES, 6, int32(len(tris)/2-6)) - gli.DisableVertexAttribArray(sr.vertex) + gli.UseProgram(sr.id) + gli.Uniform4f(sr.color, 0, 0, 0, 0) + gli.Uniform2f(sr.canvasSize, float32(cv.fw), float32(cv.fh)) - gli.ColorMask(true, true, true, true) + gli.EnableVertexAttribArray(sr.vertex) + gli.VertexAttribPointer(sr.vertex, 2, gl_FLOAT, false, 0, 0) + gli.DrawArrays(gl_TRIANGLES, 6, int32(len(tris)/2-6)) + gli.DisableVertexAttribArray(sr.vertex) - gli.StencilFunc(gl_EQUAL, 1, 0xFF) + gli.ColorMask(true, true, true, true) - origAlpha := cv.state.globalAlpha - if cv.state.lineAlpha < 1 { - cv.state.globalAlpha *= cv.state.lineAlpha + gli.StencilFunc(gl_EQUAL, 1, 0xFF) + + origAlpha := cv.state.globalAlpha + if cv.state.lineAlpha < 1 { + cv.state.globalAlpha *= cv.state.lineAlpha + } + vertex := cv.useShader(&cv.state.stroke) + cv.state.globalAlpha = origAlpha + + gli.EnableVertexAttribArray(vertex) + gli.VertexAttribPointer(vertex, 2, gl_FLOAT, false, 0, 0) + gli.DrawArrays(gl_TRIANGLES, 0, 6) + gli.DisableVertexAttribArray(vertex) + + gli.StencilOp(gl_KEEP, gl_KEEP, gl_KEEP) + gli.StencilFunc(gl_ALWAYS, 0, 0xFF) + + gli.Clear(gl_STENCIL_BUFFER_BIT) + gli.StencilMask(0xFF) } - vertex := cv.useShader(&cv.state.stroke) - cv.state.globalAlpha = origAlpha - - gli.EnableVertexAttribArray(vertex) - gli.VertexAttribPointer(vertex, 2, gl_FLOAT, false, 0, 0) - gli.DrawArrays(gl_TRIANGLES, 0, 6) - gli.DisableVertexAttribArray(vertex) - - gli.StencilOp(gl_KEEP, gl_KEEP, gl_KEEP) - gli.StencilFunc(gl_ALWAYS, 0, 0xFF) - - gli.Clear(gl_STENCIL_BUFFER_BIT) - gli.StencilMask(0xFF) } func (cv *Canvas) lineJoint(p pathPoint, p0, p1, p2, l0p0, l0p1, l0p2, l0p3 vec, tris []float32) []float32 { @@ -521,35 +530,44 @@ func (cv *Canvas) Fill() { gli.BindBuffer(gl_ARRAY_BUFFER, buf) - gli.ColorMask(false, false, false, false) - gli.StencilFunc(gl_ALWAYS, 1, 0xFF) - gli.StencilOp(gl_REPLACE, gl_REPLACE, gl_REPLACE) - gli.StencilMask(0x01) + if cv.state.globalAlpha >= 1 && cv.state.lineAlpha >= 1 && cv.state.fill.isOpaque() { + vertex := cv.useShader(&cv.state.fill) - gli.UseProgram(sr.id) - gli.Uniform4f(sr.color, 0, 0, 0, 0) - gli.Uniform2f(sr.canvasSize, float32(cv.fw), float32(cv.fh)) + gli.EnableVertexAttribArray(vertex) + gli.VertexAttribPointer(vertex, 2, gl_FLOAT, false, 0, 0) + gli.DrawArrays(gl_TRIANGLES, 6, int32(len(tris)/2-6)) + gli.DisableVertexAttribArray(vertex) + } else { + gli.ColorMask(false, false, false, false) + gli.StencilFunc(gl_ALWAYS, 1, 0xFF) + gli.StencilOp(gl_REPLACE, gl_REPLACE, gl_REPLACE) + gli.StencilMask(0x01) - gli.EnableVertexAttribArray(sr.vertex) - gli.VertexAttribPointer(sr.vertex, 2, gl_FLOAT, false, 0, 0) - gli.DrawArrays(gl_TRIANGLES, 6, int32(len(tris)/2-6)) - gli.DisableVertexAttribArray(sr.vertex) + gli.UseProgram(sr.id) + gli.Uniform4f(sr.color, 0, 0, 0, 0) + gli.Uniform2f(sr.canvasSize, float32(cv.fw), float32(cv.fh)) - gli.ColorMask(true, true, true, true) + gli.EnableVertexAttribArray(sr.vertex) + gli.VertexAttribPointer(sr.vertex, 2, gl_FLOAT, false, 0, 0) + gli.DrawArrays(gl_TRIANGLES, 6, int32(len(tris)/2-6)) + gli.DisableVertexAttribArray(sr.vertex) - gli.StencilFunc(gl_EQUAL, 1, 0xFF) + gli.ColorMask(true, true, true, true) - vertex := cv.useShader(&cv.state.fill) - gli.EnableVertexAttribArray(vertex) - gli.VertexAttribPointer(vertex, 2, gl_FLOAT, false, 0, 0) - gli.DrawArrays(gl_TRIANGLES, 0, 6) - gli.DisableVertexAttribArray(vertex) + gli.StencilFunc(gl_EQUAL, 1, 0xFF) - gli.StencilOp(gl_KEEP, gl_KEEP, gl_KEEP) - gli.StencilFunc(gl_ALWAYS, 0, 0xFF) + vertex := cv.useShader(&cv.state.fill) + gli.EnableVertexAttribArray(vertex) + gli.VertexAttribPointer(vertex, 2, gl_FLOAT, false, 0, 0) + gli.DrawArrays(gl_TRIANGLES, 0, 6) + gli.DisableVertexAttribArray(vertex) - gli.Clear(gl_STENCIL_BUFFER_BIT) - gli.StencilMask(0xFF) + gli.StencilOp(gl_KEEP, gl_KEEP, gl_KEEP) + gli.StencilFunc(gl_ALWAYS, 0, 0xFF) + + gli.Clear(gl_STENCIL_BUFFER_BIT) + gli.StencilMask(0xFF) + } } func (cv *Canvas) appendSubPathTriangles(tris []float32, path []pathPoint) []float32 {