gpu: [compute] speed up path comparisons with op keys

To re-use previously cached layers, the compute renderer must know
whether two drawing operations are equal. In the case two operations are
not equal, a fast hash comparison will most likely fail. In the case two
equal operations with complicated clipping paths, the comparison of the
path data is expensive.

This change adds support for fast ops.Key comparisons, where two paths
are equal if their ops.Key are. This is an optimization that kicks in
for text rendering, where glyph clipping shapes are re-used across
frames.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-07-19 17:00:06 +02:00
parent 4ab872e36a
commit 88fb798cca
+28 -20
View File
@@ -182,7 +182,8 @@ type clipCmd struct {
// union of the bounds of the operations that are clipped. // union of the bounds of the operations that are clipped.
union f32.Rectangle union f32.Rectangle
state clipKey state clipKey
pathVerts []byte path []byte
pathKey ops.Key
absBounds f32.Rectangle absBounds f32.Rectangle
} }
@@ -222,7 +223,8 @@ type paintKey struct {
type clipState struct { type clipState struct {
absBounds f32.Rectangle absBounds f32.Rectangle
parent *clipState parent *clipState
pathVerts []byte path []byte
pathKey ops.Key
clipKey clipKey
} }
@@ -1319,7 +1321,7 @@ func (c *opsCollector) reset() {
c.layers = c.layers[:0] c.layers = c.layers[:0]
} }
func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle, path []byte, stroke clip.StrokeStyle) { func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle, path []byte, key ops.Key, stroke clip.StrokeStyle) {
// Rectangle clip regions. // Rectangle clip regions.
if len(path) == 0 { if len(path) == 0 {
// If the rectangular clip region contains a previous path it can be discarded. // If the rectangular clip region contains a previous path it can be discarded.
@@ -1340,7 +1342,8 @@ func (c *collector) addClip(state *encoderState, viewport, bounds f32.Rectangle,
c.clipStates = append(c.clipStates, clipState{ c.clipStates = append(c.clipStates, clipState{
parent: state.clip, parent: state.clip,
absBounds: absBounds, absBounds: absBounds,
pathVerts: path, path: path,
pathKey: key,
clipKey: clipKey{ clipKey: clipKey{
bounds: bounds, bounds: bounds,
relTrans: state.relTrans, relTrans: state.relTrans,
@@ -1363,11 +1366,14 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) {
} }
r := &c.reader r := &c.reader
var ( var (
pathData []byte pathData struct {
str clip.StrokeStyle data []byte
key ops.Key
}
str clip.StrokeStyle
) )
c.save(opconst.InitialStateID, state) c.save(opconst.InitialStateID, state)
c.addClip(&state, fview, fview, nil, clip.StrokeStyle{}) c.addClip(&state, fview, fview, nil, ops.Key{}, clip.StrokeStyle{})
for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() {
switch opconst.OpType(encOp.Data[0]) { switch opconst.OpType(encOp.Data[0]) {
case opconst.TypeProfile: case opconst.TypeProfile:
@@ -1383,12 +1389,13 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) {
if !ok { if !ok {
panic("unexpected end of path operation") panic("unexpected end of path operation")
} }
pathData = encOp.Data[opconst.TypeAuxLen:] pathData.data = encOp.Data[opconst.TypeAuxLen:]
pathData.key = encOp.Key
case opconst.TypeClip: case opconst.TypeClip:
var op clipOp var op clipOp
op.decode(encOp.Data) op.decode(encOp.Data)
c.addClip(&state, fview, op.bounds, pathData, str) c.addClip(&state, fview, op.bounds, pathData.data, pathData.key, str)
pathData = nil pathData.data = nil
str = clip.StrokeStyle{} str = clip.StrokeStyle{}
case opconst.TypeColor: case opconst.TypeColor:
state.matType = materialColor state.matType = materialColor
@@ -1408,7 +1415,7 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) {
if paintState.matType == materialTexture { if paintState.matType == materialTexture {
// Clip to the bounds of the image, to hide other images in the atlas. // Clip to the bounds of the image, to hide other images in the atlas.
bounds := paintState.image.src.Bounds() bounds := paintState.image.src.Bounds()
c.addClip(&paintState, fview, layout.FRect(bounds), nil, clip.StrokeStyle{}) c.addClip(&paintState, fview, layout.FRect(bounds), nil, ops.Key{}, clip.StrokeStyle{})
} }
if paintState.intersect.Empty() { if paintState.intersect.Empty() {
break break
@@ -1429,12 +1436,13 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) {
startIdx := len(c.frame.clipCmds) startIdx := len(c.frame.clipCmds)
for p != nil { for p != nil {
idx := len(c.frame.paths) idx := len(c.frame.paths)
c.frame.paths = append(c.frame.paths, make([]byte, len(p.pathVerts))...) c.frame.paths = append(c.frame.paths, make([]byte, len(p.path))...)
path := c.frame.paths[idx:] path := c.frame.paths[idx:]
copy(path, p.pathVerts) copy(path, p.path)
c.frame.clipCmds = append(c.frame.clipCmds, clipCmd{ c.frame.clipCmds = append(c.frame.clipCmds, clipCmd{
state: p.clipKey, state: p.clipKey,
pathVerts: path, path: path,
pathKey: p.pathKey,
absBounds: p.absBounds, absBounds: p.absBounds,
}) })
p = p.parent p = p.parent
@@ -1471,7 +1479,7 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) {
for k := j + 1; k < len(op.clipStack); k++ { for k := j + 1; k < len(op.clipStack); k++ {
cl2 := op.clipStack[k] cl2 := op.clipStack[k]
p2 := cl2.state p2 := cl2.state
if len(cl2.pathVerts) == 0 && r.In(cl2.state.bounds) { if len(cl2.path) == 0 && r.In(cl2.state.bounds) {
op.clipStack = append(op.clipStack[:k], op.clipStack[k+1:]...) op.clipStack = append(op.clipStack[:k], op.clipStack[k+1:]...)
k-- k--
op.clipStack[k].state.relTrans = p2.relTrans.Mul(op.clipStack[k].state.relTrans) op.clipStack[k].state.relTrans = p2.relTrans.Mul(op.clipStack[k].state.relTrans)
@@ -1496,7 +1504,7 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) {
func (c *collector) hashOp(op paintOp) uint64 { func (c *collector) hashOp(op paintOp) uint64 {
c.hasher.Reset() c.hasher.Reset()
for _, cl := range op.clipStack { for _, cl := range op.clipStack {
c.hasher.Write(cl.pathVerts) c.hasher.Write(cl.path)
k := cl.state k := cl.state
keyBytes := (*[unsafe.Sizeof(k)]byte)(unsafe.Pointer(unsafe.Pointer(&k))) keyBytes := (*[unsafe.Sizeof(k)]byte)(unsafe.Pointer(unsafe.Pointer(&k)))
c.hasher.Write(keyBytes[:]) c.hasher.Write(keyBytes[:])
@@ -1627,13 +1635,13 @@ func opEqual(off image.Point, o1 paintOp, o2 paintOp) bool {
} }
for i, cl1 := range o1.clipStack { for i, cl1 := range o1.clipStack {
cl2 := o2.clipStack[i] cl2 := o2.clipStack[i]
if len(cl1.pathVerts) != len(cl2.pathVerts) { if len(cl1.path) != len(cl2.path) {
return false return false
} }
if cl1.state != cl2.state { if cl1.state != cl2.state {
return false return false
} }
if !bytes.Equal(cl1.pathVerts, cl2.pathVerts) { if cl1.pathKey != cl2.pathKey && !bytes.Equal(cl1.path, cl2.path) {
return false return false
} }
} }
@@ -1676,10 +1684,10 @@ func encodeOp(viewport image.Point, absOff f32.Point, enc *encoder, texOps *[]te
} }
enc.transform(cl.state.relTrans) enc.transform(cl.state.relTrans)
inv = inv.Mul(cl.state.relTrans) inv = inv.Mul(cl.state.relTrans)
if len(cl.pathVerts) == 0 { if len(cl.path) == 0 {
enc.rect(cl.state.bounds) enc.rect(cl.state.bounds)
} else { } else {
enc.encodePath(cl.pathVerts) enc.encodePath(cl.path)
} }
if i != 0 { if i != 0 {
enc.beginClip(cl.union.Add(absOff)) enc.beginClip(cl.union.Add(absOff))