diff --git a/gpu/compute.go b/gpu/compute.go index de3be0f6..a80c217c 100644 --- a/gpu/compute.go +++ b/gpu/compute.go @@ -201,6 +201,7 @@ type clipKey struct { bounds f32.Rectangle stroke clip.StrokeStyle relTrans f32.Affine2D + pathHash uint64 } // paintKey completely defines a paint operation. It is suitable for hashing and @@ -1321,7 +1322,7 @@ func (c *opsCollector) reset() { c.layers = c.layers[:0] } -func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle, path []byte, key ops.Key, stroke clip.StrokeStyle) { +func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle, path []byte, key ops.Key, hash uint64, stroke clip.StrokeStyle) { // Rectangle clip regions. if len(path) == 0 { // If the rectangular clip region contains a previous path it can be discarded. @@ -1348,6 +1349,7 @@ func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle, bounds: bounds, relTrans: state.relTrans, stroke: stroke, + pathHash: hash, }, }) state.intersect = state.intersect.Intersect(absBounds) @@ -1369,11 +1371,12 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) { pathData struct { data []byte key ops.Key + hash uint64 } str clip.StrokeStyle ) c.save(opconst.InitialStateID, state) - c.addClip(&state, fview, fview, nil, ops.Key{}, clip.StrokeStyle{}) + c.addClip(&state, fview, fview, nil, ops.Key{}, 0, clip.StrokeStyle{}) for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { switch opconst.OpType(encOp.Data[0]) { case opconst.TypeProfile: @@ -1385,16 +1388,18 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) { case opconst.TypeStroke: str = decodeStrokeOp(encOp.Data) case opconst.TypePath: + hash := bo.Uint64(encOp.Data[1:]) encOp, ok = r.Decode() if !ok { panic("unexpected end of path operation") } pathData.data = encOp.Data[opconst.TypeAuxLen:] pathData.key = encOp.Key + pathData.hash = hash case opconst.TypeClip: var op clipOp op.decode(encOp.Data) - c.addClip(&state, fview, op.bounds, pathData.data, pathData.key, str) + c.addClip(&state, fview, op.bounds, pathData.data, pathData.key, pathData.hash, str) pathData.data = nil str = clip.StrokeStyle{} case opconst.TypeColor: @@ -1415,7 +1420,7 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) { if paintState.matType == materialTexture { // Clip to the bounds of the image, to hide other images in the atlas. bounds := paintState.image.src.Bounds() - c.addClip(&paintState, fview, layout.FRect(bounds), nil, ops.Key{}, clip.StrokeStyle{}) + c.addClip(&paintState, fview, layout.FRect(bounds), nil, ops.Key{}, 0, clip.StrokeStyle{}) } if paintState.intersect.Empty() { break @@ -1504,7 +1509,6 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) { func (c *collector) hashOp(op paintOp) uint64 { c.hasher.Reset() for _, cl := range op.clipStack { - c.hasher.Write(cl.path) k := cl.state keyBytes := (*[unsafe.Sizeof(k)]byte)(unsafe.Pointer(unsafe.Pointer(&k))) c.hasher.Write(keyBytes[:]) diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go index 13c4ddf2..1fb1d825 100644 --- a/internal/opconst/ops.go +++ b/internal/opconst/ops.go @@ -59,7 +59,7 @@ const ( TypeClipLen = 1 + 4*4 + 1 TypeProfileLen = 1 TypeCursorLen = 1 + 1 - TypePathLen = 1 + TypePathLen = 8 + 1 TypeStrokeLen = 1 + 4 ) diff --git a/op/clip/clip.go b/op/clip/clip.go index f9d094ee..aec3a536 100644 --- a/op/clip/clip.go +++ b/op/clip/clip.go @@ -4,6 +4,7 @@ package clip import ( "encoding/binary" + "hash/maphash" "image" "math" @@ -15,6 +16,12 @@ import ( "gioui.org/op" ) +var pathSeed maphash.Seed + +func init() { + pathSeed = maphash.MakeSeed() +} + // Op represents a clip area. Op intersects the current clip area with // itself. type Op struct { @@ -38,9 +45,11 @@ func (p Op) Add(o *op.Ops) { outline = true } + bo := binary.LittleEndian if path.hasSegments { data := o.Write(opconst.TypePathLen) data[0] = byte(opconst.TypePath) + bo.PutUint64(data[1:], path.hash) path.spec.Add(o) } @@ -60,7 +69,6 @@ func (p Op) Add(o *op.Ops) { data := o.Write(opconst.TypeClipLen) data[0] = byte(opconst.TypeClip) - bo := binary.LittleEndian bo.PutUint32(data[1:], uint32(bounds.Min.X)) bo.PutUint32(data[5:], uint32(bounds.Min.Y)) bo.PutUint32(data[9:], uint32(bounds.Max.X)) @@ -138,6 +146,7 @@ type PathSpec struct { // hasSegments tracks whether there are any segments in the path. hasSegments bool bounds image.Rectangle + hash uint64 } // Path constructs a Op clip path described by lines and @@ -156,6 +165,7 @@ type Path struct { start f32.Point hasSegments bool bounds f32.Rectangle + hash maphash.Hash } // Pos returns the current pen position. @@ -163,6 +173,7 @@ func (p *Path) Pos() f32.Point { return p.pen } // Begin the path, storing the path data and final Op into ops. func (p *Path) Begin(ops *op.Ops) { + p.hash.SetSeed(pathSeed) p.ops = ops p.macro = op.Record(ops) // Write the TypeAux opcode @@ -178,6 +189,7 @@ func (p *Path) End() PathSpec { open: p.open || p.pen != p.start, hasSegments: p.hasSegments, bounds: boundRectF(p.bounds), + hash: p.hash.Sum64(), } } @@ -211,11 +223,16 @@ func (p *Path) LineTo(to f32.Point) { data := p.ops.Write(scene.CommandSize + 4) bo := binary.LittleEndian bo.PutUint32(data[0:], uint32(p.contour)) - ops.EncodeCommand(data[4:], scene.Line(p.pen, to)) + p.cmd(data[4:], scene.Line(p.pen, to)) p.pen = to p.expand(to) } +func (p *Path) cmd(data []byte, c scene.Command) { + ops.EncodeCommand(data, c) + p.hash.Write(data) +} + func (p *Path) expand(pt f32.Point) { if !p.hasSegments { p.hasSegments = true @@ -274,7 +291,7 @@ func (p *Path) QuadTo(ctrl, to f32.Point) { data := p.ops.Write(scene.CommandSize + 4) bo := binary.LittleEndian bo.PutUint32(data[0:], uint32(p.contour)) - ops.EncodeCommand(data[4:], scene.Quad(p.pen, ctrl, to)) + p.cmd(data[4:], scene.Quad(p.pen, ctrl, to)) p.pen = to p.expand(ctrl) p.expand(to) @@ -315,7 +332,7 @@ func (p *Path) CubeTo(ctrl0, ctrl1, to f32.Point) { data := p.ops.Write(scene.CommandSize + 4) bo := binary.LittleEndian bo.PutUint32(data[0:], uint32(p.contour)) - ops.EncodeCommand(data[4:], scene.Cubic(p.pen, ctrl0, ctrl1, to)) + p.cmd(data[4:], scene.Cubic(p.pen, ctrl0, ctrl1, to)) p.pen = to p.expand(ctrl0) p.expand(ctrl1)