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