From 1e38eec0abc1198e20d556f9f21345bf0114b211 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sun, 2 Jun 2019 16:43:28 +0200 Subject: [PATCH] ui: build paths as ops Instead of allocating and constructing a clip path, store path data directly in op lists. Use separate op lists for cached text layout paths. Signed-off-by: Elias Naur --- ui/app/internal/gpu/gpu.go | 81 ++++++++++++++------- ui/app/internal/gpu/path.go | 6 +- ui/app/window.go | 6 +- ui/draw/draw.go | 11 +-- ui/draw/path.go | 138 ++++++++++++++++++------------------ ui/internal/ops/ops.go | 9 ++- ui/internal/path/path.go | 7 -- ui/key/queue.go | 6 +- ui/layout/list.go | 2 +- ui/measure/measure.go | 21 +++--- ui/ops.go | 97 +++++++++++++++++++++---- ui/pointer/queue.go | 8 +-- ui/text/editor.go | 9 +-- ui/text/label.go | 3 +- ui/text/measure.go | 4 +- 15 files changed, 248 insertions(+), 160 deletions(-) diff --git a/ui/app/internal/gpu/gpu.go b/ui/app/internal/gpu/gpu.go index 9f2a854c..10073258 100644 --- a/ui/app/internal/gpu/gpu.go +++ b/ui/app/internal/gpu/gpu.go @@ -3,6 +3,7 @@ package gpu import ( + "encoding/binary" "fmt" "image" "image/color" @@ -16,7 +17,6 @@ import ( gdraw "gioui.org/ui/draw" "gioui.org/ui/f32" "gioui.org/ui/internal/ops" - "gioui.org/ui/internal/path" "golang.org/x/image/draw" ) @@ -87,10 +87,11 @@ type pathOp struct { off f32.Point // clip is the union of all // later clip rectangles. - clip image.Rectangle - path *path.Path - parent *pathOp - place placement + clip image.Rectangle + pathKey ui.OpKey + pathVerts []byte + parent *pathOp + place placement } type imageOp struct { @@ -114,6 +115,31 @@ type material struct { uvOffset f32.Point } +// opClip structure must match opClip in package ui/draw. +type opClip struct { + bounds f32.Rectangle +} + +func (op *opClip) decode(data []byte) { + if ops.OpType(data[0]) != ops.TypeClip { + panic("invalid op") + } + bo := binary.LittleEndian + r := f32.Rectangle{ + Min: f32.Point{ + X: math.Float32frombits(bo.Uint32(data[1:])), + Y: math.Float32frombits(bo.Uint32(data[5:])), + }, + Max: f32.Point{ + X: math.Float32frombits(bo.Uint32(data[9:])), + Y: math.Float32frombits(bo.Uint32(data[13:])), + }, + } + *op = opClip{ + bounds: r, + } +} + type clipType uint8 type resourceCache struct { @@ -485,10 +511,10 @@ func (r *renderer) stencilClips(cache *resourceCache, ops []*pathOp) { bindFramebuffer(r.ctx, f.fbo) r.ctx.Clear(gl.COLOR_BUFFER_BIT) } - data, exists := cache.get(p.path) + data, exists := cache.get(p.pathKey) if !exists { - data = buildPath(r.ctx, p.path) - cache.put(p.path, data) + data = buildPath(r.ctx, p.pathVerts) + cache.put(p.pathKey, data) } r.pather.stencilPath(p.clip, p.off, p.place.Pos, data.(*pathData)) } @@ -528,7 +554,7 @@ func (r *renderer) intersectPath(p *pathOp, clip image.Rectangle) { if p.parent != nil { r.intersectPath(p.parent, clip) } - if p.path == nil { + if len(p.pathVerts) == 0 { return } o := p.place.Pos.Add(clip.Min).Sub(p.clip.Min) @@ -550,7 +576,7 @@ func (r *renderer) packIntersections(ops []imageOp) { var npaths int var onePath *pathOp for p := img.path; p != nil; p = p.parent { - if p.path != nil { + if len(p.pathVerts) > 0 { onePath = p npaths++ } @@ -665,27 +691,27 @@ func (d *drawOps) newPathOp() *pathOp { } func (d *drawOps) collectOps(r *ui.OpsReader, state drawState) int { + var aux []byte + var auxKey ui.OpKey loop: for { - data, refs, ok := r.Decode() + encOp, ok := r.Decode() if !ok { break } - switch ops.OpType(data[0]) { + switch ops.OpType(encOp.Data[0]) { case ops.TypeTransform: var op ui.OpTransform - op.Decode(data) + op.Decode(encOp.Data) state.t = state.t.Mul(op.Transform) + case ops.TypeAux: + aux = encOp.Data[ops.TypeAuxLen:] + auxKey = encOp.Key case ops.TypeClip: - var op gdraw.OpClip - op.Decode(data, refs) - if op.Path == nil { - state.clip = f32.Rectangle{} - continue - } - data := op.Path.Data().(*path.Path) + var op opClip + op.decode(encOp.Data) off := state.t.Transform(f32.Point{}) - state.clip = state.clip.Intersect(data.Bounds.Add(off)) + state.clip = state.clip.Intersect(op.bounds.Add(off)) if state.clip.Empty() { continue } @@ -695,24 +721,27 @@ loop: off: off, } state.cpath = npath - if len(data.Vertices) > 0 { + if len(aux) > 0 { state.rect = false - state.cpath.path = data + state.cpath.pathKey = auxKey + state.cpath.pathVerts = aux d.pathOps = append(d.pathOps, state.cpath) } + aux = nil + auxKey = ui.OpKey{} case ops.TypeColor: var op gdraw.OpColor - op.Decode(data, refs) + op.Decode(encOp.Data, encOp.Refs) state.img = nil state.color = op.Col case ops.TypeImage: var op gdraw.OpImage - op.Decode(data, refs) + op.Decode(encOp.Data, encOp.Refs) state.img = op.Img state.imgRect = op.Rect case ops.TypeDraw: var op gdraw.OpDraw - op.Decode(data, refs) + op.Decode(encOp.Data, encOp.Refs) off := state.t.Transform(f32.Point{}) clip := state.clip.Intersect(op.Rect.Add(off)) if clip.Empty() { diff --git a/ui/app/internal/gpu/path.go b/ui/app/internal/gpu/path.go index 380511e7..94f7ed44 100644 --- a/ui/app/internal/gpu/path.go +++ b/ui/app/internal/gpu/path.go @@ -221,12 +221,12 @@ func (c *coverer) release() { } } -func buildPath(ctx *context, p *path.Path) *pathData { +func buildPath(ctx *context, p []byte) *pathData { buf := ctx.CreateBuffer() ctx.BindBuffer(gl.ARRAY_BUFFER, buf) - ctx.BufferData(gl.ARRAY_BUFFER, gl.BytesView(p.Vertices), gl.STATIC_DRAW) + ctx.BufferData(gl.ARRAY_BUFFER, p, gl.STATIC_DRAW) return &pathData{ - ncurves: len(p.Vertices), + ncurves: len(p) / path.VertStride, data: buf, } } diff --git a/ui/app/window.go b/ui/app/window.go index 74c0f152..88a7e52f 100644 --- a/ui/app/window.go +++ b/ui/app/window.go @@ -156,14 +156,14 @@ func collectRedraws(r *ui.OpsReader) (time.Time, bool) { var t time.Time redraw := false for { - data, _, ok := r.Decode() + encOp, ok := r.Decode() if !ok { break } - switch ops.OpType(data[0]) { + switch ops.OpType(encOp.Data[0]) { case ops.TypeRedraw: var op ui.OpRedraw - op.Decode(data) + op.Decode(encOp.Data) if !redraw || op.At.Before(t) { redraw = true t = op.At diff --git a/ui/draw/draw.go b/ui/draw/draw.go index 92690d21..d1bc7e7d 100644 --- a/ui/draw/draw.go +++ b/ui/draw/draw.go @@ -11,7 +11,6 @@ import ( "gioui.org/ui" "gioui.org/ui/f32" "gioui.org/ui/internal/ops" - "gioui.org/ui/internal/path" ) type OpImage struct { @@ -114,14 +113,10 @@ func (d *OpDraw) Decode(data []byte, refs []interface{}) { } } -// RectPath constructs a path corresponding to +// RectClip append a clip op corresponding to // a pixel aligned rectangular area. -func RectPath(r image.Rectangle) *Path { - return &Path{ - data: &path.Path{ - Bounds: toRectF(r), - }, - } +func RectClip(ops *ui.Ops, r image.Rectangle) { + opClip{bounds: toRectF(r)}.Add(ops) } func itof(i int) float32 { diff --git a/ui/draw/path.go b/ui/draw/path.go index dbbb7812..7b107160 100644 --- a/ui/draw/path.go +++ b/ui/draw/path.go @@ -3,7 +3,9 @@ package draw import ( + "encoding/binary" "math" + "unsafe" "gioui.org/ui" "gioui.org/ui/f32" @@ -11,81 +13,71 @@ import ( "gioui.org/ui/internal/path" ) -type OpClip struct { - Path *Path -} - -type Path struct { - data *path.Path -} - type PathBuilder struct { - verts []path.Vertex firstVert int + nverts int maxy float32 pen f32.Point bounds f32.Rectangle hasBounds bool } -// Data is for internal use only. -func (p *Path) Data() interface{} { - return p.data +// opClip structure must match opClip in package ui/internal/gpu. +type opClip struct { + bounds f32.Rectangle } -func (c OpClip) Add(o *ui.Ops) { +func (p opClip) Add(o *ui.Ops) { data := make([]byte, ops.TypeClipLen) data[0] = byte(ops.TypeClip) - o.Write(data, []interface{}{c.Path}) -} - -func (c *OpClip) Decode(d []byte, refs []interface{}) { - if ops.OpType(d[0]) != ops.TypeClip { - panic("invalid op") - } - *c = OpClip{ - Path: refs[0].(*Path), - } + bo := binary.LittleEndian + bo.PutUint32(data[1:], math.Float32bits(p.bounds.Min.X)) + bo.PutUint32(data[5:], math.Float32bits(p.bounds.Min.Y)) + bo.PutUint32(data[9:], math.Float32bits(p.bounds.Max.X)) + bo.PutUint32(data[13:], math.Float32bits(p.bounds.Max.Y)) + o.Write(data, nil) } // MoveTo moves the pen to the given position. -func (p *PathBuilder) Move(to f32.Point) { - p.end() +func (p *PathBuilder) Move(ops *ui.Ops, to f32.Point) { + p.end(ops) to = to.Add(p.pen) p.maxy = to.Y p.pen = to } // end completes the current contour. -func (p *PathBuilder) end() { - // Fill in maximal Y coordinates of the NW and NE corners - // and offset their curve coordinates. - for i := p.firstVert; i < len(p.verts); i++ { - p.verts[i].MaxY = p.maxy +func (p *PathBuilder) end(ops *ui.Ops) { + aux := ops.Aux() + bo := binary.LittleEndian + // Fill in maximal Y coordinates of the NW and NE corners. + for i := p.firstVert; i < p.nverts; i++ { + off := path.VertStride*i + int(unsafe.Offsetof(((*path.Vertex)(nil)).MaxY)) + bo.PutUint32(aux[off:], math.Float32bits(p.maxy)) } - p.firstVert = len(p.verts) + p.firstVert = p.nverts } // Line records a line from the pen to end. -func (p *PathBuilder) Line(to f32.Point) { +func (p *PathBuilder) Line(ops *ui.Ops, to f32.Point) { to = to.Add(p.pen) - p.lineTo(to) + p.lineTo(ops, to) } -func (p *PathBuilder) lineTo(to f32.Point) { +func (p *PathBuilder) lineTo(ops *ui.Ops, to f32.Point) { // Model lines as degenerate quadratic beziers. - p.quadTo(to.Add(p.pen).Mul(.5), to) + p.quadTo(ops, to.Add(p.pen).Mul(.5), to) } // Quad records a quadratic bezier from the pen to end // with the control point ctrl. -func (p *PathBuilder) Quad(ctrl, to f32.Point) { +func (p *PathBuilder) Quad(ops *ui.Ops, ctrl, to f32.Point) { ctrl = ctrl.Add(p.pen) to = to.Add(p.pen) - p.quadTo(ctrl, to) + p.quadTo(ops, ctrl, to) } -func (p *PathBuilder) quadTo(ctrl, to f32.Point) { +func (p *PathBuilder) quadTo(ops *ui.Ops, ctrl, to f32.Point) { // Zero width curves don't contribute to stenciling. if p.pen.X == to.X && p.pen.X == ctrl.X { p.pen = to @@ -112,8 +104,8 @@ func (p *PathBuilder) quadTo(ctrl, to f32.Point) { ctrl0 := p.pen.Mul(1 - t).Add(ctrl.Mul(t)) ctrl1 := ctrl.Mul(1 - t).Add(to.Mul(t)) mid := ctrl0.Mul(1 - t).Add(ctrl1.Mul(t)) - p.simpleQuadTo(ctrl0, mid) - p.simpleQuadTo(ctrl1, to) + p.simpleQuadTo(ops, ctrl0, mid) + p.simpleQuadTo(ops, ctrl1, to) if mid.X > bounds.Max.X { bounds.Max.X = mid.X } @@ -121,7 +113,7 @@ func (p *PathBuilder) quadTo(ctrl, to f32.Point) { bounds.Min.X = mid.X } } else { - p.simpleQuadTo(ctrl, to) + p.simpleQuadTo(ops, ctrl, to) } // Find the y extremum, if any. d = v0.Y - v1.Y @@ -140,7 +132,7 @@ func (p *PathBuilder) quadTo(ctrl, to f32.Point) { // Cube records a cubic bezier from the pen through // two control points ending in to. -func (p *PathBuilder) Cube(ctrl0, ctrl1, to f32.Point) { +func (p *PathBuilder) Cube(ops *ui.Ops, ctrl0, ctrl1, to f32.Point) { ctrl0 = ctrl0.Add(p.pen) ctrl1 = ctrl1.Add(p.pen) to = to.Add(p.pen) @@ -154,12 +146,12 @@ func (p *PathBuilder) Cube(ctrl0, ctrl1, to f32.Point) { if h := hull.Dy(); h > l { l = h } - p.approxCubeTo(0, l*0.001, ctrl0, ctrl1, to) + p.approxCubeTo(ops, 0, l*0.001, ctrl0, ctrl1, to) } // approxCube approximates a cubic beziér by a series of quadratic // curves. -func (p *PathBuilder) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to f32.Point) int { +func (p *PathBuilder) approxCubeTo(ops *ui.Ops, splits int, maxDist float32, ctrl0, ctrl1, to f32.Point) int { // The idea is from // https://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html // where a quadratic approximates a cubic by eliminating its t³ term @@ -171,7 +163,7 @@ func (p *PathBuilder) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to // // C1 = (3ctrl0 - pen)/2 // - // The reverse cubic that is anchored at the end point has the polynomial + // The reverse cubic anchored at the end point has the polynomial // // P'(t) = to + 3t(ctrl1 - to) + 3t²(ctrl0 - 2ctrl1 + to) + t³(pen - 3ctrl0 + 3ctrl1 - to) // @@ -187,7 +179,7 @@ func (p *PathBuilder) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to c := ctrl0.Mul(3).Sub(p.pen).Add(ctrl1.Mul(3)).Sub(to).Mul(1.0 / 4.0) const maxSplits = 32 if splits >= maxSplits { - p.quadTo(c, to) + p.quadTo(ops, c, to) return splits } // The maximum distance between the cubic P and its approximation Q given t @@ -199,7 +191,7 @@ func (p *PathBuilder) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to v := to.Sub(ctrl1.Mul(3)).Add(ctrl0.Mul(3)).Sub(p.pen) d2 := (v.X*v.X + v.Y*v.Y) * 3 / (36 * 36) if d2 <= maxDist*maxDist { - p.quadTo(c, to) + p.quadTo(ops, c, to) return splits } // De Casteljau split the curve and approximate the halves. @@ -211,8 +203,8 @@ func (p *PathBuilder) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to c12 := c1.Add(c2.Sub(c1).Mul(t)) c0112 := c01.Add(c12.Sub(c01).Mul(t)) splits++ - splits = p.approxCubeTo(splits, maxDist, c0, c01, c0112) - splits = p.approxCubeTo(splits, maxDist, c12, c2, to) + splits = p.approxCubeTo(ops, splits, maxDist, c0, c01, c0112) + splits = p.approxCubeTo(ops, splits, maxDist, c12, c2, to) return splits } @@ -228,8 +220,9 @@ func (p *PathBuilder) expand(b f32.Rectangle) { p.bounds = p.bounds.Union(b) } -func (p *PathBuilder) vertex(cornerx, cornery int16, ctrl, to f32.Point) { - p.verts = append(p.verts, path.Vertex{ +func (p *PathBuilder) vertex(o *ui.Ops, cornerx, cornery int16, ctrl, to f32.Point) { + p.nverts++ + v := path.Vertex{ CornerX: cornerx, CornerY: cornery, FromX: p.pen.X, @@ -238,10 +231,25 @@ func (p *PathBuilder) vertex(cornerx, cornery int16, ctrl, to f32.Point) { CtrlY: ctrl.Y, ToX: to.X, ToY: to.Y, - }) + } + data := make([]byte, path.VertStride+1) + data[0] = byte(ops.TypeAux) + bo := binary.LittleEndian + data[1] = byte(uint16(v.CornerX)) + data[2] = byte(uint16(v.CornerX) >> 8) + data[3] = byte(uint16(v.CornerY)) + data[4] = byte(uint16(v.CornerY) >> 8) + bo.PutUint32(data[5:], math.Float32bits(v.MaxY)) + bo.PutUint32(data[9:], math.Float32bits(v.FromX)) + bo.PutUint32(data[13:], math.Float32bits(v.FromY)) + bo.PutUint32(data[17:], math.Float32bits(v.CtrlX)) + bo.PutUint32(data[21:], math.Float32bits(v.CtrlY)) + bo.PutUint32(data[25:], math.Float32bits(v.ToX)) + bo.PutUint32(data[29:], math.Float32bits(v.ToY)) + o.Write(data, nil) } -func (p *PathBuilder) simpleQuadTo(ctrl, to f32.Point) { +func (p *PathBuilder) simpleQuadTo(ops *ui.Ops, ctrl, to f32.Point) { if p.pen.Y > p.maxy { p.maxy = p.pen.Y } @@ -252,25 +260,19 @@ func (p *PathBuilder) simpleQuadTo(ctrl, to f32.Point) { p.maxy = to.Y } // NW. - p.vertex(-1, 1, ctrl, to) + p.vertex(ops, -1, 1, ctrl, to) // NE. - p.vertex(1, 1, ctrl, to) + p.vertex(ops, 1, 1, ctrl, to) // SW. - p.vertex(-1, -1, ctrl, to) + p.vertex(ops, -1, -1, ctrl, to) // SE. - p.vertex(1, -1, ctrl, to) + p.vertex(ops, 1, -1, ctrl, to) p.pen = to } -func (p *PathBuilder) Path() *Path { - p.end() - data := &Path{ - data: &path.Path{ - Bounds: p.bounds, - }, - } - if !p.bounds.Empty() { - data.data.Vertices = p.verts - } - return data +func (p *PathBuilder) End(ops *ui.Ops) { + p.end(ops) + opClip{ + bounds: p.bounds, + }.Add(ops) } diff --git a/ui/internal/ops/ops.go b/ui/internal/ops/ops.go index d3843a3f..a5c59122 100644 --- a/ui/internal/ops/ops.go +++ b/ui/internal/ops/ops.go @@ -11,7 +11,6 @@ const ( TypeTransform TypeLayer TypeRedraw - TypeClip TypeImage TypeDraw TypeColor @@ -20,6 +19,8 @@ const ( TypeHideInput TypePush TypePop + TypeAux + TypeClip ) const ( @@ -28,7 +29,6 @@ const ( TypeTransformLen = 1 + 4*2 TypeLayerLen = 1 TypeRedrawLen = 1 + 8 - TypeClipLen = 1 TypeImageLen = 1 + 4*4 TypeDrawLen = 1 + 4*4 TypeColorLen = 1 + 4 @@ -37,13 +37,14 @@ const ( TypeHideInputLen = 1 TypePushLen = 1 TypePopLen = 1 + TypeAuxLen = 1 + 4 + TypeClipLen = 1 + 4*4 TypeBlockDefRefs = 0 TypeBlockRefs = 1 TypeTransformRefs = 0 TypeLayerRefs = 0 TypeRedrawRefs = 0 - TypeClipRefs = 1 TypeImageRefs = 1 TypeDrawRefs = 0 TypeColorRefs = 0 @@ -52,4 +53,6 @@ const ( TypeHideInputRefs = 0 TypePushRefs = 0 TypePopRefs = 0 + TypeAuxRefs = 0 + TypeClipRefs = 0 ) diff --git a/ui/internal/path/path.go b/ui/internal/path/path.go index 62fe731e..e1e5b23e 100644 --- a/ui/internal/path/path.go +++ b/ui/internal/path/path.go @@ -4,15 +4,8 @@ package path import ( "unsafe" - - "gioui.org/ui/f32" ) -type Path struct { - Vertices []Vertex - Bounds f32.Rectangle -} - // The vertex data suitable for passing to vertex programs. type Vertex struct { CornerX, CornerY int16 diff --git a/ui/key/queue.go b/ui/key/queue.go index cd0582f0..e21a379b 100644 --- a/ui/key/queue.go +++ b/ui/key/queue.go @@ -80,14 +80,14 @@ func resolveFocus(r *ui.OpsReader, focus Key) (Key, listenerPriority, bool) { var hide bool loop: for { - data, refs, ok := r.Decode() + encOp, ok := r.Decode() if !ok { break } - switch ops.OpType(data[0]) { + switch ops.OpType(encOp.Data[0]) { case ops.TypeKeyHandler: var op OpHandler - op.Decode(data, refs) + op.Decode(encOp.Data, encOp.Refs) var newPri listenerPriority switch { case op.Focus: diff --git a/ui/layout/list.go b/ui/layout/list.go index 79f2b1d0..e81b7f1f 100644 --- a/ui/layout/list.go +++ b/ui/layout/list.go @@ -179,7 +179,7 @@ func (l *List) Layout(ops *ui.Ops) Dimens { Max: axisPoint(l.Axis, max, ui.Inf), } ui.OpPush{}.Add(ops) - draw.OpClip{Path: draw.RectPath(r)}.Add(ops) + draw.RectClip(ops, r) ui.OpTransform{ Transform: ui.Offset(toPointF(axisPoint(l.Axis, pos, cross))), }.Add(ops) diff --git a/ui/measure/measure.go b/ui/measure/measure.go index 2ff2840e..24da13ec 100644 --- a/ui/measure/measure.go +++ b/ui/measure/measure.go @@ -30,7 +30,7 @@ type cachedLayout struct { type cachedPath struct { active bool - path *draw.Path + path ui.OpBlock } type layoutKey struct { @@ -121,7 +121,7 @@ func (f *textFace) Layout(str string, singleLine bool, maxWidth int) *text.Layou return l } -func (f *textFace) Path(str text.String) *draw.Path { +func (f *textFace) Path(str text.String) ui.OpBlock { ppem := fixed.Int26_6(f.faces.Cfg.Val(f.size)*64 + .5) pk := pathKey{ f: f.font.Font, @@ -229,11 +229,13 @@ func layoutText(ppem fixed.Int26_6, str string, f *opentype, singleLine bool, ma return &text.Layout{Lines: lines} } -func textPath(ppem fixed.Int26_6, f *opentype, str text.String) *draw.Path { +func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.OpBlock { var lastPos f32.Point var builder draw.PathBuilder + ops := new(ui.Ops) var x fixed.Int26_6 var advIdx int + ops.Begin() for _, r := range str.String { if !unicode.IsSpace(r) { segs, ok := f.LoadGlyph(ppem, r) @@ -244,7 +246,7 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) *draw.Path { pos := f32.Point{ X: float32(x) / 64, } - builder.Move(pos.Sub(lastPos)) + builder.Move(ops, pos.Sub(lastPos)) lastPos = pos var lastArg f32.Point // Convert sfnt.Segments to relative segments. @@ -269,13 +271,13 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) *draw.Path { } switch fseg.Op { case sfnt.SegmentOpMoveTo: - builder.Move(args[0]) + builder.Move(ops, args[0]) case sfnt.SegmentOpLineTo: - builder.Line(args[0]) + builder.Line(ops, args[0]) case sfnt.SegmentOpQuadTo: - builder.Quad(args[0], args[1]) + builder.Quad(ops, args[0], args[1]) case sfnt.SegmentOpCubeTo: - builder.Cube(args[0], args[1], args[2]) + builder.Cube(ops, args[0], args[1], args[2]) default: panic("unsupported segment op") } @@ -285,5 +287,6 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) *draw.Path { x += str.Advances[advIdx] advIdx++ } - return builder.Path() + builder.End(ops) + return ops.End() } diff --git a/ui/ops.go b/ui/ops.go index 40b84dfa..ebd21184 100644 --- a/ui/ops.go +++ b/ui/ops.go @@ -11,6 +11,10 @@ type Ops struct { // Stack of block start indices. stack []pc ops opsData + + inAux bool + auxOff int + auxLen int } type opsData struct { @@ -21,14 +25,30 @@ type opsData struct { refs []interface{} } +// OpsReader parses an ops list. Internal use only. type OpsReader struct { pc pc stack []block - ops opsData + ops *opsData +} + +// EncodedOp represents an encoded op returned by +// OpsReader. Internal use only. +type EncodedOp struct { + Key OpKey + Data []byte + Refs []interface{} +} + +// OpKey is a unique key for a given op. Internal use only. +type OpKey struct { + ops *opsData + pc int + version int } type block struct { - ops opsData + ops *opsData retPC pc endPC pc } @@ -44,7 +64,6 @@ var typeLengths = [...]int{ ops.TypeTransformLen, ops.TypeLayerLen, ops.TypeRedrawLen, - ops.TypeClipLen, ops.TypeImageLen, ops.TypeDrawLen, ops.TypeColorLen, @@ -53,6 +72,8 @@ var typeLengths = [...]int{ ops.TypeHideInputLen, ops.TypePushLen, ops.TypePopLen, + ops.TypeAuxLen, + ops.TypeClipLen, } var refLengths = [...]int{ @@ -61,7 +82,6 @@ var refLengths = [...]int{ ops.TypeTransformRefs, ops.TypeLayerRefs, ops.TypeRedrawRefs, - ops.TypeClipRefs, ops.TypeImageRefs, ops.TypeDrawRefs, ops.TypeColorRefs, @@ -70,6 +90,8 @@ var refLengths = [...]int{ ops.TypeHideInputRefs, ops.TypePushRefs, ops.TypePopRefs, + ops.TypeAuxRefs, + ops.TypeClipRefs, } type OpPush struct{} @@ -77,7 +99,7 @@ type OpPush struct{} type OpPop struct{} type OpBlock struct { - ops *Ops + ops *opsData version int pc pc } @@ -86,6 +108,10 @@ type opBlockDef struct { endpc pc } +type opAux struct { + len int +} + func (p OpPush) Add(o *Ops) { o.Write([]byte{byte(ops.TypePush)}, nil) } @@ -101,6 +127,16 @@ func (o *Ops) Begin() { o.Write(make([]byte, ops.TypeBlockDefLen), nil) } +func (op *opAux) decode(data []byte) { + if ops.OpType(data[0]) != ops.TypeAux { + panic("invalid op") + } + bo := binary.LittleEndian + *op = opAux{ + len: int(bo.Uint32(data[1:])), + } +} + func (op *opBlockDef) decode(data []byte) { if ops.OpType(data[0]) != ops.TypeBlockDef { panic("invalid op") @@ -128,15 +164,24 @@ func (o *Ops) End() OpBlock { bo := binary.LittleEndian bo.PutUint32(data[1:], uint32(pc.data)) bo.PutUint32(data[5:], uint32(pc.refs)) - return OpBlock{ops: o, pc: start, version: o.ops.version} + return OpBlock{ops: &o.ops, pc: start, version: o.ops.version} } // Reset clears the Ops. func (o *Ops) Reset() { + o.inAux = false o.stack = o.stack[:0] o.ops.reset() } +// Internal use only. +func (o *Ops) Aux() []byte { + if !o.inAux { + return nil + } + return o.ops.data[o.auxOff+ops.TypeAuxLen : o.auxOff+ops.TypeAuxLen+o.auxLen] +} + func (d *opsData) reset() { d.data = d.data[:0] d.refs = d.refs[:0] @@ -149,6 +194,26 @@ func (d *opsData) write(op []byte, refs []interface{}) { } func (o *Ops) Write(op []byte, refs []interface{}) { + switch ops.OpType(op[0]) { + case ops.TypeAux: + // Write only the data. + op = op[1:] + if !o.inAux { + o.inAux = true + o.auxOff = o.ops.pc().data + o.auxLen = 0 + header := make([]byte, ops.TypeAuxLen) + header[0] = byte(ops.TypeAux) + o.ops.write(header, nil) + } + o.auxLen += len(op) + default: + if o.inAux { + o.inAux = false + bo := binary.LittleEndian + bo.PutUint32(o.ops.data[o.auxOff+1:], uint32(o.auxLen)) + } + } o.ops.write(op, refs) } @@ -165,7 +230,7 @@ func (b *OpBlock) decode(data []byte, refs []interface{}) { refsIdx := int(bo.Uint32(data[5:])) version := int(bo.Uint32(data[9:])) *b = OpBlock{ - ops: refs[0].(*Ops), + ops: refs[0].(*opsData), pc: pc{ data: dataIdx, refs: refsIdx, @@ -186,12 +251,12 @@ func (b OpBlock) Add(o *Ops) { // Reset start reading from the op list. func (r *OpsReader) Reset(ops *Ops) { - r.ops = ops.ops + r.ops = &ops.ops r.stack = r.stack[:0] r.pc = pc{} } -func (r *OpsReader) Decode() ([]byte, []interface{}, bool) { +func (r *OpsReader) Decode() (EncodedOp, bool) { for { if len(r.stack) > 0 { b := r.stack[len(r.stack)-1] @@ -203,22 +268,28 @@ func (r *OpsReader) Decode() ([]byte, []interface{}, bool) { } } if r.pc.data == len(r.ops.data) { - return nil, nil, false + return EncodedOp{}, false } + key := OpKey{ops: r.ops, pc: r.pc.data, version: r.ops.version} t := ops.OpType(r.ops.data[r.pc.data]) n := typeLengths[t-ops.FirstOpIndex] nrefs := refLengths[t-ops.FirstOpIndex] data := r.ops.data[r.pc.data : r.pc.data+n] refs := r.ops.refs[r.pc.refs : r.pc.refs+nrefs] switch t { + case ops.TypeAux: + var op opAux + op.decode(data) + n += op.len + data = r.ops.data[r.pc.data : r.pc.data+n] case ops.TypeBlock: var op OpBlock op.decode(data, refs) - blockOps := op.ops.ops + blockOps := op.ops if ops.OpType(blockOps.data[op.pc.data]) != ops.TypeBlockDef { panic("invalid block reference") } - if op.version != r.ops.version { + if op.version != op.ops.version { panic("invalid OpBlock reference to reset Ops") } var opDef opBlockDef @@ -244,6 +315,6 @@ func (r *OpsReader) Decode() ([]byte, []interface{}, bool) { } r.pc.data += n r.pc.refs += nrefs - return data, refs, true + return EncodedOp{Key: key, Data: data, Refs: refs}, true } } diff --git a/ui/pointer/queue.go b/ui/pointer/queue.go index 2299bf27..d24ee7c7 100644 --- a/ui/pointer/queue.go +++ b/ui/pointer/queue.go @@ -39,11 +39,11 @@ type handler struct { func (q *Queue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int) { for { - data, refs, ok := r.Decode() + encOp, ok := r.Decode() if !ok { return } - switch ops.OpType(data[0]) { + switch ops.OpType(encOp.Data[0]) { case ops.TypePush: q.collectHandlers(r, t, layer) case ops.TypePop: @@ -53,11 +53,11 @@ func (q *Queue) collectHandlers(r *ui.OpsReader, t ui.Transform, layer int) { q.hitTree = append(q.hitTree, hitNode{level: layer}) case ops.TypeTransform: var op ui.OpTransform - op.Decode(data) + op.Decode(encOp.Data) t = t.Mul(op.Transform) case ops.TypePointerHandler: var op OpHandler - op.Decode(data, refs) + op.Decode(encOp.Data, encOp.Refs) q.hitTree = append(q.hitTree, hitNode{level: layer, key: op.Key}) h, ok := q.handlers[op.Key] if !ok { diff --git a/ui/text/editor.go b/ui/text/editor.go index ac205a0b..d89c003e 100644 --- a/ui/text/editor.go +++ b/ui/text/editor.go @@ -10,7 +10,6 @@ import ( "gioui.org/ui" "gioui.org/ui/draw" - "gioui.org/ui/f32" "gioui.org/ui/gesture" "gioui.org/ui/key" "gioui.org/ui/layout" @@ -49,11 +48,6 @@ type Editor struct { clicker gesture.Click } -type linePath struct { - path *draw.Path - off f32.Point -} - const ( blinksPerSecond = 1 maxBlinkDuration = 10 * time.Second @@ -170,10 +164,9 @@ func (e *Editor) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens { if !ok { break } - path := e.Face.Path(str) ui.OpPush{}.Add(ops) ui.OpTransform{Transform: ui.Offset(lineOff)}.Add(ops) - draw.OpClip{Path: path}.Add(ops) + e.Face.Path(str).Add(ops) draw.OpDraw{Rect: toRectF(clip).Sub(lineOff)}.Add(ops) ui.OpPop{}.Add(ops) } diff --git a/ui/text/label.go b/ui/text/label.go index 8d483e52..dfbc5351 100644 --- a/ui/text/label.go +++ b/ui/text/label.go @@ -100,11 +100,10 @@ func (l Label) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens { if !ok { break } - path := l.Face.Path(str) lclip := toRectF(clip).Sub(off) ui.OpPush{}.Add(ops) ui.OpTransform{Transform: ui.Offset(off)}.Add(ops) - draw.OpClip{Path: path}.Add(ops) + l.Face.Path(str).Add(ops) draw.OpDraw{Rect: lclip}.Add(ops) ui.OpPop{}.Add(ops) } diff --git a/ui/text/measure.go b/ui/text/measure.go index 9e8863dc..024442de 100644 --- a/ui/text/measure.go +++ b/ui/text/measure.go @@ -6,7 +6,7 @@ import ( "fmt" "image" - "gioui.org/ui/draw" + "gioui.org/ui" "gioui.org/ui/layout" "golang.org/x/image/math/fixed" ) @@ -35,7 +35,7 @@ type Layout struct { type Face interface { Layout(str string, singleLine bool, maxWidth int) *Layout - Path(str String) *draw.Path + Path(str String) ui.OpBlock } type Alignment uint8