diff --git a/gpu/compute.go b/gpu/compute.go index 9deb3499..ba10f824 100644 --- a/gpu/compute.go +++ b/gpu/compute.go @@ -8,7 +8,6 @@ import ( "fmt" "image" "image/color" - "math" "math/bits" "time" "unsafe" @@ -17,9 +16,11 @@ import ( "gioui.org/gpu/internal/driver" "gioui.org/internal/byteslice" "gioui.org/internal/f32color" + "gioui.org/internal/ops" "gioui.org/internal/scene" "gioui.org/layout" "gioui.org/op" + "gioui.org/op/clip" ) type compute struct { @@ -227,7 +228,7 @@ func newCompute(ctx driver.Device) (*compute, error) { g.materials.layout = progLayout g.drawOps.pathCache = newOpCache() - g.drawOps.retainPathData = true + g.drawOps.compute = true buf, err := ctx.NewBuffer(driver.BufferBindingShaderStorage, int(unsafe.Sizeof(config{}))) if err != nil { @@ -661,53 +662,59 @@ func (g *compute) encodeClipStack(clip, bounds f32.Rectangle, p *pathOp) int { } if p != nil && p.path { 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.transform(f32.Affine2D{}.Offset(p.off.Mul(-1))) + g.enc.transform(p.trans.Invert()) } else { g.enc.rect(bounds, false) } return nclips } -// encodePath takes a Path encoded with quadSplitter and encode it for elements.comp. -// This is certainly wasteful, but minimizes implementation differences to the old -// renderer. -func encodePath(p []byte) encoder { +func encodePath(pathData []byte, stroke clip.StrokeStyle, dashes dashOp) 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 ( prevTo f32.Point hasPrev bool ) - for len(p) > 0 { - // p contains quadratic curves encoded in vertex structs. - vertex := p[:vertStride] - // We only need some of the values. This code undoes vertex.encode. - from := f32.Pt( - math.Float32frombits(bo.Uint32(vertex[8:])), - math.Float32frombits(bo.Uint32(vertex[12:])), - ) - ctrl := f32.Pt( - math.Float32frombits(bo.Uint32(vertex[16:])), - math.Float32frombits(bo.Uint32(vertex[20:])), - ) - to := f32.Pt( - math.Float32frombits(bo.Uint32(vertex[24:])), - math.Float32frombits(bo.Uint32(vertex[28:])), - ) - if hasPrev && from != prevTo { - enc.scene[len(enc.scene)-1][0] = (flagEndPath << 16) | enc.scene[len(enc.scene)-1][0] + for len(pathData) >= scene.CommandSize+4 { + cmd := ops.DecodeCommand(pathData[4:]) + switch cmd.Op() { + case scene.OpFillLine: + from, to := scene.DecodeLine(cmd) + if hasPrev && from != prevTo { + enc.scene[len(enc.scene)-1][0] |= (flagEndPath << 16) + } + hasPrev = true + prevTo = to + case scene.OpFillQuad: + from, _, to := scene.DecodeQuad(cmd) + if hasPrev && from != prevTo { + enc.scene[len(enc.scene)-1][0] |= (flagEndPath << 16) + } + hasPrev = true + prevTo = to + default: + panic("unsupported path scene command") } - hasPrev = true - prevTo = to - enc.quad(from, ctrl, to, false) - - // The vertex is duplicated 4 times, one for each corner of quads drawn - // by the old renderer. - p = p[vertStride*4:] + enc.scene = append(enc.scene, cmd) + enc.npathseg++ + pathData = pathData[scene.CommandSize+4:] } 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 } diff --git a/gpu/gpu.go b/gpu/gpu.go index 0b1d5867..4095b021 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -93,7 +93,7 @@ type drawOps struct { pathCache *opCache // hack for the compute renderer to access // converted path data. - retainPathData bool + compute bool } type drawState struct { @@ -126,6 +126,11 @@ type pathOp struct { pathVerts []byte parent *pathOp place placement + + // For compute + trans f32.Affine2D + stroke clip.StrokeStyle + dashes dashOp } 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 { data := buildPath(ctx, p.pathVerts) var computePath encoder - if d.retainPathData { - computePath = encodePath(p.pathVerts) + if d.compute { + computePath = encodePath(p.pathVerts, p.stroke, p.dashes) } d.pathCache.put(p.pathKey, opCacheValue{ data: data, @@ -847,12 +852,15 @@ func (d *drawOps) newPathOp() *pathOp { 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 = pathOp{ parent: state.cpath, bounds: bounds, off: off, + trans: tr, + stroke: stroke, + dashes: dashes, } state.cpath = npath if len(aux) > 0 { @@ -939,9 +947,13 @@ loop: // Why is this not used for the offset shapes? op.bounds = v.bounds } else { - quads.aux, op.bounds = d.buildVerts( + pathData, bounds := d.buildVerts( 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 // reused. d.pathCache.put(quads.key, opCacheValue{bounds: op.bounds}) @@ -952,7 +964,7 @@ loop: quads.key.SetTransform(trans) } 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{} stroke = clip.StrokeStyle{} dashes = dashOp{} @@ -983,8 +995,8 @@ loop: dst = layout.FRect(state.image.src.Rect) } clipData, bnd, partialTrans := d.boundsForTransformedRect(dst, trans) - clip := state.clip.Intersect(bnd.Add(off)) - if clip.Empty() { + cl := state.clip.Intersect(bnd.Add(off)) + if cl.Empty() { continue } @@ -993,10 +1005,10 @@ loop: // The paint operation is sheared or rotated, add a clip path representing // this transformed rectangle. 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) 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 l := len(d.vertCache) - d.vertCache = append(d.vertCache, make([]byte, vertStride*4*4)...) - aux = d.vertCache[l:] - encodeQuadTo(aux, 0, corners[0], corners[0].Add(corners[1]).Mul(0.5), corners[1]) - encodeQuadTo(aux[vertStride*4:], 0, corners[1], corners[1].Add(corners[2]).Mul(0.5), corners[2]) - encodeQuadTo(aux[vertStride*4*2:], 0, corners[2], corners[2].Add(corners[3]).Mul(0.5), corners[3]) - encodeQuadTo(aux[vertStride*4*3:], 0, corners[3], corners[3].Add(corners[0]).Mul(0.5), corners[0]) - fillMaxY(aux) + if !d.compute { + d.vertCache = append(d.vertCache, make([]byte, vertStride*4*4)...) + aux = d.vertCache[l:] + encodeQuadTo(aux, 0, corners[0], corners[0].Add(corners[1]).Mul(0.5), corners[1]) + encodeQuadTo(aux[vertStride*4:], 0, corners[1], corners[1].Add(corners[2]).Mul(0.5), corners[2]) + encodeQuadTo(aux[vertStride*4*2:], 0, corners[2], corners[2].Add(corners[3]).Mul(0.5), corners[3]) + 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 var P1, P2, P3 f32.Point diff --git a/internal/ops/ops.go b/internal/ops/ops.go index 32105bce..4586685c 100644 --- a/internal/ops/ops.go +++ b/internal/ops/ops.go @@ -18,6 +18,10 @@ func DecodeCommand(d []byte) scene.Command { return cmd } +func EncodeCommand(out []byte, cmd scene.Command) { + copy(out, byteslice.Uint32(cmd[:])) +} + func DecodeTransform(data []byte) (t f32.Affine2D) { if opconst.OpType(data[0]) != opconst.TypeTransform { panic("invalid op") diff --git a/internal/scene/scene.go b/internal/scene/scene.go index 8326ef42..db663bf7 100644 --- a/internal/scene/scene.go +++ b/internal/scene/scene.go @@ -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) { if cmd[0] != uint32(OpFillQuad) { panic("invalid command") diff --git a/op/clip/clip.go b/op/clip/clip.go index fe0e257a..0477e637 100644 --- a/op/clip/clip.go +++ b/op/clip/clip.go @@ -8,8 +8,8 @@ import ( "math" "gioui.org/f32" - "gioui.org/internal/byteslice" "gioui.org/internal/opconst" + "gioui.org/internal/ops" "gioui.org/internal/scene" "gioui.org/op" ) @@ -156,7 +156,7 @@ func (p *Path) QuadTo(ctrl, to f32.Point) { data := p.ops.Write(scene.CommandSize + 4) bo := binary.LittleEndian 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.hasSegments = true } @@ -392,7 +392,3 @@ func (o Outline) Op() Op { outline: true, } } - -func encodeCommand(out []byte, cmd scene.Command) { - copy(out, byteslice.Uint32(cmd[:])) -}