mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 01:45:36 +00:00
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:
+28
-20
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user