From d6886737a51026e6e28e640fbce3d23e45f44c81 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Tue, 19 Jan 2021 20:04:30 +0100 Subject: [PATCH] op: change Defer to only restore transformation state It turns out restoring all operation state from the moment Defer is executed is too much; for example, a right-click pop-up needs the transformation, but not the current clip. Change Defer to only restore the transformation, and reset all other state. Other combinations may be needed in future; we'll deal with them then, possibly by exposing the load state mask. Signed-off-by: Elias Naur --- gpu/gpu.go | 23 ++++++++---- internal/opconst/ops.go | 16 ++++++++- internal/ops/ops.go | 6 ++-- .../rendertest/refs/TestDeferredPaint.png | Bin 396 -> 420 bytes internal/rendertest/render_test.go | 10 ++++-- io/router/key.go | 6 ++-- io/router/pointer.go | 23 ++++++++---- op/op.go | 34 +++++++++++------- 8 files changed, 84 insertions(+), 34 deletions(-) diff --git a/gpu/gpu.go b/gpu/gpu.go index 64395eaf..db914552 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -869,6 +869,13 @@ func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) { return } +func (d *drawOps) save(id int, state drawState) { + if extra := id - len(d.states) + 1; extra > 0 { + d.states = append(d.states, make([]drawState, extra)...) + } + d.states[id] = state +} + func (d *drawOps) collectOps(r *ops.Reader, state drawState) { var ( quads quadsOp @@ -876,6 +883,7 @@ func (d *drawOps) collectOps(r *ops.Reader, state drawState) { dashes dashOp z int ) + d.save(opconst.InitialStateID, state) loop: for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { switch opconst.OpType(encOp.Data[0]) { @@ -1029,13 +1037,16 @@ loop: } case opconst.TypeSave: id := ops.DecodeSave(encOp.Data) - if extra := id - len(d.states) + 1; extra > 0 { - d.states = append(d.states, make([]drawState, extra)...) - } - d.states[id] = state + d.save(id, state) case opconst.TypeLoad: - id := ops.DecodeLoad(encOp.Data) - state = d.states[id] + id, mask := ops.DecodeLoad(encOp.Data) + s := d.states[id] + if mask&opconst.TransformState != 0 { + state.t = s.t + } + if mask&^opconst.TransformState != 0 { + state = s + } } } } diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go index cff4b0d1..58904ee1 100644 --- a/internal/opconst/ops.go +++ b/internal/opconst/ops.go @@ -57,7 +57,7 @@ const ( TypeKeyFocusLen = 1 + 1 TypeKeySoftKeyboardLen = 1 + 1 TypeSaveLen = 1 + 4 - TypeLoadLen = 1 + 4 + TypeLoadLen = 1 + 1 + 4 TypeAuxLen = 1 TypeClipLen = 1 + 4*4 + 1 TypeProfileLen = 1 @@ -67,6 +67,20 @@ const ( TypeDashLen = 1 + 4 + 1 ) +// StateMask is a bitmask of state types a load operation +// should restore. +type StateMask uint8 + +const ( + TransformState StateMask = 1 << iota + + AllState = ^StateMask(0) +) + +// InitialStateID is the ID for saving and loading +// the initial operation state. +const InitialStateID = 0 + func (t OpType) Size() int { return [...]int{ TypeMacroLen, diff --git a/internal/ops/ops.go b/internal/ops/ops.go index aa5b571c..49f68d73 100644 --- a/internal/ops/ops.go +++ b/internal/ops/ops.go @@ -72,11 +72,11 @@ func DecodeSave(data []byte) int { return int(bo.Uint32(data[1:])) } -// DecodeLoad decodes the state id of a restore op. -func DecodeLoad(data []byte) int { +// DecodeLoad decodes the state id and mask of a load op. +func DecodeLoad(data []byte) (int, opconst.StateMask) { if opconst.OpType(data[0]) != opconst.TypeLoad { panic("invalid op") } bo := binary.LittleEndian - return int(bo.Uint32(data[1:])) + return int(bo.Uint32(data[2:])), opconst.StateMask(data[1]) } diff --git a/internal/rendertest/refs/TestDeferredPaint.png b/internal/rendertest/refs/TestDeferredPaint.png index 68f278d5bd0299ac930a738738f7714b485215d7..1446810f306880fe6d4f07b7ecfae8b812de0d5b 100644 GIT binary patch literal 420 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^V9fS(aSW-L^X9H$U$cRLLtsSV zf9>l01TG&g+b5K>9^h&aWzc2_V_3rw!MK4b;riL{ zpJS^Z7eD{+TFKXmLHq(e(L_`pSz2MQ8iZv+rNO`*#1rYg0zB z(~&K^aaMHc*YBm}mst{!g~96j-*MJI-uEtV7R=8WDhLVu2L|~6|CamtncrBZvjaH{ Mp00i_>zopr0CdoYs{jB1 delta 182 zcmZ3&+`~LU#Vgp;#WAE}&YQc2e1{bT7!C@2{I5S*e!?N4 restored.keyboard { restored.keyboard = state.keyboard @@ -148,7 +148,9 @@ func (q *keyQueue) resolveFocus(events *handlerEvents) resolveState { if state.pri.replaces(restored.pri) { restored.tag, restored.pri = state.tag, state.pri } - state = restored + if mask != 0 { + state = restored + } } } return state diff --git a/io/router/pointer.go b/io/router/pointer.go index f0115b66..0fb2b22e 100644 --- a/io/router/pointer.go +++ b/io/router/pointer.go @@ -85,22 +85,33 @@ const ( areaEllipse ) +func (q *pointerQueue) save(id int, state collectState) { + if extra := id - len(q.states) + 1; extra > 0 { + q.states = append(q.states, make([]collectState, extra)...) + } + q.states[id] = state +} + func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) { state := collectState{ area: -1, node: -1, } + q.save(opconst.InitialStateID, state) for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { switch opconst.OpType(encOp.Data[0]) { case opconst.TypeSave: id := ops.DecodeSave(encOp.Data) - if extra := id - len(q.states) + 1; extra > 0 { - q.states = append(q.states, make([]collectState, extra)...) - } - q.states[id] = state + q.save(id, state) case opconst.TypeLoad: - id := ops.DecodeLoad(encOp.Data) - state = q.states[id] + id, mask := ops.DecodeLoad(encOp.Data) + s := q.states[id] + if mask&opconst.TransformState != 0 { + state.t = s.t + } + if mask&^opconst.TransformState != 0 { + state = s + } case opconst.TypePass: state.pass = encOp.Data[1] != 0 case opconst.TypeArea: diff --git a/op/op.go b/op/op.go index efe8cbc2..7dc0af02 100644 --- a/op/op.go +++ b/op/op.go @@ -144,8 +144,8 @@ type pc struct { // 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. +// Defer saves the current transformation and restores it prior +// to execution. All other operation state is reset. // // Note that deferred operations are executed in first-in-first-out // order, unlike the Go facility of the same name. @@ -156,8 +156,8 @@ func Defer(o *Ops, c CallOp) { state := Save(o) // Wrap c in a macro that loads the saved state before execution. m := Record(o) - // - state.load() + load(o, opconst.InitialStateID, opconst.AllState) + load(o, state.id, opconst.TransformState) c.Add(o) c = m.Stop() // A Defer is recorded as a TypeDefer followed by the @@ -175,11 +175,17 @@ func Save(o *Ops) StateOp { id: o.nextStateID, macroID: o.macroStack.currentID, } + save(o, s.id) + return s +} + +// save records a save of the operations state to +// id. +func save(o *Ops, id int) { bo := binary.LittleEndian data := o.Write(opconst.TypeSaveLen) data[0] = byte(opconst.TypeSave) - bo.PutUint32(data[1:], uint32(s.id)) - return s + bo.PutUint32(data[1:], uint32(id)) } // Load a previously saved operations state. @@ -187,18 +193,20 @@ 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") } + load(s.ops, s.id, opconst.AllState) +} + +// load a previously saved operations state given +// its ID. Only state included in mask is affected. +func load(o *Ops, id int, m opconst.StateMask) { bo := binary.LittleEndian - data := s.ops.Write(opconst.TypeLoadLen) + data := o.Write(opconst.TypeLoadLen) data[0] = byte(opconst.TypeLoad) - bo.PutUint32(data[1:], uint32(s.id)) + data[1] = byte(m) + bo.PutUint32(data[2:], uint32(id)) } // Reset the Ops, preparing it for re-use. Reset invalidates