From 1dde94d8ddc0e0ff07e57bd79d93705d31f56914 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Fri, 12 Mar 2021 16:01:49 +0100 Subject: [PATCH] gpu: [compute] use support for simple strokes In the old renderer, all strokes are converted to filled paths. The new renderer can draw simple strokes natively. Do that, and avoid the costly conversions. Signed-off-by: Elias Naur --- gpu/compute.go | 49 +++++++++++++++++++++++++++++++++++++------------ gpu/gpu.go | 2 +- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/gpu/compute.go b/gpu/compute.go index 51329f7f..18e8027e 100644 --- a/gpu/compute.go +++ b/gpu/compute.go @@ -623,7 +623,7 @@ func (g *compute) encodeOps(trans f32.Affine2D, viewport image.Point, ops []imag // clip is the union of all drawing affected by the clipping // operation. TODO: tighten. clip := f32.Rect(0, 0, float32(viewport.X), float32(viewport.Y)) - nclips := g.encodeClipStack(clip, bounds, op.path) + nclips := g.encodeClipStack(clip, bounds, op.path, false) m := op.material switch m.material { case materialTexture: @@ -646,6 +646,10 @@ func (g *compute) encodeOps(trans f32.Affine2D, viewport image.Point, ops []imag default: panic("not implemented") } + if op.path != nil && op.path.path { + g.enc.fillMode(scene.FillModeNonzero) + g.enc.transform(op.path.trans.Invert()) + } // Pop the clip stack. for i := 0; i < nclips; i++ { g.enc.endClip(clip) @@ -655,40 +659,61 @@ func (g *compute) encodeOps(trans f32.Affine2D, viewport image.Point, ops []imag } // encodeClips encodes a stack of clip paths and return the stack depth. -func (g *compute) encodeClipStack(clip, bounds f32.Rectangle, p *pathOp) int { +func (g *compute) encodeClipStack(clip, bounds f32.Rectangle, p *pathOp, begin bool) int { nclips := 0 if p != nil && p.parent != nil { - nclips += g.encodeClipStack(clip, bounds, p.parent) - g.enc.beginClip(clip) + nclips += g.encodeClipStack(clip, bounds, p.parent, true) nclips += 1 } + s := isStroke(p) if p != nil && p.path { + if s { + g.enc.fillMode(scene.FillModeStroke) + g.enc.lineWidth(p.stroke.Width) + } pathData, _ := g.drawOps.pathCache.get(p.pathKey) g.enc.transform(p.trans) g.enc.append(pathData.computePath) - g.enc.transform(p.trans.Invert()) } else { g.enc.rect(bounds) } + if begin { + g.enc.beginClip(clip) + if s { + g.enc.fillMode(scene.FillModeNonzero) + } + if p != nil && p.path { + g.enc.transform(p.trans.Invert()) + } + } return nclips } -func encodePath(pathData []byte, stroke clip.StrokeStyle, dashes dashOp) encoder { +func supportsStroke(p *pathOp) bool { + return isSolidLine(p.dashes) && p.stroke.Miter == 0 && p.stroke.Join == clip.RoundJoin && p.stroke.Cap == clip.RoundCap +} + +func isStroke(p *pathOp) bool { + return p.stroke.Width > 0 && supportsStroke(p) +} + +func encodePath(p *pathOp) encoder { var enc encoder - if stroke.Width > 0 { - quads := decodeToStrokeQuads(pathData) - quads = quads.stroke(stroke, dashes) + verts := p.pathVerts + if p.stroke.Width > 0 && !supportsStroke(p) { + quads := decodeToStrokeQuads(verts) + quads = quads.stroke(p.stroke, p.dashes) for _, quad := range quads { q := quad.quad enc.quad(q.From, q.Ctrl, q.To) } return enc } - for len(pathData) >= scene.CommandSize+4 { - cmd := ops.DecodeCommand(pathData[4:]) + for len(verts) >= scene.CommandSize+4 { + cmd := ops.DecodeCommand(verts[4:]) enc.scene = append(enc.scene, cmd) enc.npathseg++ - pathData = pathData[scene.CommandSize+4:] + verts = verts[scene.CommandSize+4:] } return enc } diff --git a/gpu/gpu.go b/gpu/gpu.go index f685db51..b5dbb448 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -835,7 +835,7 @@ func (d *drawOps) collect(ctx driver.Device, cache *resourceCache, root *op.Ops, data := buildPath(ctx, p.pathVerts) var computePath encoder if d.compute { - computePath = encodePath(p.pathVerts, p.stroke, p.dashes) + computePath = encodePath(p) } d.pathCache.put(p.pathKey, opCacheValue{ data: data,