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[:])) }