mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 02:15:34 +00:00
all: serialize ops
Pros: - Much less per-frame garbage - Allow future preprocessing of ops while building it - Much fewer interface calls and pointer chasing - Allow future serialization of ops for remote rendering Cons: - Slightly clumsier API Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+101
-75
@@ -14,6 +14,7 @@ import (
|
||||
"gioui.org/ui/app/internal/gl"
|
||||
gdraw "gioui.org/ui/draw"
|
||||
"gioui.org/ui/f32"
|
||||
"gioui.org/ui/internal/ops"
|
||||
"gioui.org/ui/internal/path"
|
||||
"golang.org/x/image/draw"
|
||||
)
|
||||
@@ -31,13 +32,13 @@ type GPU struct {
|
||||
refreshErr chan error
|
||||
stop chan struct{}
|
||||
stopped chan struct{}
|
||||
ops ops
|
||||
ops drawOps
|
||||
}
|
||||
|
||||
type frame struct {
|
||||
collectStats bool
|
||||
viewport image.Point
|
||||
ops ops
|
||||
ops drawOps
|
||||
}
|
||||
|
||||
type frameResult struct {
|
||||
@@ -54,7 +55,8 @@ type renderer struct {
|
||||
intersections packer
|
||||
}
|
||||
|
||||
type ops struct {
|
||||
type drawOps struct {
|
||||
reader ops.Reader
|
||||
cache *resourceCache
|
||||
viewport image.Point
|
||||
clearColor [3]float32
|
||||
@@ -66,6 +68,14 @@ type ops struct {
|
||||
pathOps []*pathOp
|
||||
}
|
||||
|
||||
type drawState struct {
|
||||
clip f32.Rectangle
|
||||
t ui.Transform
|
||||
cpath *pathOp
|
||||
rect bool
|
||||
z int
|
||||
}
|
||||
|
||||
type pathOp struct {
|
||||
off f32.Point
|
||||
// clip is the union of all
|
||||
@@ -298,12 +308,13 @@ func (g *GPU) Refresh() {
|
||||
g.setErr(<-g.refreshErr)
|
||||
}
|
||||
|
||||
func (g *GPU) Draw(profile bool, viewport image.Point, op ui.Op) {
|
||||
func (g *GPU) Draw(profile bool, viewport image.Point, root *ui.Ops) {
|
||||
if g.err != nil {
|
||||
return
|
||||
}
|
||||
g.Flush()
|
||||
g.ops.collect(g.cache, op, viewport)
|
||||
g.ops.reset(g.cache, viewport)
|
||||
g.ops.collect(g.cache, root, viewport)
|
||||
g.frames <- frame{profile, viewport, g.ops}
|
||||
g.drawing = true
|
||||
}
|
||||
@@ -629,84 +640,99 @@ func floor(v float32) int {
|
||||
}
|
||||
}
|
||||
|
||||
func (ops *ops) collect(cache *resourceCache, op ui.Op, viewport image.Point) {
|
||||
ops.clearColor = [3]float32{1.0, 1.0, 1.0}
|
||||
ops.cache = cache
|
||||
ops.viewport = viewport
|
||||
ops.imageOps = ops.imageOps[:0]
|
||||
ops.zimageOps = ops.zimageOps[:0]
|
||||
ops.pathOps = ops.pathOps[:0]
|
||||
func (d *drawOps) reset(cache *resourceCache, viewport image.Point) {
|
||||
d.clearColor = [3]float32{1.0, 1.0, 1.0}
|
||||
d.cache = cache
|
||||
d.viewport = viewport
|
||||
d.imageOps = d.imageOps[:0]
|
||||
d.zimageOps = d.zimageOps[:0]
|
||||
d.pathOps = d.pathOps[:0]
|
||||
}
|
||||
|
||||
func (d *drawOps) collect(cache *resourceCache, root *ui.Ops, viewport image.Point) {
|
||||
d.reset(cache, viewport)
|
||||
clip := f32.Rectangle{
|
||||
Max: f32.Point{X: float32(viewport.X), Y: float32(viewport.Y)},
|
||||
}
|
||||
ops.collectOp(op, clip, ui.Transform{}, nil, true, 0)
|
||||
d.reader.Reset(root.Data(), root.Refs())
|
||||
d.collectOps(&d.reader, clip, ui.Transform{}, nil, true, 0)
|
||||
}
|
||||
|
||||
func (ops *ops) collectOp(op ui.Op, clip f32.Rectangle, t ui.Transform, cpath *pathOp, rect bool, z int) int {
|
||||
type childOp interface {
|
||||
ChildOp() ui.Op
|
||||
}
|
||||
switch op := op.(type) {
|
||||
case ui.OpTransform:
|
||||
t := t.Mul(op.Transform)
|
||||
z = ops.collectOp(op.ChildOp(), clip, t, cpath, rect, z)
|
||||
case gdraw.OpClip:
|
||||
data := op.Path.Data().(*path.Path)
|
||||
off := t.Transform(f32.Point{})
|
||||
clip := clip.Intersect(data.Bounds.Add(off))
|
||||
if clip.Empty() {
|
||||
func (d *drawOps) collectOps(r *ops.Reader, clip f32.Rectangle, t ui.Transform, cpath *pathOp, rect bool, z int) int {
|
||||
loop:
|
||||
for {
|
||||
data, ok := r.Decode()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
cpath := &pathOp{
|
||||
parent: cpath,
|
||||
off: off,
|
||||
switch ops.OpType(data[0]) {
|
||||
case ops.TypeTransform:
|
||||
var op ui.OpTransform
|
||||
op.Decode(data)
|
||||
t = t.Mul(op.Transform)
|
||||
case ops.TypeClip:
|
||||
var op gdraw.OpClip
|
||||
op.Decode(data, r.Refs)
|
||||
if op.Path == nil {
|
||||
clip = f32.Rectangle{}
|
||||
continue
|
||||
}
|
||||
data := op.Path.Data().(*path.Path)
|
||||
off := t.Transform(f32.Point{})
|
||||
clip = clip.Intersect(data.Bounds.Add(off))
|
||||
if clip.Empty() {
|
||||
continue
|
||||
}
|
||||
cpath = &pathOp{
|
||||
parent: cpath,
|
||||
off: off,
|
||||
}
|
||||
if len(data.Vertices) > 0 {
|
||||
rect = false
|
||||
cpath.path = data
|
||||
d.pathOps = append(d.pathOps, cpath)
|
||||
}
|
||||
case ops.TypeImage:
|
||||
var op gdraw.OpImage
|
||||
op.Decode(data, r.Refs)
|
||||
off := t.Transform(f32.Point{})
|
||||
clip := clip.Intersect(op.Rect.Add(off))
|
||||
if clip.Empty() {
|
||||
continue
|
||||
}
|
||||
bounds := boundRectF(clip)
|
||||
mat := materialFor(d.cache, op, off, bounds)
|
||||
if bounds.Min == (image.Point{}) && bounds.Max == d.viewport && mat.opaque && mat.material == materialColor {
|
||||
// The image is a uniform opaque color and takes up the whole screen.
|
||||
// Scrap images up to and including this image and set clear color.
|
||||
d.zimageOps = d.zimageOps[:0]
|
||||
d.imageOps = d.imageOps[:0]
|
||||
z = 0
|
||||
copy(d.clearColor[:], mat.color[:3])
|
||||
continue
|
||||
}
|
||||
z++
|
||||
// Assume 16-bit depth buffer.
|
||||
const zdepth = 1 << 16
|
||||
// Convert z to window-space, assuming depth range [0;1].
|
||||
zf := float32(z)*2/zdepth - 1.0
|
||||
img := imageOp{
|
||||
z: zf,
|
||||
path: cpath,
|
||||
off: off,
|
||||
clip: bounds,
|
||||
material: mat,
|
||||
}
|
||||
if rect && img.material.opaque {
|
||||
d.zimageOps = append(d.zimageOps, img)
|
||||
} else {
|
||||
d.imageOps = append(d.imageOps, img)
|
||||
}
|
||||
case ops.TypePush:
|
||||
z = d.collectOps(r, clip, t, cpath, rect, z)
|
||||
case ops.TypePop:
|
||||
break loop
|
||||
}
|
||||
if len(data.Vertices) > 0 {
|
||||
rect = false
|
||||
cpath.path = data
|
||||
ops.pathOps = append(ops.pathOps, cpath)
|
||||
}
|
||||
z = ops.collectOp(op.ChildOp(), clip, t, cpath, rect, z)
|
||||
case gdraw.OpImage:
|
||||
off := t.Transform(f32.Point{})
|
||||
clip := clip.Intersect(op.Rect.Add(off))
|
||||
if clip.Empty() {
|
||||
break
|
||||
}
|
||||
bounds := boundRectF(clip)
|
||||
mat := materialFor(ops.cache, op, off, bounds)
|
||||
if bounds.Min == (image.Point{}) && bounds.Max == ops.viewport && mat.opaque && mat.material == materialColor {
|
||||
// The image is a uniform opaque color and takes up the whole screen.
|
||||
// Scrap images up to and including this image and set clear color.
|
||||
ops.zimageOps = ops.zimageOps[:0]
|
||||
ops.imageOps = ops.imageOps[:0]
|
||||
z = 0
|
||||
copy(ops.clearColor[:], mat.color[:3])
|
||||
break
|
||||
}
|
||||
z++
|
||||
// Assume 16-bit depth buffer.
|
||||
const zdepth = 1 << 16
|
||||
// Convert z to window-space, assuming depth range [0;1].
|
||||
zf := float32(z)*2/zdepth - 1.0
|
||||
img := imageOp{
|
||||
z: zf,
|
||||
path: cpath,
|
||||
off: off,
|
||||
clip: bounds,
|
||||
material: mat,
|
||||
}
|
||||
if rect && img.material.opaque {
|
||||
ops.zimageOps = append(ops.zimageOps, img)
|
||||
} else {
|
||||
ops.imageOps = append(ops.imageOps, img)
|
||||
}
|
||||
case ui.Ops:
|
||||
for _, op := range op {
|
||||
z = ops.collectOp(op, clip, t, cpath, rect, z)
|
||||
}
|
||||
case childOp:
|
||||
z = ops.collectOp(op.ChildOp(), clip, t, cpath, rect, z)
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
+27
-28
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"gioui.org/ui"
|
||||
"gioui.org/ui/app/internal/gpu"
|
||||
"gioui.org/ui/internal/ops"
|
||||
"gioui.org/ui/key"
|
||||
"gioui.org/ui/pointer"
|
||||
)
|
||||
@@ -41,6 +42,8 @@ type Window struct {
|
||||
hasNextFrame bool
|
||||
nextFrame time.Time
|
||||
delayedDraw *time.Timer
|
||||
|
||||
reader ops.Reader
|
||||
}
|
||||
|
||||
// driver is the interface for the platform implementation
|
||||
@@ -89,7 +92,7 @@ func (w *Window) Err() error {
|
||||
return w.err
|
||||
}
|
||||
|
||||
func (w *Window) Draw(root ui.Op) {
|
||||
func (w *Window) Draw(root *ui.Ops) {
|
||||
if !w.IsAlive() {
|
||||
return
|
||||
}
|
||||
@@ -132,13 +135,35 @@ func (w *Window) Draw(root ui.Op) {
|
||||
w.timings = fmt.Sprintf("t:%7s %s", frameDur, w.gpu.Timings())
|
||||
w.setNextFrame(time.Time{})
|
||||
}
|
||||
if t, ok := collectRedraws(root); ok {
|
||||
w.reader.Reset(root.Data(), root.Refs())
|
||||
if t, ok := collectRedraws(&w.reader); ok {
|
||||
w.setNextFrame(t)
|
||||
}
|
||||
w.updateAnimation()
|
||||
w.gpu.Draw(w.Profiling, size, root)
|
||||
}
|
||||
|
||||
func collectRedraws(r *ops.Reader) (time.Time, bool) {
|
||||
var t time.Time
|
||||
redraw := false
|
||||
for {
|
||||
data, ok := r.Decode()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
switch ops.OpType(data[0]) {
|
||||
case ops.TypeRedraw:
|
||||
var op ui.OpRedraw
|
||||
op.Decode(data)
|
||||
if !redraw || op.At.Before(t) {
|
||||
redraw = true
|
||||
t = op.At
|
||||
}
|
||||
}
|
||||
}
|
||||
return t, redraw
|
||||
}
|
||||
|
||||
func (w *Window) Redraw() {
|
||||
if !w.IsAlive() {
|
||||
return
|
||||
@@ -256,29 +281,3 @@ func (w *Window) event(e Event) {
|
||||
close(w.events)
|
||||
}
|
||||
}
|
||||
|
||||
func collectRedraws(op ui.Op) (time.Time, bool) {
|
||||
type childOp interface {
|
||||
ChildOp() ui.Op
|
||||
}
|
||||
switch op := op.(type) {
|
||||
case ui.Ops:
|
||||
var earliest time.Time
|
||||
var valid bool
|
||||
for _, op := range op {
|
||||
if t, ok := collectRedraws(op); ok {
|
||||
if !valid || t.Before(earliest) {
|
||||
valid = true
|
||||
earliest = t
|
||||
}
|
||||
}
|
||||
}
|
||||
return earliest, valid
|
||||
case ui.OpRedraw:
|
||||
return op.At, true
|
||||
case childOp:
|
||||
return collectRedraws(op.ChildOp())
|
||||
default:
|
||||
return time.Time{}, false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user