From f86703e4b007c94d2d6f131170e2af4537d86fde Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Thu, 14 Jan 2021 14:57:58 +0100 Subject: [PATCH] op: introduce Defer for deferring CallOps Updates gio#164 Signed-off-by: Elias Naur --- internal/opconst/ops.go | 3 ++ internal/ops/reader.go | 37 +++++++++++++++--- .../rendertest/refs/TestDeferredPaint.png | Bin 0 -> 396 bytes internal/rendertest/render_test.go | 20 ++++++++++ op/op.go | 36 +++++++++++++++-- 5 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 internal/rendertest/refs/TestDeferredPaint.png diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go index ccb9884e..cff4b0d1 100644 --- a/internal/opconst/ops.go +++ b/internal/opconst/ops.go @@ -10,6 +10,7 @@ const firstOpIndex = 200 const ( TypeMacro OpType = iota + firstOpIndex TypeCall + TypeDefer TypeTransform TypeLayer TypeInvalidate @@ -39,6 +40,7 @@ const ( const ( TypeMacroLen = 1 + 4 + 4 TypeCallLen = 1 + 4 + 4 + TypeDeferLen = 1 TypeTransformLen = 1 + 4*6 TypeLayerLen = 1 TypeRedrawLen = 1 + 8 @@ -69,6 +71,7 @@ func (t OpType) Size() int { return [...]int{ TypeMacroLen, TypeCallLen, + TypeDeferLen, TypeTransformLen, TypeLayerLen, TypeRedrawLen, diff --git a/internal/ops/reader.go b/internal/ops/reader.go index 0bab3a06..fdab7a3d 100644 --- a/internal/ops/reader.go +++ b/internal/ops/reader.go @@ -12,9 +12,11 @@ import ( // Reader parses an ops list. type Reader struct { - pc pc - stack []macro - ops *op.Ops + pc pc + stack []macro + ops *op.Ops + deferOps op.Ops + deferDone bool } // EncodedOp represents an encoded op returned by @@ -57,6 +59,8 @@ type opMacroDef struct { // Reset start reading from the op list. func (r *Reader) Reset(ops *op.Ops) { r.stack = r.stack[:0] + r.deferOps.Reset() + r.deferDone = false r.pc = pc{} r.ops = ops } @@ -74,6 +78,7 @@ func (r *Reader) Decode() (EncodedOp, bool) { if r.ops == nil { return EncodedOp{}, false } + deferring := false for { if len(r.stack) > 0 { b := r.stack[len(r.stack)-1] @@ -86,18 +91,30 @@ func (r *Reader) Decode() (EncodedOp, bool) { } data := r.ops.Data() data = data[r.pc.data:] + refs := r.ops.Refs() if len(data) == 0 { - return EncodedOp{}, false + if r.deferDone { + return EncodedOp{}, false + } + r.deferDone = true + // Execute deferred macros. + r.ops = &r.deferOps + r.pc = pc{} + continue } key := Key{ops: r.ops, pc: r.pc.data, version: r.ops.Version()} t := opconst.OpType(data[0]) n := t.Size() nrefs := t.NumRefs() data = data[:n] - refs := r.ops.Refs() refs = refs[r.pc.refs:] refs = refs[:nrefs] switch t { + case opconst.TypeDefer: + deferring = true + r.pc.data += n + r.pc.refs += nrefs + continue case opconst.TypeAux: // An Aux operations is always wrapped in a macro, and // its length is the remaining space. @@ -105,6 +122,16 @@ func (r *Reader) Decode() (EncodedOp, bool) { n += block.endPC.data - r.pc.data - opconst.TypeAuxLen data = data[:n] case opconst.TypeCall: + if deferring { + deferring = false + // Copy macro for deferred execution. + if t.NumRefs() != 1 { + panic("internal error: unexpected number of macro refs") + } + deferData := r.deferOps.Write1(t.Size(), refs[0]) + copy(deferData, data) + continue + } var op macroOp op.decode(data, refs) macroData := op.ops.Data()[op.pc.data:] diff --git a/internal/rendertest/refs/TestDeferredPaint.png b/internal/rendertest/refs/TestDeferredPaint.png new file mode 100644 index 0000000000000000000000000000000000000000..68f278d5bd0299ac930a738738f7714b485215d7 GIT binary patch literal 396 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^U<~$jaSW-L^X9H0-(dv-hJykh z|LaedpKwShxpVHdvyrQ{SLCfbpL%WgzaOjFcf>MoU`k*;z||nipv@4*u!iBr73Mi+ z$KJjEnO-Cf7mPqxH9!J4?#T1qx-WC8I2Y_z6jywEKJUZN-@CSFGNbtjMHR@4BnkXx dWnlRKU%;1(CE|j34=|J&JYD@<);T3K0RRHWbj1Jw literal 0 HcmV?d00001 diff --git a/internal/rendertest/render_test.go b/internal/rendertest/render_test.go index 75b3fb99..33c1ff43 100644 --- a/internal/rendertest/render_test.go +++ b/internal/rendertest/render_test.go @@ -103,6 +103,26 @@ func TestNoClipFromPaint(t *testing.T) { }) } +func TestDeferredPaint(t *testing.T) { + run(t, func(o *op.Ops) { + state := op.Save(o) + clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o) + paint.ColorOp{Color: color.NRGBA{A: 0xff, R: 0xff}}.Add(o) + m := op.Record(o) + paint.PaintOp{}.Add(o) + paintMacro := m.Stop() + op.Defer(o, paintMacro) + paint.ColorOp{Color: color.NRGBA{A: 0xff, G: 0xff}}.Add(o) + paint.PaintOp{}.Add(o) + state.Load() + op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Add(o) + clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o) + paint.ColorOp{Color: color.NRGBA{A: 0xff, B: 0xff}}.Add(o) + paint.PaintOp{}.Add(o) + }, func(r result) { + }) +} + func constSqPath() op.CallOp { innerOps := new(op.Ops) m := op.Record(innerOps) diff --git a/op/op.go b/op/op.go index 3ba3746b..efe8cbc2 100644 --- a/op/op.go +++ b/op/op.go @@ -142,6 +142,31 @@ type pc struct { refs int } +// Defer executes c after all other operations have completed, +// including previously deferred operations. +// Defer saves the operation state, which is then loaded prior +// to execution. +// +// Note that deferred operations are executed in first-in-first-out +// order, unlike the Go facility of the same name. +func Defer(o *Ops, c CallOp) { + if c.ops == nil { + return + } + state := Save(o) + // Wrap c in a macro that loads the saved state before execution. + m := Record(o) + // + state.load() + c.Add(o) + c = m.Stop() + // A Defer is recorded as a TypeDefer followed by the + // wrapped macro. + data := o.Write(opconst.TypeDeferLen) + data[0] = byte(opconst.TypeDefer) + c.Add(o) +} + // Save the current operations state. func Save(o *Ops) StateOp { o.nextStateID++ @@ -159,12 +184,17 @@ func Save(o *Ops) StateOp { // Load a previously saved operations state. func (s StateOp) Load() { + if s.ops.macroStack.currentID != s.macroID { + panic("load in a different macro than save") + } + s.load() +} + +// load is like Load without the same-macro check. +func (s StateOp) load() { if s.id == 0 { panic("zero-value op") } - if s.ops.macroStack.currentID != s.macroID { - panic("pop in a different macro than push") - } bo := binary.LittleEndian data := s.ops.Write(opconst.TypeLoadLen) data[0] = byte(opconst.TypeLoad)