gpu: [compute] skip encoding roundtrip for path data

Since clip.Path now encodes paths in the format expected by
elements.comp, use that data directly instead of a roundtrip through
drawOps.buildVerts.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2021-03-11 15:58:33 +01:00
parent eb9bf60b09
commit a369c408f9
5 changed files with 103 additions and 57 deletions
+41 -34
View File
@@ -8,7 +8,6 @@ import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"math"
"math/bits" "math/bits"
"time" "time"
"unsafe" "unsafe"
@@ -17,9 +16,11 @@ import (
"gioui.org/gpu/internal/driver" "gioui.org/gpu/internal/driver"
"gioui.org/internal/byteslice" "gioui.org/internal/byteslice"
"gioui.org/internal/f32color" "gioui.org/internal/f32color"
"gioui.org/internal/ops"
"gioui.org/internal/scene" "gioui.org/internal/scene"
"gioui.org/layout" "gioui.org/layout"
"gioui.org/op" "gioui.org/op"
"gioui.org/op/clip"
) )
type compute struct { type compute struct {
@@ -227,7 +228,7 @@ func newCompute(ctx driver.Device) (*compute, error) {
g.materials.layout = progLayout g.materials.layout = progLayout
g.drawOps.pathCache = newOpCache() g.drawOps.pathCache = newOpCache()
g.drawOps.retainPathData = true g.drawOps.compute = true
buf, err := ctx.NewBuffer(driver.BufferBindingShaderStorage, int(unsafe.Sizeof(config{}))) buf, err := ctx.NewBuffer(driver.BufferBindingShaderStorage, int(unsafe.Sizeof(config{})))
if err != nil { if err != nil {
@@ -661,53 +662,59 @@ func (g *compute) encodeClipStack(clip, bounds f32.Rectangle, p *pathOp) int {
} }
if p != nil && p.path { if p != nil && p.path {
pathData, _ := g.drawOps.pathCache.get(p.pathKey) pathData, _ := g.drawOps.pathCache.get(p.pathKey)
g.enc.transform(f32.Affine2D{}.Offset(p.off)) g.enc.transform(p.trans)
g.enc.append(pathData.computePath) g.enc.append(pathData.computePath)
g.enc.transform(f32.Affine2D{}.Offset(p.off.Mul(-1))) g.enc.transform(p.trans.Invert())
} else { } else {
g.enc.rect(bounds, false) g.enc.rect(bounds, false)
} }
return nclips return nclips
} }
// encodePath takes a Path encoded with quadSplitter and encode it for elements.comp. func encodePath(pathData []byte, stroke clip.StrokeStyle, dashes dashOp) encoder {
// This is certainly wasteful, but minimizes implementation differences to the old
// renderer.
func encodePath(p []byte) encoder {
var enc encoder var enc encoder
if stroke.Width > 0 {
quads := decodeToStrokeQuads(pathData)
quads = quads.stroke(stroke, dashes)
for _, quad := range quads {
q := quad.quad
enc.quad(q.From, q.Ctrl, q.To, false)
}
if len(quads) > 0 {
enc.scene[len(enc.scene)-1][0] |= (flagEndPath << 16)
}
return enc
}
var ( var (
prevTo f32.Point prevTo f32.Point
hasPrev bool hasPrev bool
) )
for len(p) > 0 { for len(pathData) >= scene.CommandSize+4 {
// p contains quadratic curves encoded in vertex structs. cmd := ops.DecodeCommand(pathData[4:])
vertex := p[:vertStride] switch cmd.Op() {
// We only need some of the values. This code undoes vertex.encode. case scene.OpFillLine:
from := f32.Pt( from, to := scene.DecodeLine(cmd)
math.Float32frombits(bo.Uint32(vertex[8:])), if hasPrev && from != prevTo {
math.Float32frombits(bo.Uint32(vertex[12:])), enc.scene[len(enc.scene)-1][0] |= (flagEndPath << 16)
) }
ctrl := f32.Pt( hasPrev = true
math.Float32frombits(bo.Uint32(vertex[16:])), prevTo = to
math.Float32frombits(bo.Uint32(vertex[20:])), case scene.OpFillQuad:
) from, _, to := scene.DecodeQuad(cmd)
to := f32.Pt( if hasPrev && from != prevTo {
math.Float32frombits(bo.Uint32(vertex[24:])), enc.scene[len(enc.scene)-1][0] |= (flagEndPath << 16)
math.Float32frombits(bo.Uint32(vertex[28:])), }
) hasPrev = true
if hasPrev && from != prevTo { prevTo = to
enc.scene[len(enc.scene)-1][0] = (flagEndPath << 16) | enc.scene[len(enc.scene)-1][0] default:
panic("unsupported path scene command")
} }
hasPrev = true enc.scene = append(enc.scene, cmd)
prevTo = to enc.npathseg++
enc.quad(from, ctrl, to, false) pathData = pathData[scene.CommandSize+4:]
// The vertex is duplicated 4 times, one for each corner of quads drawn
// by the old renderer.
p = p[vertStride*4:]
} }
if hasPrev { if hasPrev {
enc.scene[len(enc.scene)-1][0] = (flagEndPath << 16) | enc.scene[len(enc.scene)-1][0] enc.scene[len(enc.scene)-1][0] |= (flagEndPath << 16)
} }
return enc return enc
} }
+47 -17
View File
@@ -93,7 +93,7 @@ type drawOps struct {
pathCache *opCache pathCache *opCache
// hack for the compute renderer to access // hack for the compute renderer to access
// converted path data. // converted path data.
retainPathData bool compute bool
} }
type drawState struct { type drawState struct {
@@ -126,6 +126,11 @@ type pathOp struct {
pathVerts []byte pathVerts []byte
parent *pathOp parent *pathOp
place placement place placement
// For compute
trans f32.Affine2D
stroke clip.StrokeStyle
dashes dashOp
} }
type imageOp struct { type imageOp struct {
@@ -829,8 +834,8 @@ func (d *drawOps) collect(ctx driver.Device, cache *resourceCache, root *op.Ops,
if v, exists := d.pathCache.get(p.pathKey); !exists || v.data.data == nil { if v, exists := d.pathCache.get(p.pathKey); !exists || v.data.data == nil {
data := buildPath(ctx, p.pathVerts) data := buildPath(ctx, p.pathVerts)
var computePath encoder var computePath encoder
if d.retainPathData { if d.compute {
computePath = encodePath(p.pathVerts) computePath = encodePath(p.pathVerts, p.stroke, p.dashes)
} }
d.pathCache.put(p.pathKey, opCacheValue{ d.pathCache.put(p.pathKey, opCacheValue{
data: data, data: data,
@@ -847,12 +852,15 @@ func (d *drawOps) newPathOp() *pathOp {
return &d.pathOpCache[len(d.pathOpCache)-1] return &d.pathOpCache[len(d.pathOpCache)-1]
} }
func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey ops.Key, bounds f32.Rectangle, off f32.Point) { func (d *drawOps) addClipPath(state *drawState, aux []byte, auxKey ops.Key, bounds f32.Rectangle, off f32.Point, tr f32.Affine2D, stroke clip.StrokeStyle, dashes dashOp) {
npath := d.newPathOp() npath := d.newPathOp()
*npath = pathOp{ *npath = pathOp{
parent: state.cpath, parent: state.cpath,
bounds: bounds, bounds: bounds,
off: off, off: off,
trans: tr,
stroke: stroke,
dashes: dashes,
} }
state.cpath = npath state.cpath = npath
if len(aux) > 0 { if len(aux) > 0 {
@@ -939,9 +947,13 @@ loop:
// Why is this not used for the offset shapes? // Why is this not used for the offset shapes?
op.bounds = v.bounds op.bounds = v.bounds
} else { } else {
quads.aux, op.bounds = d.buildVerts( pathData, bounds := d.buildVerts(
quads.aux, trans, op.outline, stroke, dashes, quads.aux, trans, op.outline, stroke, dashes,
) )
op.bounds = bounds
if !d.compute {
quads.aux = pathData
}
// add it to the cache, without GPU data, so the transform can be // add it to the cache, without GPU data, so the transform can be
// reused. // reused.
d.pathCache.put(quads.key, opCacheValue{bounds: op.bounds}) d.pathCache.put(quads.key, opCacheValue{bounds: op.bounds})
@@ -952,7 +964,7 @@ loop:
quads.key.SetTransform(trans) quads.key.SetTransform(trans)
} }
state.clip = state.clip.Intersect(op.bounds.Add(off)) state.clip = state.clip.Intersect(op.bounds.Add(off))
d.addClipPath(&state, quads.aux, quads.key, op.bounds, off) d.addClipPath(&state, quads.aux, quads.key, op.bounds, off, state.t, stroke, dashes)
quads = quadsOp{} quads = quadsOp{}
stroke = clip.StrokeStyle{} stroke = clip.StrokeStyle{}
dashes = dashOp{} dashes = dashOp{}
@@ -983,8 +995,8 @@ loop:
dst = layout.FRect(state.image.src.Rect) dst = layout.FRect(state.image.src.Rect)
} }
clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, trans) clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, trans)
clip := state.clip.Intersect(bnd.Add(off)) cl := state.clip.Intersect(bnd.Add(off))
if clip.Empty() { if cl.Empty() {
continue continue
} }
@@ -993,10 +1005,10 @@ loop:
// The paint operation is sheared or rotated, add a clip path representing // The paint operation is sheared or rotated, add a clip path representing
// this transformed rectangle. // this transformed rectangle.
encOp.Key.SetTransform(trans) encOp.Key.SetTransform(trans)
d.addClipPath(&state, clipData, encOp.Key, bnd, off) d.addClipPath(&state, clipData, encOp.Key, bnd, off, state.t, clip.StrokeStyle{}, dashOp{})
} }
bounds := boundRectF(clip) bounds := boundRectF(cl)
mat := state.materialFor(bnd, off, partialTrans, bounds, state.t) mat := state.materialFor(bnd, off, partialTrans, bounds, state.t)
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && state.rect && mat.opaque && (mat.material == materialColor) { if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && state.rect && mat.opaque && (mat.material == materialColor) {
@@ -1452,13 +1464,31 @@ func (d *drawOps) boundsForTransformedRect(r f32.Rectangle, tr f32.Affine2D) (au
// build the GPU vertices // build the GPU vertices
l := len(d.vertCache) l := len(d.vertCache)
d.vertCache = append(d.vertCache, make([]byte, vertStride*4*4)...) if !d.compute {
aux = d.vertCache[l:] d.vertCache = append(d.vertCache, make([]byte, vertStride*4*4)...)
encodeQuadTo(aux, 0, corners[0], corners[0].Add(corners[1]).Mul(0.5), corners[1]) aux = d.vertCache[l:]
encodeQuadTo(aux[vertStride*4:], 0, corners[1], corners[1].Add(corners[2]).Mul(0.5), corners[2]) encodeQuadTo(aux, 0, corners[0], corners[0].Add(corners[1]).Mul(0.5), corners[1])
encodeQuadTo(aux[vertStride*4*2:], 0, corners[2], corners[2].Add(corners[3]).Mul(0.5), corners[3]) encodeQuadTo(aux[vertStride*4:], 0, corners[1], corners[1].Add(corners[2]).Mul(0.5), corners[2])
encodeQuadTo(aux[vertStride*4*3:], 0, corners[3], corners[3].Add(corners[0]).Mul(0.5), corners[0]) encodeQuadTo(aux[vertStride*4*2:], 0, corners[2], corners[2].Add(corners[3]).Mul(0.5), corners[3])
fillMaxY(aux) encodeQuadTo(aux[vertStride*4*3:], 0, corners[3], corners[3].Add(corners[0]).Mul(0.5), corners[0])
fillMaxY(aux)
} else {
d.vertCache = append(d.vertCache, make([]byte, (scene.CommandSize+4)*4)...)
aux = d.vertCache[l:]
buf := aux
bo := binary.LittleEndian
bo.PutUint32(buf, 0) // Contour
ops.EncodeCommand(buf[4:], scene.Line(r.Min, f32.Pt(r.Max.X, r.Min.Y), false, 0))
buf = buf[4+scene.CommandSize:]
bo.PutUint32(buf, 0)
ops.EncodeCommand(buf[4:], scene.Line(f32.Pt(r.Max.X, r.Min.Y), r.Max, false, 0))
buf = buf[4+scene.CommandSize:]
bo.PutUint32(buf, 0)
ops.EncodeCommand(buf[4:], scene.Line(r.Max, f32.Pt(r.Min.X, r.Max.Y), false, 0))
buf = buf[4+scene.CommandSize:]
bo.PutUint32(buf, 0)
ops.EncodeCommand(buf[4:], scene.Line(f32.Pt(r.Min.X, r.Max.Y), r.Min, false, 0))
}
// establish the transform mapping from bounds rectangle to transformed corners // establish the transform mapping from bounds rectangle to transformed corners
var P1, P2, P3 f32.Point var P1, P2, P3 f32.Point
+4
View File
@@ -18,6 +18,10 @@ func DecodeCommand(d []byte) scene.Command {
return cmd return cmd
} }
func EncodeCommand(out []byte, cmd scene.Command) {
copy(out, byteslice.Uint32(cmd[:]))
}
func DecodeTransform(data []byte) (t f32.Affine2D) { func DecodeTransform(data []byte) (t f32.Affine2D) {
if opconst.OpType(data[0]) != opconst.TypeTransform { if opconst.OpType(data[0]) != opconst.TypeTransform {
panic("invalid op") panic("invalid op")
+9
View File
@@ -133,6 +133,15 @@ func FillImage(index int) Command {
} }
} }
func DecodeLine(cmd Command) (from, to f32.Point) {
if cmd[0] != uint32(OpFillLine) {
panic("invalid command")
}
from = f32.Pt(math.Float32frombits(cmd[1]), math.Float32frombits(cmd[2]))
to = f32.Pt(math.Float32frombits(cmd[3]), math.Float32frombits(cmd[4]))
return
}
func DecodeQuad(cmd Command) (from, ctrl, to f32.Point) { func DecodeQuad(cmd Command) (from, ctrl, to f32.Point) {
if cmd[0] != uint32(OpFillQuad) { if cmd[0] != uint32(OpFillQuad) {
panic("invalid command") panic("invalid command")
+2 -6
View File
@@ -8,8 +8,8 @@ import (
"math" "math"
"gioui.org/f32" "gioui.org/f32"
"gioui.org/internal/byteslice"
"gioui.org/internal/opconst" "gioui.org/internal/opconst"
"gioui.org/internal/ops"
"gioui.org/internal/scene" "gioui.org/internal/scene"
"gioui.org/op" "gioui.org/op"
) )
@@ -156,7 +156,7 @@ func (p *Path) QuadTo(ctrl, to f32.Point) {
data := p.ops.Write(scene.CommandSize + 4) data := p.ops.Write(scene.CommandSize + 4)
bo := binary.LittleEndian bo := binary.LittleEndian
bo.PutUint32(data[0:], uint32(p.contour)) bo.PutUint32(data[0:], uint32(p.contour))
encodeCommand(data[4:], scene.Quad(p.pen, ctrl, to, false)) ops.EncodeCommand(data[4:], scene.Quad(p.pen, ctrl, to, false))
p.pen = to p.pen = to
p.hasSegments = true p.hasSegments = true
} }
@@ -392,7 +392,3 @@ func (o Outline) Op() Op {
outline: true, outline: true,
} }
} }
func encodeCommand(out []byte, cmd scene.Command) {
copy(out, byteslice.Uint32(cmd[:]))
}