ops.mx raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 // Binary op encoding, reader, and decoder.
4 // Ported from gioui.org/internal/ops.
5
6 package gio
7
8 import (
9 "encoding/binary"
10 "image"
11 "math"
12 )
13
14 type Ops struct {
15 // version is incremented at each ResetOps.
16 version uint32
17 // data contains the serialized operations.
18 data []byte
19 // refs hold external references for operations.
20 refs []any
21 // stringRefs provides space for string references, pointers to which will
22 // be stored in refs. Storing a string directly in refs would cause a heap
23 // allocation, to store the string header in an interface value. The backing
24 // array of stringRefs, on the other hand, gets reused between calls to
25 // reset, making string references free on average.
26 stringRefs []string
27 // nextStateID is the id allocated for the next StateOp.
28 nextStateID uint32
29 // multipOp indicates a multi-op such as clip.Path is being added.
30 multipOp bool
31
32 macroStack opsStack
33 stacks [_StackKind]opsStack
34 }
35
36 type OpType byte
37
38 type Shape byte
39
40 // Start at a high number for easier debugging.
41 const firstOpIndex = 200
42
43 const (
44 TypeMacro OpType = iota + firstOpIndex
45 TypeCall
46 TypeDefer
47 TypeTransform
48 TypePopTransform
49 TypePushOpacity
50 TypePopOpacity
51 TypeImage
52 TypePaint
53 TypeColor
54 TypeLinearGradient
55 TypePass
56 TypePopPass
57 TypeInput
58 TypeKeyInputHint
59 TypeSave
60 TypeLoad
61 TypeAux
62 TypeClip
63 TypePopClip
64 TypeCursor
65 TypePath
66 TypeStroke
67 TypeSemanticLabel
68 TypeSemanticDesc
69 TypeSemanticClass
70 TypeSemanticSelected
71 TypeSemanticEnabled
72 TypeActionInput
73 )
74
75 type StackID struct {
76 id uint32
77 prev uint32
78 }
79
80 // StateOp represents a saved operation snapshot to be restored later.
81 type StateOp struct {
82 id uint32
83 macroID uint32
84 ops *Ops
85 }
86
87 // opsStack tracks the integer identities of stack operations to ensure correct
88 // pairing of their push and pop methods.
89 type opsStack struct {
90 currentID uint32
91 nextID uint32
92 }
93
94 type StackKind uint8
95
96 // DecodedClipOp is the decode-side shadow of the public ClipOp.
97 type DecodedClipOp struct {
98 Bounds image.Rectangle
99 Outline bool
100 Shape Shape
101 }
102
103 const (
104 ClipStackKind StackKind = iota
105 TransStack
106 PassStackKind
107 OpacityStackKind
108 _StackKind
109 )
110
111 const (
112 ShapePath Shape = iota
113 ShapeEllipse
114 ShapeRect
115 )
116
117 const (
118 TypeMacroLen = 1 + 4 + 4
119 TypeCallLen = 1 + 4 + 4 + 4 + 4
120 TypeDeferLen = 1
121 TypeTransformLen = 1 + 1 + 4*6
122 TypePopTransformLen = 1
123 TypePushOpacityLen = 1 + 4
124 TypePopOpacityLen = 1
125 TypeRedrawLen = 1 + 8
126 TypeImageLen = 1 + 1
127 TypePaintLen = 1
128 TypeColorLen = 1 + 4
129 TypeLinearGradientLen = 1 + 8*2 + 4*2
130 TypePassLen = 1
131 TypePopPassLen = 1
132 TypeInputLen = 1
133 TypeKeyInputHintLen = 1 + 1
134 TypeSaveLen = 1 + 4
135 TypeLoadLen = 1 + 4
136 TypeAuxLen = 1
137 TypeClipLen = 1 + 4*4 + 1 + 1
138 TypePopClipLen = 1
139 TypeCursorLen = 2
140 TypePathLen = 8 + 1
141 TypeStrokeLen = 1 + 4
142 TypeSemanticLabelLen = 1
143 TypeSemanticDescLen = 1
144 TypeSemanticClassLen = 2
145 TypeSemanticSelectedLen = 2
146 TypeSemanticEnabledLen = 2
147 TypeActionInputLen = 1 + 1
148 )
149
150 func (op *DecodedClipOp) Decode(data []byte) {
151 if len(data) < TypeClipLen || OpType(data[0]) != TypeClip {
152 panic("invalid op")
153 }
154 data = data[:TypeClipLen]
155 bo := binary.LittleEndian()
156 op.Bounds.Min.X = int(int32(bo.Uint32(data[1:])))
157 op.Bounds.Min.Y = int(int32(bo.Uint32(data[5:])))
158 op.Bounds.Max.X = int(int32(bo.Uint32(data[9:])))
159 op.Bounds.Max.Y = int(int32(bo.Uint32(data[13:])))
160 op.Outline = data[17] == 1
161 op.Shape = Shape(data[18])
162 }
163
164 func ResetOps(o *Ops) {
165 o.macroStack = opsStack{}
166 o.stacks = [_StackKind]opsStack{}
167 // Leave references to the GC.
168 for i := range o.refs {
169 o.refs[i] = nil
170 }
171 for i := range o.stringRefs {
172 o.stringRefs[i] = ""
173 }
174 o.data = o.data[:0]
175 o.refs = o.refs[:0]
176 o.stringRefs = o.stringRefs[:0]
177 o.nextStateID = 0
178 o.version++
179 }
180
181 func WriteOps(o *Ops, n int) []byte {
182 if o.multipOp {
183 panic("cannot mix multi ops with single ones")
184 }
185 o.data = append(o.data, []byte{:n}...)
186 return o.data[len(o.data)-n:]
187 }
188
189 func BeginMulti(o *Ops) {
190 if o.multipOp {
191 panic("cannot interleave multi ops")
192 }
193 o.multipOp = true
194 }
195
196 func EndMulti(o *Ops) {
197 if !o.multipOp {
198 panic("cannot end non multi ops")
199 }
200 o.multipOp = false
201 }
202
203 func WriteMulti(o *Ops, n int) []byte {
204 if !o.multipOp {
205 panic("cannot use multi ops in single ops")
206 }
207 o.data = append(o.data, []byte{:n}...)
208 return o.data[len(o.data)-n:]
209 }
210
211 func PushMacro(o *Ops) StackID {
212 return o.macroStack.push()
213 }
214
215 func PopMacro(o *Ops, id StackID) {
216 o.macroStack.pop(id)
217 }
218
219 func FillMacro(o *Ops, startPC PC) {
220 pc := PCFor(o)
221 // Fill out the macro definition reserved in Record.
222 data := o.data[startPC.data:]
223 data = data[:TypeMacroLen]
224 data[0] = byte(TypeMacro)
225 bo := binary.LittleEndian()
226 bo.PutUint32(data[1:], pc.data)
227 bo.PutUint32(data[5:], pc.refs)
228 }
229
230 func AddCall(o *Ops, callOps *Ops, pc PC, end PC) {
231 data := WriteOps1(o, TypeCallLen, callOps)
232 data[0] = byte(TypeCall)
233 bo := binary.LittleEndian()
234 bo.PutUint32(data[1:], pc.data)
235 bo.PutUint32(data[5:], pc.refs)
236 bo.PutUint32(data[9:], end.data)
237 bo.PutUint32(data[13:], end.refs)
238 }
239
240 func PushOp(o *Ops, kind StackKind) (StackID, uint32) {
241 return o.stacks[kind].push(), o.macroStack.currentID
242 }
243
244 func PopOp(o *Ops, kind StackKind, sid StackID, macroID uint32) {
245 if o.macroStack.currentID != macroID {
246 panic("stack push and pop must not cross macro boundary")
247 }
248 o.stacks[kind].pop(sid)
249 }
250
251 func WriteOps1(o *Ops, n int, ref1 any) []byte {
252 o.data = append(o.data, []byte{:n}...)
253 o.refs = append(o.refs, ref1)
254 return o.data[len(o.data)-n:]
255 }
256
257 func WriteOps1String(o *Ops, n int, ref1 string) []byte {
258 o.data = append(o.data, []byte{:n}...)
259 o.stringRefs = append(o.stringRefs, ref1)
260 o.refs = append(o.refs, &o.stringRefs[len(o.stringRefs)-1])
261 return o.data[len(o.data)-n:]
262 }
263
264 func WriteOps2(o *Ops, n int, ref1, ref2 any) []byte {
265 o.data = append(o.data, []byte{:n}...)
266 o.refs = append(o.refs, ref1, ref2)
267 return o.data[len(o.data)-n:]
268 }
269
270 func WriteOps2String(o *Ops, n int, ref1 any, ref2 string) []byte {
271 o.data = append(o.data, []byte{:n}...)
272 o.stringRefs = append(o.stringRefs, ref2)
273 o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
274 return o.data[len(o.data)-n:]
275 }
276
277 func WriteOps3(o *Ops, n int, ref1, ref2, ref3 any) []byte {
278 o.data = append(o.data, []byte{:n}...)
279 o.refs = append(o.refs, ref1, ref2, ref3)
280 return o.data[len(o.data)-n:]
281 }
282
283 func PCFor(o *Ops) PC {
284 return PC{data: uint32(len(o.data)), refs: uint32(len(o.refs))}
285 }
286
287 func (s *opsStack) push() StackID {
288 s.nextID++
289 sid := StackID{
290 id: s.nextID,
291 prev: s.currentID,
292 }
293 s.currentID = s.nextID
294 return sid
295 }
296
297 func (s *opsStack) check(sid StackID) {
298 if s.currentID != sid.id {
299 panic("unbalanced operation")
300 }
301 }
302
303 func (s *opsStack) pop(sid StackID) {
304 s.check(sid)
305 s.currentID = sid.prev
306 }
307
308 // SaveOps saves the effective transformation.
309 func SaveOps(o *Ops) StateOp {
310 o.nextStateID++
311 s := StateOp{
312 ops: o,
313 id: o.nextStateID,
314 macroID: o.macroStack.currentID,
315 }
316 bo := binary.LittleEndian()
317 data := WriteOps(o, TypeSaveLen)
318 data[0] = byte(TypeSave)
319 bo.PutUint32(data[1:], s.id)
320 return s
321 }
322
323 // Load a previously saved operations state given its ID.
324 func (s StateOp) Load() {
325 bo := binary.LittleEndian()
326 data := WriteOps(s.ops, TypeLoadLen)
327 data[0] = byte(TypeLoad)
328 bo.PutUint32(data[1:], s.id)
329 }
330
331 // DecodeCommand decodes a Command from a byte slice.
332 // Uses Uint32sToBytes for the same host-endian reinterpret as the original.
333 func DecodeCommand(d []byte) Command {
334 var cmd Command
335 copy(Uint32sToBytes(cmd[:]), d)
336 return cmd
337 }
338
339 // EncodeCommand encodes a Command into a byte slice.
340 func EncodeCommand(out []byte, cmd Command) {
341 copy(out, Uint32sToBytes(cmd[:]))
342 }
343
344 func DecodeTransform(data []byte) (t Affine2D, push bool) {
345 if OpType(data[0]) != TypeTransform {
346 panic("invalid op")
347 }
348 push = data[1] != 0
349 data = data[2:]
350 data = data[:4*6]
351
352 bo := binary.LittleEndian()
353 a := math.Float32frombits(bo.Uint32(data))
354 b := math.Float32frombits(bo.Uint32(data[4*1:]))
355 c := math.Float32frombits(bo.Uint32(data[4*2:]))
356 d := math.Float32frombits(bo.Uint32(data[4*3:]))
357 e := math.Float32frombits(bo.Uint32(data[4*4:]))
358 f := math.Float32frombits(bo.Uint32(data[4*5:]))
359 return NewAffine2D(a, b, c, d, e, f), push
360 }
361
362 func DecodeOpacity(data []byte) float32 {
363 if OpType(data[0]) != TypePushOpacity {
364 panic("invalid op")
365 }
366 bo := binary.LittleEndian()
367 return math.Float32frombits(bo.Uint32(data[1:]))
368 }
369
370 // DecodeSave decodes the state id of a save op.
371 func DecodeSave(data []byte) int {
372 if OpType(data[0]) != TypeSave {
373 panic("invalid op")
374 }
375 bo := binary.LittleEndian()
376 return int(bo.Uint32(data[1:]))
377 }
378
379 // DecodeLoad decodes the state id of a load op.
380 func DecodeLoad(data []byte) int {
381 if OpType(data[0]) != TypeLoad {
382 panic("invalid op")
383 }
384 bo := binary.LittleEndian()
385 return int(bo.Uint32(data[1:]))
386 }
387
388 type opProp struct {
389 Size byte
390 NumRefs byte
391 }
392
393 func opProps() [0x100]opProp {
394 return [0x100]opProp{
395 TypeMacro: {Size: TypeMacroLen, NumRefs: 0},
396 TypeCall: {Size: TypeCallLen, NumRefs: 1},
397 TypeDefer: {Size: TypeDeferLen, NumRefs: 0},
398 TypeTransform: {Size: TypeTransformLen, NumRefs: 0},
399 TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
400 TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
401 TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
402 TypeImage: {Size: TypeImageLen, NumRefs: 2},
403 TypePaint: {Size: TypePaintLen, NumRefs: 0},
404 TypeColor: {Size: TypeColorLen, NumRefs: 0},
405 TypeLinearGradient: {Size: TypeLinearGradientLen, NumRefs: 0},
406 TypePass: {Size: TypePassLen, NumRefs: 0},
407 TypePopPass: {Size: TypePopPassLen, NumRefs: 0},
408 TypeInput: {Size: TypeInputLen, NumRefs: 1},
409 TypeKeyInputHint: {Size: TypeKeyInputHintLen, NumRefs: 1},
410 TypeSave: {Size: TypeSaveLen, NumRefs: 0},
411 TypeLoad: {Size: TypeLoadLen, NumRefs: 0},
412 TypeAux: {Size: TypeAuxLen, NumRefs: 0},
413 TypeClip: {Size: TypeClipLen, NumRefs: 0},
414 TypePopClip: {Size: TypePopClipLen, NumRefs: 0},
415 TypeCursor: {Size: TypeCursorLen, NumRefs: 0},
416 TypePath: {Size: TypePathLen, NumRefs: 0},
417 TypeStroke: {Size: TypeStrokeLen, NumRefs: 0},
418 TypeSemanticLabel: {Size: TypeSemanticLabelLen, NumRefs: 1},
419 TypeSemanticDesc: {Size: TypeSemanticDescLen, NumRefs: 1},
420 TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0},
421 TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0},
422 TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0},
423 TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0},
424 }
425 }
426
427 func (t OpType) props() (size, numRefs uint32) {
428 v := opProps()[t]
429 return uint32(v.Size), uint32(v.NumRefs)
430 }
431
432 func (t OpType) Size() uint32 {
433 return uint32(opProps()[t].Size)
434 }
435
436 func (t OpType) NumRefs() uint32 {
437 return uint32(opProps()[t].NumRefs)
438 }
439
440 func (t OpType) String() string {
441 switch t {
442 case TypeMacro:
443 return "Macro"
444 case TypeCall:
445 return "Call"
446 case TypeDefer:
447 return "Defer"
448 case TypeTransform:
449 return "Transform"
450 case TypePopTransform:
451 return "PopTransform"
452 case TypePushOpacity:
453 return "PushOpacity"
454 case TypePopOpacity:
455 return "PopOpacity"
456 case TypeImage:
457 return "Image"
458 case TypePaint:
459 return "Paint"
460 case TypeColor:
461 return "Color"
462 case TypeLinearGradient:
463 return "LinearGradient"
464 case TypePass:
465 return "Pass"
466 case TypePopPass:
467 return "PopPass"
468 case TypeInput:
469 return "Input"
470 case TypeKeyInputHint:
471 return "KeyInputHint"
472 case TypeSave:
473 return "Save"
474 case TypeLoad:
475 return "Load"
476 case TypeAux:
477 return "Aux"
478 case TypeClip:
479 return "Clip"
480 case TypePopClip:
481 return "PopClip"
482 case TypeCursor:
483 return "Cursor"
484 case TypePath:
485 return "Path"
486 case TypeStroke:
487 return "Stroke"
488 case TypeSemanticLabel:
489 return "SemanticDescription"
490 default:
491 panic("unknown OpType")
492 }
493 }
494
495 // --- Reader (from reader.go) ---
496
497 // Reader parses an ops list.
498 type Reader struct {
499 pc PC
500 stack []readerMacro
501 ops *Ops
502 deferOps Ops
503 deferDone bool
504 }
505
506 // EncodedOp represents an encoded op returned by Reader.
507 type EncodedOp struct {
508 Key Key
509 Data []byte
510 Refs []any
511 }
512
513 // Key is a unique key for a given op.
514 type Key struct {
515 ops *Ops
516 pc uint32
517 version uint32
518 }
519
520 // Shadow of op.MacroOp.
521 type macroOp struct {
522 ops *Ops
523 start PC
524 end PC
525 }
526
527 // PC is an instruction counter for an operation list.
528 type PC struct {
529 data uint32
530 refs uint32
531 }
532
533 type readerMacro struct {
534 ops *Ops
535 retPC PC
536 endPC PC
537 }
538
539 type opMacroDef struct {
540 endpc PC
541 }
542
543 func (pc PC) Add(op OpType) PC {
544 size, numRefs := op.props()
545 return PC{
546 data: pc.data + size,
547 refs: pc.refs + numRefs,
548 }
549 }
550
551 // ResetReader starts reading from the beginning of ops.
552 func (r *Reader) ResetReader(ops *Ops) {
553 r.ResetAt(ops, PC{})
554 }
555
556 // ResetAt is like ResetReader, except it starts reading from pc.
557 func (r *Reader) ResetAt(ops *Ops, pc PC) {
558 r.stack = r.stack[:0]
559 ResetOps(&r.deferOps)
560 r.deferDone = false
561 r.pc = pc
562 r.ops = ops
563 }
564
565 func (r *Reader) Decode() (EncodedOp, bool) {
566 if r.ops == nil {
567 return EncodedOp{}, false
568 }
569 deferring := false
570 for {
571 if len(r.stack) > 0 {
572 b := r.stack[len(r.stack)-1]
573 if r.pc == b.endPC {
574 r.ops = b.ops
575 r.pc = b.retPC
576 r.stack = r.stack[:len(r.stack)-1]
577 continue
578 }
579 }
580 data := r.ops.data
581 data = data[r.pc.data:]
582 refs := r.ops.refs
583 if len(data) == 0 {
584 if r.deferDone {
585 return EncodedOp{}, false
586 }
587 r.deferDone = true
588 // Execute deferred macros.
589 r.ops = &r.deferOps
590 r.pc = PC{}
591 continue
592 }
593 key := Key{ops: r.ops, pc: r.pc.data, version: r.ops.version}
594 t := OpType(data[0])
595 n, nrefs := t.props()
596 data = data[:n]
597 refs = refs[r.pc.refs:]
598 refs = refs[:nrefs]
599 switch t {
600 case TypeDefer:
601 deferring = true
602 r.pc.data += n
603 r.pc.refs += nrefs
604 continue
605 case TypeAux:
606 // An Aux operation is always wrapped in a macro, and
607 // its length is the remaining space.
608 block := r.stack[len(r.stack)-1]
609 n += block.endPC.data - r.pc.data - TypeAuxLen
610 data = data[:n]
611 case TypeCall:
612 if deferring {
613 deferring = false
614 // Copy macro for deferred execution.
615 if nrefs != 1 {
616 panic("internal error: unexpected number of macro refs")
617 }
618 deferData := WriteOps1(&r.deferOps, int(n), refs[0])
619 copy(deferData, data)
620 r.pc.data += n
621 r.pc.refs += nrefs
622 continue
623 }
624 var op macroOp
625 op.decode(data, refs)
626 retPC := r.pc
627 retPC.data += n
628 retPC.refs += nrefs
629 r.stack = append(r.stack, readerMacro{
630 ops: r.ops,
631 retPC: retPC,
632 endPC: op.end,
633 })
634 r.ops = op.ops
635 r.pc = op.start
636 continue
637 case TypeMacro:
638 var op opMacroDef
639 op.decode(data)
640 if op.endpc != (PC{}) {
641 r.pc = op.endpc
642 } else {
643 // Treat an incomplete macro as containing all remaining ops.
644 r.pc.data = uint32(len(r.ops.data))
645 r.pc.refs = uint32(len(r.ops.refs))
646 }
647 continue
648 }
649 r.pc.data += n
650 r.pc.refs += nrefs
651 return EncodedOp{Key: key, Data: data, Refs: refs}, true
652 }
653 }
654
655 func (op *opMacroDef) decode(data []byte) {
656 if len(data) < TypeMacroLen || OpType(data[0]) != TypeMacro {
657 panic("invalid op")
658 }
659 bo := binary.LittleEndian()
660 data = data[:TypeMacroLen]
661 op.endpc.data = bo.Uint32(data[1:])
662 op.endpc.refs = bo.Uint32(data[5:])
663 }
664
665 func (m *macroOp) decode(data []byte, refs []any) {
666 if len(data) < TypeCallLen || len(refs) < 1 || OpType(data[0]) != TypeCall {
667 panic("invalid op")
668 }
669 bo := binary.LittleEndian()
670 data = data[:TypeCallLen]
671
672 m.ops = refs[0].(*Ops)
673 m.start.data = bo.Uint32(data[1:])
674 m.start.refs = bo.Uint32(data[5:])
675 m.end.data = bo.Uint32(data[9:])
676 m.end.refs = bo.Uint32(data[13:])
677 }
678