gpu: [compute] re-use layers that differ only in integer offsets

To re-use drawing operations common to two layers, every operation must
exactly match, including their transformations. However, layers that
differ only by an integer offset can be re-used because rendering does
not depend on the absolute integer offset. This is important in the very
common case of scrolling otherwise static UI content.

This change separate the integer offset from drawing operations and
relaxes the layer cache to match layers that differ only in integer
offsets.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-07-19 15:08:34 +02:00
parent 318ddd0644
commit 4ab872e36a
+30 -15
View File
@@ -169,6 +169,7 @@ type opsCollector struct {
type paintOp struct {
clipStack []clipCmd
offset image.Point
state paintKey
intersect f32.Rectangle
hash uint64
@@ -1383,7 +1384,6 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) {
panic("unexpected end of path operation")
}
pathData = encOp.Data[opconst.TypeAuxLen:]
case opconst.TypeClip:
var op clipOp
op.decode(encOp.Data)
@@ -1464,21 +1464,31 @@ func (c *collector) collect(root *op.Ops, viewport image.Point) {
// For each clip, cull rectangular clip regions that contain its
// (transformed) bounds. addClip already handled the converse case.
// TODO: do better than O(n²) to efficiently deal with deep stacks.
for i := 0; i < len(op.clipStack)-1; i++ {
cl := op.clipStack[i]
for j := 0; j < len(op.clipStack)-1; j++ {
cl := op.clipStack[j]
p := cl.state
r := transformBounds(p.relTrans, p.bounds)
for j := i + 1; j < len(op.clipStack); j++ {
cl2 := op.clipStack[j]
for k := j + 1; k < len(op.clipStack); k++ {
cl2 := op.clipStack[k]
p2 := cl2.state
if len(cl2.pathVerts) == 0 && r.In(cl2.state.bounds) {
op.clipStack = append(op.clipStack[:j], op.clipStack[j+1:]...)
j--
op.clipStack[j].state.relTrans = p2.relTrans.Mul(op.clipStack[j].state.relTrans)
op.clipStack = append(op.clipStack[:k], op.clipStack[k+1:]...)
k--
op.clipStack[k].state.relTrans = p2.relTrans.Mul(op.clipStack[k].state.relTrans)
}
r = transformRect(p2.relTrans, r)
}
}
// Separate the integer offset from the first transform. Two ops that differ
// only in integer offsets may share backing storage.
if len(op.clipStack) > 0 {
c := &op.clipStack[len(op.clipStack)-1]
t := c.state.relTrans
t, off := separateTransform(t)
c.state.relTrans = t
op.offset = off
op.state.t = op.state.t.Offset(layout.FPt(off.Mul(-1)))
}
op.hash = c.hashOp(*op)
}
}
@@ -1561,12 +1571,13 @@ outer:
match := prev[first.index:]
// Potential match found. Now find longest matching sequence.
end := 0
layer := -1
layer := match[0].layer
off := match[0].offset.Sub(ops[0].offset)
for end < len(match) && end < len(ops) {
m := match[end]
o := ops[end]
// End on layer boundaries.
if layer != -1 && m.layer != layer {
if m.layer != layer {
break
}
// End layer when the next op doesn't match.
@@ -1578,10 +1589,9 @@ outer:
}
break
}
if !opEqual(m, o) {
if !opEqual(off, m, o) {
break
}
layer = m.layer
end++
}
if end > longest {
@@ -1605,13 +1615,16 @@ func searchOp(order []hashIndex, hash uint64) int {
return lo
}
func opEqual(o1 paintOp, o2 paintOp) bool {
func opEqual(off image.Point, o1 paintOp, o2 paintOp) bool {
if len(o1.clipStack) != len(o2.clipStack) {
return false
}
if o1.state != o2.state {
return false
}
if o1.offset.Sub(o2.offset) != off {
return false
}
for i, cl1 := range o1.clipStack {
cl2 := o2.clipStack[i]
if len(cl1.pathVerts) != len(cl2.pathVerts) {
@@ -1648,7 +1661,9 @@ func encodeOp(viewport image.Point, absOff f32.Point, enc *encoder, texOps *[]te
}
fillMode := scene.FillModeNonzero
var inv f32.Affine2D
opOff := layout.FPt(op.offset)
inv := f32.Affine2D{}.Offset(opOff)
enc.transform(inv)
for i := len(op.clipStack) - 1; i >= 0; i-- {
cl := op.clipStack[i]
if str := cl.state.stroke; str.Width > 0 {
@@ -1681,7 +1696,7 @@ func encodeOp(viewport image.Point, absOff f32.Point, enc *encoder, texOps *[]te
idx := enc.fillImage(0)
// Separate integer offset from transformation. TextureOps that have identical transforms
// except for their integer offsets can share a transformed image.
t := op.state.t.Offset(absOff)
t := op.state.t.Offset(absOff.Add(opOff))
t, off := separateTransform(t)
*texOps = append(*texOps, textureOp{
sceneIdx: idx,