From eb9bf60b097cb5a4584f7766660e1a77f32526eb Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Thu, 11 Mar 2021 13:47:33 +0100 Subject: [PATCH] gpu,internal/ops: decode scene commands directly, not through quads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We're about to let clip.Path use more of the compute renderer features (lines, cubic béziers). This change prepares the gpu package for reading one of several commands types, not just the quadratic béziers of before. The old Quad type is still the basis for the stroking algorithms, but this change moves it into package gpu which is the only user. Signed-off-by: Elias Naur --- gpu/clip.go | 3 +- gpu/dash.go | 5 +-- gpu/gpu.go | 85 +++++++++++++++++++++++++++++++---------- gpu/stroke.go | 18 ++++----- internal/ops/ops.go | 21 ++-------- internal/scene/scene.go | 61 ++++++++++++++++------------- op/clip/clip.go | 5 +-- 7 files changed, 116 insertions(+), 82 deletions(-) diff --git a/gpu/clip.go b/gpu/clip.go index 920ebb94..3c4e5ca6 100644 --- a/gpu/clip.go +++ b/gpu/clip.go @@ -2,7 +2,6 @@ package gpu import ( "gioui.org/f32" - "gioui.org/internal/ops" ) type quadSplitter struct { @@ -47,7 +46,7 @@ func (qs *quadSplitter) encodeQuadTo(from, ctrl, to f32.Point) { encodeQuadTo(data, qs.contour, from, ctrl, to) } -func (qs *quadSplitter) splitAndEncode(quad ops.Quad) { +func (qs *quadSplitter) splitAndEncode(quad quadSegment) { cbnd := f32.Rectangle{ Min: quad.From, Max: quad.To, diff --git a/gpu/dash.go b/gpu/dash.go index 06f0cbf8..76706026 100644 --- a/gpu/dash.go +++ b/gpu/dash.go @@ -11,7 +11,6 @@ import ( "sort" "gioui.org/f32" - "gioui.org/internal/ops" ) func isSolidLine(sty dashOp) bool { @@ -238,7 +237,7 @@ func (qs strokeQuads) splitAt(contour *uint32, ts ...float64) []strokeQuads { oi = append(oi, strokeQuad{ contour: *contour, - quad: ops.Quad{ + quad: quadSegment{ From: from, Ctrl: q1, To: r0, @@ -256,7 +255,7 @@ func (qs strokeQuads) splitAt(contour *uint32, ts ...float64) []strokeQuads { } oi = append(oi, strokeQuad{ contour: *contour, - quad: ops.Quad{ + quad: quadSegment{ From: r0, Ctrl: r1, To: r2, diff --git a/gpu/gpu.go b/gpu/gpu.go index efa3c0f7..0b1d5867 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -25,6 +25,7 @@ import ( "gioui.org/internal/f32color" "gioui.org/internal/opconst" "gioui.org/internal/ops" + "gioui.org/internal/scene" "gioui.org/layout" "gioui.org/op" "gioui.org/op/clip" @@ -141,6 +142,10 @@ type dashOp struct { dashes []float32 } +type quadSegment struct { + From, Ctrl, To f32.Point +} + func decodeDashOp(data []byte) dashOp { _ = data[5] if opconst.OpType(data[0]) != opconst.TypeDash { @@ -1340,28 +1345,19 @@ func (d *drawOps) writeVertCache(n int) []byte { } // transform, split paths as needed, calculate maxY, bounds and create GPU vertices. -func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, outline bool, stroke clip.StrokeStyle, dashes dashOp) (verts []byte, bounds f32.Rectangle) { +func (d *drawOps) buildVerts(pathData []byte, tr f32.Affine2D, outline bool, stroke clip.StrokeStyle, dashes dashOp) (verts []byte, bounds f32.Rectangle) { inf := float32(math.Inf(+1)) d.qs.bounds = f32.Rectangle{ Min: f32.Point{X: inf, Y: inf}, Max: f32.Point{X: -inf, Y: -inf}, } d.qs.d = d - bo := binary.LittleEndian startLength := len(d.vertCache) switch { case stroke.Width > 0: // Stroke path. - quads := make(strokeQuads, 0, 2*len(aux)/(ops.QuadSize+4)) - for len(aux) >= ops.QuadSize+4 { - quad := strokeQuad{ - contour: bo.Uint32(aux), - quad: ops.DecodeQuad(aux[4:]), - } - quads = append(quads, quad) - aux = aux[ops.QuadSize+4:] - } + quads := decodeToStrokeQuads(pathData) quads = quads.stroke(stroke, dashes) for _, quad := range quads { d.qs.contour = quad.contour @@ -1371,22 +1367,56 @@ func (d *drawOps) buildVerts(aux []byte, tr f32.Affine2D, outline bool, stroke c } case outline: - // Outline path. - for len(aux) >= ops.QuadSize+4 { - d.qs.contour = bo.Uint32(aux) - quad := ops.DecodeQuad(aux[4:]) - quad = quad.Transform(tr) - - d.qs.splitAndEncode(quad) - - aux = aux[ops.QuadSize+4:] - } + decodeToOutlineQuads(&d.qs, tr, pathData) } fillMaxY(d.vertCache[startLength:]) return d.vertCache[startLength:], d.qs.bounds } +// decodeOutlineQuads decodes scene commands, splits them into quadratic béziers +// as needed and feeds them to the supplied splitter. +func decodeToOutlineQuads(qs *quadSplitter, tr f32.Affine2D, pathData []byte) { + for len(pathData) >= scene.CommandSize+4 { + qs.contour = bo.Uint32(pathData) + cmd := ops.DecodeCommand(pathData[4:]) + switch cmd.Op() { + case scene.OpFillQuad: + var q quadSegment + q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd) + q = q.Transform(tr) + qs.splitAndEncode(q) + default: + panic("unsupported scene command") + } + pathData = pathData[scene.CommandSize+4:] + } +} + +// decodeToStrokeQuads is like decodeOutlineQuads, except it returns a list of stroke +// quads ready to stroke. +func decodeToStrokeQuads(pathData []byte) strokeQuads { + quads := make(strokeQuads, 0, 2*len(pathData)/(scene.CommandSize+4)) + for len(pathData) >= scene.CommandSize+4 { + contour := bo.Uint32(pathData) + cmd := ops.DecodeCommand(pathData[4:]) + switch cmd.Op() { + case scene.OpFillQuad: + var q quadSegment + q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd) + quad := strokeQuad{ + contour: contour, + quad: q, + } + quads = append(quads, quad) + default: + panic("unsupported scene command") + } + pathData = pathData[scene.CommandSize+4:] + } + return quads +} + // create GPU vertices for transformed r, find the bounds and establish texture transform. func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (aux []byte, bnd f32.Rectangle, ptr f32.Affine2D) { if isPureOffset(tr) { @@ -1448,3 +1478,16 @@ func isPureOffset(t f32.Affine2D) bool { a, b, _, d, e, _ := t.Elems() return a == 1 && b == 0 && d == 0 && e == 1 } + +func (q quadSegment) Transform(t f32.Affine2D) quadSegment { + q.From = t.Transform(q.From) + q.Ctrl = t.Transform(q.Ctrl) + q.To = t.Transform(q.To) + return q +} + +func decodeQuad(d []byte) (q quadSegment) { + cmd := ops.DecodeCommand(d) + q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd) + return +} diff --git a/gpu/stroke.go b/gpu/stroke.go index d75ef325..c0bd8f18 100644 --- a/gpu/stroke.go +++ b/gpu/stroke.go @@ -27,7 +27,7 @@ import ( "math" "gioui.org/f32" - "gioui.org/internal/ops" + "gioui.org/internal/scene" "gioui.org/op" "gioui.org/op/clip" ) @@ -43,7 +43,7 @@ const strokeTolerance = 0.01 type strokeQuad struct { contour uint32 - quad ops.Quad + quad quadSegment } type strokeState struct { @@ -74,7 +74,7 @@ func (qs *strokeQuads) closed() bool { func (qs *strokeQuads) lineTo(pt f32.Point) { end := qs.pen() *qs = append(*qs, strokeQuad{ - quad: ops.Quad{ + quad: quadSegment{ From: end, Ctrl: end.Add(pt).Mul(0.5), To: pt, @@ -94,9 +94,9 @@ func (qs *strokeQuads) arc(f1, f2 f32.Point, angle float32) { end := len(o.Data()) raw := o.Data()[beg:end] - for qi := 0; len(raw) >= (ops.QuadSize + 4); qi++ { - quad := ops.DecodeQuad(raw[4:]) - raw = raw[ops.QuadSize+4:] + for qi := 0; len(raw) >= (scene.CommandSize + 4); qi++ { + quad := decodeQuad(raw[4:]) + raw = raw[scene.CommandSize+4:] *qs = append(*qs, strokeQuad{ quad: quad, }) @@ -240,7 +240,7 @@ func (qs *strokeQuads) close() { } *qs = append(*qs, strokeQuad{ - quad: ops.Quad{ + quad: quadSegment{ From: p0, Ctrl: p0.Add(p1).Mul(0.5), To: p1, @@ -293,7 +293,7 @@ func (qs strokeQuads) append(ps strokeQuads) strokeQuads { p1 := ps[0].quad.From if p0 != p1 && lenPt(p0.Sub(p1)) < strokeTolerance { qs = append(qs, strokeQuad{ - quad: ops.Quad{ + quad: quadSegment{ From: p0, Ctrl: p0.Add(p1).Mul(0.5), To: p1, @@ -477,7 +477,7 @@ func (qs *strokeQuads) addLine(p0, ctrl, p1 f32.Point, t, d float32) { *qs = append(*qs, strokeQuad{ - quad: ops.Quad{ + quad: quadSegment{ From: p0, Ctrl: p0.Add(p1).Mul(0.5), To: p1, diff --git a/internal/ops/ops.go b/internal/ops/ops.go index a584a615..32105bce 100644 --- a/internal/ops/ops.go +++ b/internal/ops/ops.go @@ -5,7 +5,6 @@ package ops import ( "encoding/binary" "math" - "unsafe" "gioui.org/f32" "gioui.org/internal/byteslice" @@ -13,24 +12,10 @@ import ( "gioui.org/internal/scene" ) -const QuadSize = int(unsafe.Sizeof(scene.Command{})) - -type Quad struct { - From, Ctrl, To f32.Point -} - -func (q Quad) Transform(t f32.Affine2D) Quad { - q.From = t.Transform(q.From) - q.Ctrl = t.Transform(q.Ctrl) - q.To = t.Transform(q.To) - return q -} - -func DecodeQuad(d []byte) (q Quad) { +func DecodeCommand(d []byte) scene.Command { var cmd scene.Command - copy(byteslice.Slice(cmd[:]), d) - q.From, q.Ctrl, q.To = scene.DecodeQuad(cmd) - return + copy(byteslice.Uint32(cmd[:]), d) + return cmd } func DecodeTransform(data []byte) (t f32.Affine2D) { diff --git a/internal/scene/scene.go b/internal/scene/scene.go index 9ba916a9..8326ef42 100644 --- a/internal/scene/scene.go +++ b/internal/scene/scene.go @@ -7,36 +7,45 @@ package scene import ( "image/color" "math" + "unsafe" "gioui.org/f32" ) +type Op uint32 + type Command [sceneElemSize / 4]uint32 const sceneElemSize = 36 // GPU commands from scene.h const ( - elemNop = iota - elemStrokeLine - elemFillLine - elemStrokeQuad - elemFillQuad - elemStrokeCubic - elemFillCubic - elemStroke - elemFill - elemLineWidth - elemTransform - elemBeginClip - elemEndClip - elemFillImage + OpNop Op = iota + OpStrokeLine + OpFillLine + OpStrokeQuad + OpFillQuad + OpStrokeCubic + OpFillCubic + OpStroke + OpFill + OpLineWidth + OpTransform + OpBeginClip + OpEndClip + OpFillImage ) +const CommandSize = int(unsafe.Sizeof(Command{})) + +func (c Command) Op() Op { + return Op(c[0]) +} + func Line(start, end f32.Point, stroke bool, flags uint32) Command { - tag := uint32(elemFillLine) + tag := uint32(OpFillLine) if stroke { - tag = elemStrokeLine + tag = uint32(OpStrokeLine) } return Command{ 0: flags<<16 | tag, @@ -48,9 +57,9 @@ func Line(start, end f32.Point, stroke bool, flags uint32) Command { } func Quad(start, ctrl, end f32.Point, stroke bool) Command { - tag := uint32(elemFillQuad) + tag := uint32(OpFillQuad) if stroke { - tag = elemStrokeQuad + tag = uint32(OpStrokeQuad) } return Command{ 0: tag, @@ -66,7 +75,7 @@ func Quad(start, ctrl, end f32.Point, stroke bool) Command { func Transform(m f32.Affine2D) Command { sx, hx, ox, hy, sy, oy := m.Elems() return Command{ - 0: elemTransform, + 0: uint32(OpTransform), 1: math.Float32bits(sx), 2: math.Float32bits(hy), 3: math.Float32bits(hx), @@ -78,21 +87,21 @@ func Transform(m f32.Affine2D) Command { func LineWidth(width float32) Command { return Command{ - 0: elemLineWidth, + 0: uint32(OpLineWidth), 1: math.Float32bits(width), } } func Stroke(col color.RGBA) Command { return Command{ - 0: elemStroke, + 0: uint32(OpStroke), 1: uint32(col.R)<<24 | uint32(col.G)<<16 | uint32(col.B)<<8 | uint32(col.A), } } func BeginClip(bbox f32.Rectangle) Command { return Command{ - 0: elemBeginClip, + 0: uint32(OpBeginClip), 1: math.Float32bits(bbox.Min.X), 2: math.Float32bits(bbox.Min.Y), 3: math.Float32bits(bbox.Max.X), @@ -102,7 +111,7 @@ func BeginClip(bbox f32.Rectangle) Command { func EndClip(bbox f32.Rectangle) Command { return Command{ - 0: elemEndClip, + 0: uint32(OpEndClip), 1: math.Float32bits(bbox.Min.X), 2: math.Float32bits(bbox.Min.Y), 3: math.Float32bits(bbox.Max.X), @@ -112,20 +121,20 @@ func EndClip(bbox f32.Rectangle) Command { func Fill(col color.RGBA) Command { return Command{ - 0: elemFill, + 0: uint32(OpFill), 1: uint32(col.R)<<24 | uint32(col.G)<<16 | uint32(col.B)<<8 | uint32(col.A), } } func FillImage(index int) Command { return Command{ - 0: elemFillImage, + 0: uint32(OpFillImage), 1: uint32(index), } } func DecodeQuad(cmd Command) (from, ctrl, to f32.Point) { - if cmd[0] != elemFillQuad { + if cmd[0] != uint32(OpFillQuad) { panic("invalid command") } from = f32.Pt(math.Float32frombits(cmd[1]), math.Float32frombits(cmd[2])) diff --git a/op/clip/clip.go b/op/clip/clip.go index 7adc3ab8..fe0e257a 100644 --- a/op/clip/clip.go +++ b/op/clip/clip.go @@ -10,7 +10,6 @@ import ( "gioui.org/f32" "gioui.org/internal/byteslice" "gioui.org/internal/opconst" - "gioui.org/internal/ops" "gioui.org/internal/scene" "gioui.org/op" ) @@ -154,7 +153,7 @@ func (p *Path) Quad(ctrl, to f32.Point) { // QuadTo records a quadratic Bézier from the pen to end // with the control point ctrl, with absolute coordinates. func (p *Path) QuadTo(ctrl, to f32.Point) { - data := p.ops.Write(ops.QuadSize + 4) + data := p.ops.Write(scene.CommandSize + 4) bo := binary.LittleEndian bo.PutUint32(data[0:], uint32(p.contour)) encodeCommand(data[4:], scene.Quad(p.pen, ctrl, to, false)) @@ -395,5 +394,5 @@ func (o Outline) Op() Op { } func encodeCommand(out []byte, cmd scene.Command) { - copy(out, byteslice.Slice(cmd[:])) + copy(out, byteslice.Uint32(cmd[:])) }