github.com/utopiagio/gio@v0.0.8/internal/ops/ops.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package ops 4 5 import ( 6 "encoding/binary" 7 "image" 8 "math" 9 10 "github.com/utopiagio/gio/f32" 11 "github.com/utopiagio/gio/internal/byteslice" 12 "github.com/utopiagio/gio/internal/scene" 13 ) 14 15 type Ops struct { 16 // version is incremented at each Reset. 17 version uint32 18 // data contains the serialized operations. 19 data []byte 20 // refs hold external references for operations. 21 refs []interface{} 22 // stringRefs provides space for string references, pointers to which will 23 // be stored in refs. Storing a string directly in refs would cause a heap 24 // allocation, to store the string header in an interface value. The backing 25 // array of stringRefs, on the other hand, gets reused between calls to 26 // reset, making string references free on average. 27 // 28 // Appending to stringRefs might reallocate the backing array, which will 29 // leave pointers to the old array in refs. This temporarily causes a slight 30 // increase in memory usage, but this, too, amortizes away as the capacity 31 // of stringRefs approaches its stable maximum. 32 stringRefs []string 33 // nextStateID is the id allocated for the next 34 // StateOp. 35 nextStateID uint32 36 // multipOp indicates a multi-op such as clip.Path is being added. 37 multipOp bool 38 39 macroStack stack 40 stacks [_StackKind]stack 41 } 42 43 type OpType byte 44 45 type Shape byte 46 47 // Start at a high number for easier debugging. 48 const firstOpIndex = 200 49 50 const ( 51 TypeMacro OpType = iota + firstOpIndex 52 TypeCall 53 TypeDefer 54 TypeTransform 55 TypePopTransform 56 TypePushOpacity 57 TypePopOpacity 58 TypeImage 59 TypePaint 60 TypeColor 61 TypeLinearGradient 62 TypePass 63 TypePopPass 64 TypeInput 65 TypeKeyInputHint 66 TypeSave 67 TypeLoad 68 TypeAux 69 TypeClip 70 TypePopClip 71 TypeCursor 72 TypePath 73 TypeStroke 74 TypeSemanticLabel 75 TypeSemanticDesc 76 TypeSemanticClass 77 TypeSemanticSelected 78 TypeSemanticEnabled 79 TypeActionInput 80 ) 81 82 type StackID struct { 83 id uint32 84 prev uint32 85 } 86 87 // StateOp represents a saved operation snapshot to be restored 88 // later. 89 type StateOp struct { 90 id uint32 91 macroID uint32 92 ops *Ops 93 } 94 95 // stack tracks the integer identities of stack operations to ensure correct 96 // pairing of their push and pop methods. 97 type stack struct { 98 currentID uint32 99 nextID uint32 100 } 101 102 type StackKind uint8 103 104 // ClipOp is the shadow of clip.Op. 105 type ClipOp struct { 106 Bounds image.Rectangle 107 Outline bool 108 Shape Shape 109 } 110 111 const ( 112 ClipStack StackKind = iota 113 TransStack 114 PassStack 115 OpacityStack 116 _StackKind 117 ) 118 119 const ( 120 Path Shape = iota 121 Ellipse 122 Rect 123 ) 124 125 const ( 126 TypeMacroLen = 1 + 4 + 4 127 TypeCallLen = 1 + 4 + 4 + 4 + 4 128 TypeDeferLen = 1 129 TypeTransformLen = 1 + 1 + 4*6 130 TypePopTransformLen = 1 131 TypePushOpacityLen = 1 + 4 132 TypePopOpacityLen = 1 133 TypeRedrawLen = 1 + 8 134 TypeImageLen = 1 + 1 135 TypePaintLen = 1 136 TypeColorLen = 1 + 4 137 TypeLinearGradientLen = 1 + 8*2 + 4*2 138 TypePassLen = 1 139 TypePopPassLen = 1 140 TypeInputLen = 1 141 TypeKeyInputHintLen = 1 + 1 142 TypeSaveLen = 1 + 4 143 TypeLoadLen = 1 + 4 144 TypeAuxLen = 1 145 TypeClipLen = 1 + 4*4 + 1 + 1 146 TypePopClipLen = 1 147 TypeCursorLen = 2 148 TypePathLen = 8 + 1 149 TypeStrokeLen = 1 + 4 150 TypeSemanticLabelLen = 1 151 TypeSemanticDescLen = 1 152 TypeSemanticClassLen = 2 153 TypeSemanticSelectedLen = 2 154 TypeSemanticEnabledLen = 2 155 TypeActionInputLen = 1 + 1 156 ) 157 158 func (op *ClipOp) Decode(data []byte) { 159 if len(data) < TypeClipLen || OpType(data[0]) != TypeClip { 160 panic("invalid op") 161 } 162 data = data[:TypeClipLen] 163 bo := binary.LittleEndian 164 op.Bounds.Min.X = int(int32(bo.Uint32(data[1:]))) 165 op.Bounds.Min.Y = int(int32(bo.Uint32(data[5:]))) 166 op.Bounds.Max.X = int(int32(bo.Uint32(data[9:]))) 167 op.Bounds.Max.Y = int(int32(bo.Uint32(data[13:]))) 168 op.Outline = data[17] == 1 169 op.Shape = Shape(data[18]) 170 } 171 172 func Reset(o *Ops) { 173 o.macroStack = stack{} 174 o.stacks = [_StackKind]stack{} 175 // Leave references to the GC. 176 for i := range o.refs { 177 o.refs[i] = nil 178 } 179 for i := range o.stringRefs { 180 o.stringRefs[i] = "" 181 } 182 o.data = o.data[:0] 183 o.refs = o.refs[:0] 184 o.stringRefs = o.stringRefs[:0] 185 o.nextStateID = 0 186 o.version++ 187 } 188 189 func Write(o *Ops, n int) []byte { 190 if o.multipOp { 191 panic("cannot mix multi ops with single ones") 192 } 193 o.data = append(o.data, make([]byte, n)...) 194 return o.data[len(o.data)-n:] 195 } 196 197 func BeginMulti(o *Ops) { 198 if o.multipOp { 199 panic("cannot interleave multi ops") 200 } 201 o.multipOp = true 202 } 203 204 func EndMulti(o *Ops) { 205 if !o.multipOp { 206 panic("cannot end non multi ops") 207 } 208 o.multipOp = false 209 } 210 211 func WriteMulti(o *Ops, n int) []byte { 212 if !o.multipOp { 213 panic("cannot use multi ops in single ops") 214 } 215 o.data = append(o.data, make([]byte, n)...) 216 return o.data[len(o.data)-n:] 217 } 218 219 func PushMacro(o *Ops) StackID { 220 return o.macroStack.push() 221 } 222 223 func PopMacro(o *Ops, id StackID) { 224 o.macroStack.pop(id) 225 } 226 227 func FillMacro(o *Ops, startPC PC) { 228 pc := PCFor(o) 229 // Fill out the macro definition reserved in Record. 230 data := o.data[startPC.data:] 231 data = data[:TypeMacroLen] 232 data[0] = byte(TypeMacro) 233 bo := binary.LittleEndian 234 bo.PutUint32(data[1:], uint32(pc.data)) 235 bo.PutUint32(data[5:], uint32(pc.refs)) 236 } 237 238 func AddCall(o *Ops, callOps *Ops, pc PC, end PC) { 239 data := Write1(o, TypeCallLen, callOps) 240 data[0] = byte(TypeCall) 241 bo := binary.LittleEndian 242 bo.PutUint32(data[1:], uint32(pc.data)) 243 bo.PutUint32(data[5:], uint32(pc.refs)) 244 bo.PutUint32(data[9:], uint32(end.data)) 245 bo.PutUint32(data[13:], uint32(end.refs)) 246 } 247 248 func PushOp(o *Ops, kind StackKind) (StackID, uint32) { 249 return o.stacks[kind].push(), o.macroStack.currentID 250 } 251 252 func PopOp(o *Ops, kind StackKind, sid StackID, macroID uint32) { 253 if o.macroStack.currentID != macroID { 254 panic("stack push and pop must not cross macro boundary") 255 } 256 o.stacks[kind].pop(sid) 257 } 258 259 func Write1(o *Ops, n int, ref1 interface{}) []byte { 260 o.data = append(o.data, make([]byte, n)...) 261 o.refs = append(o.refs, ref1) 262 return o.data[len(o.data)-n:] 263 } 264 265 func Write1String(o *Ops, n int, ref1 string) []byte { 266 o.data = append(o.data, make([]byte, n)...) 267 o.stringRefs = append(o.stringRefs, ref1) 268 o.refs = append(o.refs, &o.stringRefs[len(o.stringRefs)-1]) 269 return o.data[len(o.data)-n:] 270 } 271 272 func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte { 273 o.data = append(o.data, make([]byte, n)...) 274 o.refs = append(o.refs, ref1, ref2) 275 return o.data[len(o.data)-n:] 276 } 277 278 func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte { 279 o.data = append(o.data, make([]byte, n)...) 280 o.stringRefs = append(o.stringRefs, ref2) 281 o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1]) 282 return o.data[len(o.data)-n:] 283 } 284 285 func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte { 286 o.data = append(o.data, make([]byte, n)...) 287 o.refs = append(o.refs, ref1, ref2, ref3) 288 return o.data[len(o.data)-n:] 289 } 290 291 func PCFor(o *Ops) PC { 292 return PC{data: uint32(len(o.data)), refs: uint32(len(o.refs))} 293 } 294 295 func (s *stack) push() StackID { 296 s.nextID++ 297 sid := StackID{ 298 id: s.nextID, 299 prev: s.currentID, 300 } 301 s.currentID = s.nextID 302 return sid 303 } 304 305 func (s *stack) check(sid StackID) { 306 if s.currentID != sid.id { 307 panic("unbalanced operation") 308 } 309 } 310 311 func (s *stack) pop(sid StackID) { 312 s.check(sid) 313 s.currentID = sid.prev 314 } 315 316 // Save the effective transformation. 317 func Save(o *Ops) StateOp { 318 o.nextStateID++ 319 s := StateOp{ 320 ops: o, 321 id: o.nextStateID, 322 macroID: o.macroStack.currentID, 323 } 324 bo := binary.LittleEndian 325 data := Write(o, TypeSaveLen) 326 data[0] = byte(TypeSave) 327 bo.PutUint32(data[1:], uint32(s.id)) 328 return s 329 } 330 331 // Load a previously saved operations state given 332 // its ID. 333 func (s StateOp) Load() { 334 bo := binary.LittleEndian 335 data := Write(s.ops, TypeLoadLen) 336 data[0] = byte(TypeLoad) 337 bo.PutUint32(data[1:], uint32(s.id)) 338 } 339 340 func DecodeCommand(d []byte) scene.Command { 341 var cmd scene.Command 342 copy(byteslice.Uint32(cmd[:]), d) 343 return cmd 344 } 345 346 func EncodeCommand(out []byte, cmd scene.Command) { 347 copy(out, byteslice.Uint32(cmd[:])) 348 } 349 350 func DecodeTransform(data []byte) (t f32.Affine2D, push bool) { 351 if OpType(data[0]) != TypeTransform { 352 panic("invalid op") 353 } 354 push = data[1] != 0 355 data = data[2:] 356 data = data[:4*6] 357 358 bo := binary.LittleEndian 359 a := math.Float32frombits(bo.Uint32(data)) 360 b := math.Float32frombits(bo.Uint32(data[4*1:])) 361 c := math.Float32frombits(bo.Uint32(data[4*2:])) 362 d := math.Float32frombits(bo.Uint32(data[4*3:])) 363 e := math.Float32frombits(bo.Uint32(data[4*4:])) 364 f := math.Float32frombits(bo.Uint32(data[4*5:])) 365 return f32.NewAffine2D(a, b, c, d, e, f), push 366 } 367 368 func DecodeOpacity(data []byte) float32 { 369 if OpType(data[0]) != TypePushOpacity { 370 panic("invalid op") 371 } 372 bo := binary.LittleEndian 373 return math.Float32frombits(bo.Uint32(data[1:])) 374 } 375 376 // DecodeSave decodes the state id of a save op. 377 func DecodeSave(data []byte) int { 378 if OpType(data[0]) != TypeSave { 379 panic("invalid op") 380 } 381 bo := binary.LittleEndian 382 return int(bo.Uint32(data[1:])) 383 } 384 385 // DecodeLoad decodes the state id of a load op. 386 func DecodeLoad(data []byte) int { 387 if OpType(data[0]) != TypeLoad { 388 panic("invalid op") 389 } 390 bo := binary.LittleEndian 391 return int(bo.Uint32(data[1:])) 392 } 393 394 type opProp struct { 395 Size byte 396 NumRefs byte 397 } 398 399 var opProps = [0x100]opProp{ 400 TypeMacro: {Size: TypeMacroLen, NumRefs: 0}, 401 TypeCall: {Size: TypeCallLen, NumRefs: 1}, 402 TypeDefer: {Size: TypeDeferLen, NumRefs: 0}, 403 TypeTransform: {Size: TypeTransformLen, NumRefs: 0}, 404 TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0}, 405 TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0}, 406 TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0}, 407 TypeImage: {Size: TypeImageLen, NumRefs: 2}, 408 TypePaint: {Size: TypePaintLen, NumRefs: 0}, 409 TypeColor: {Size: TypeColorLen, NumRefs: 0}, 410 TypeLinearGradient: {Size: TypeLinearGradientLen, NumRefs: 0}, 411 TypePass: {Size: TypePassLen, NumRefs: 0}, 412 TypePopPass: {Size: TypePopPassLen, NumRefs: 0}, 413 TypeInput: {Size: TypeInputLen, NumRefs: 1}, 414 TypeKeyInputHint: {Size: TypeKeyInputHintLen, NumRefs: 1}, 415 TypeSave: {Size: TypeSaveLen, NumRefs: 0}, 416 TypeLoad: {Size: TypeLoadLen, NumRefs: 0}, 417 TypeAux: {Size: TypeAuxLen, NumRefs: 0}, 418 TypeClip: {Size: TypeClipLen, NumRefs: 0}, 419 TypePopClip: {Size: TypePopClipLen, NumRefs: 0}, 420 TypeCursor: {Size: TypeCursorLen, NumRefs: 0}, 421 TypePath: {Size: TypePathLen, NumRefs: 0}, 422 TypeStroke: {Size: TypeStrokeLen, NumRefs: 0}, 423 TypeSemanticLabel: {Size: TypeSemanticLabelLen, NumRefs: 1}, 424 TypeSemanticDesc: {Size: TypeSemanticDescLen, NumRefs: 1}, 425 TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0}, 426 TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0}, 427 TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0}, 428 TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0}, 429 } 430 431 func (t OpType) props() (size, numRefs uint32) { 432 v := opProps[t] 433 return uint32(v.Size), uint32(v.NumRefs) 434 } 435 436 func (t OpType) Size() uint32 { 437 return uint32(opProps[t].Size) 438 } 439 440 func (t OpType) NumRefs() uint32 { 441 return uint32(opProps[t].NumRefs) 442 } 443 444 func (t OpType) String() string { 445 switch t { 446 case TypeMacro: 447 return "Macro" 448 case TypeCall: 449 return "Call" 450 case TypeDefer: 451 return "Defer" 452 case TypeTransform: 453 return "Transform" 454 case TypePopTransform: 455 return "PopTransform" 456 case TypePushOpacity: 457 return "PushOpacity" 458 case TypePopOpacity: 459 return "PopOpacity" 460 case TypeImage: 461 return "Image" 462 case TypePaint: 463 return "Paint" 464 case TypeColor: 465 return "Color" 466 case TypeLinearGradient: 467 return "LinearGradient" 468 case TypePass: 469 return "Pass" 470 case TypePopPass: 471 return "PopPass" 472 case TypeInput: 473 return "Input" 474 case TypeKeyInputHint: 475 return "KeyInputHint" 476 case TypeSave: 477 return "Save" 478 case TypeLoad: 479 return "Load" 480 case TypeAux: 481 return "Aux" 482 case TypeClip: 483 return "Clip" 484 case TypePopClip: 485 return "PopClip" 486 case TypeCursor: 487 return "Cursor" 488 case TypePath: 489 return "Path" 490 case TypeStroke: 491 return "Stroke" 492 case TypeSemanticLabel: 493 return "SemanticDescription" 494 default: 495 panic("unknown OpType") 496 } 497 }