123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- // SPDX-License-Identifier: Unlicense OR MIT
- package ops
- import (
- "encoding/binary"
- "image"
- "math"
- "gioui.org/f32"
- "gioui.org/internal/byteslice"
- "gioui.org/internal/scene"
- )
- type Ops struct {
- // version is incremented at each Reset.
- version uint32
- // data contains the serialized operations.
- data []byte
- // refs hold external references for operations.
- refs []interface{}
- // stringRefs provides space for string references, pointers to which will
- // be stored in refs. Storing a string directly in refs would cause a heap
- // allocation, to store the string header in an interface value. The backing
- // array of stringRefs, on the other hand, gets reused between calls to
- // reset, making string references free on average.
- //
- // Appending to stringRefs might reallocate the backing array, which will
- // leave pointers to the old array in refs. This temporarily causes a slight
- // increase in memory usage, but this, too, amortizes away as the capacity
- // of stringRefs approaches its stable maximum.
- stringRefs []string
- // nextStateID is the id allocated for the next
- // StateOp.
- nextStateID uint32
- // multipOp indicates a multi-op such as clip.Path is being added.
- multipOp bool
- macroStack stack
- stacks [_StackKind]stack
- }
- type OpType byte
- type Shape byte
- // Start at a high number for easier debugging.
- const firstOpIndex = 200
- const (
- TypeMacro OpType = iota + firstOpIndex
- TypeCall
- TypeDefer
- TypeTransform
- TypePopTransform
- TypePushOpacity
- TypePopOpacity
- TypeImage
- TypePaint
- TypeColor
- TypeLinearGradient
- TypePass
- TypePopPass
- TypeInput
- TypeKeyInputHint
- TypeSave
- TypeLoad
- TypeAux
- TypeClip
- TypePopClip
- TypeCursor
- TypePath
- TypeStroke
- TypeSemanticLabel
- TypeSemanticDesc
- TypeSemanticClass
- TypeSemanticSelected
- TypeSemanticEnabled
- TypeActionInput
- )
- type StackID struct {
- id uint32
- prev uint32
- }
- // StateOp represents a saved operation snapshot to be restored
- // later.
- type StateOp struct {
- id uint32
- macroID uint32
- ops *Ops
- }
- // stack tracks the integer identities of stack operations to ensure correct
- // pairing of their push and pop methods.
- type stack struct {
- currentID uint32
- nextID uint32
- }
- type StackKind uint8
- // ClipOp is the shadow of clip.Op.
- type ClipOp struct {
- Bounds image.Rectangle
- Outline bool
- Shape Shape
- }
- const (
- ClipStack StackKind = iota
- TransStack
- PassStack
- OpacityStack
- _StackKind
- )
- const (
- Path Shape = iota
- Ellipse
- Rect
- )
- const (
- TypeMacroLen = 1 + 4 + 4
- TypeCallLen = 1 + 4 + 4 + 4 + 4
- TypeDeferLen = 1
- TypeTransformLen = 1 + 1 + 4*6
- TypePopTransformLen = 1
- TypePushOpacityLen = 1 + 4
- TypePopOpacityLen = 1
- TypeRedrawLen = 1 + 8
- TypeImageLen = 1 + 1
- TypePaintLen = 1
- TypeColorLen = 1 + 4
- TypeLinearGradientLen = 1 + 8*2 + 4*2
- TypePassLen = 1
- TypePopPassLen = 1
- TypeInputLen = 1
- TypeKeyInputHintLen = 1 + 1
- TypeSaveLen = 1 + 4
- TypeLoadLen = 1 + 4
- TypeAuxLen = 1
- TypeClipLen = 1 + 4*4 + 1 + 1
- TypePopClipLen = 1
- TypeCursorLen = 2
- TypePathLen = 8 + 1
- TypeStrokeLen = 1 + 4
- TypeSemanticLabelLen = 1
- TypeSemanticDescLen = 1
- TypeSemanticClassLen = 2
- TypeSemanticSelectedLen = 2
- TypeSemanticEnabledLen = 2
- TypeActionInputLen = 1 + 1
- )
- func (op *ClipOp) Decode(data []byte) {
- if len(data) < TypeClipLen || OpType(data[0]) != TypeClip {
- panic("invalid op")
- }
- data = data[:TypeClipLen]
- bo := binary.LittleEndian
- op.Bounds.Min.X = int(int32(bo.Uint32(data[1:])))
- op.Bounds.Min.Y = int(int32(bo.Uint32(data[5:])))
- op.Bounds.Max.X = int(int32(bo.Uint32(data[9:])))
- op.Bounds.Max.Y = int(int32(bo.Uint32(data[13:])))
- op.Outline = data[17] == 1
- op.Shape = Shape(data[18])
- }
- func Reset(o *Ops) {
- o.macroStack = stack{}
- o.stacks = [_StackKind]stack{}
- // Leave references to the GC.
- for i := range o.refs {
- o.refs[i] = nil
- }
- for i := range o.stringRefs {
- o.stringRefs[i] = ""
- }
- o.data = o.data[:0]
- o.refs = o.refs[:0]
- o.stringRefs = o.stringRefs[:0]
- o.nextStateID = 0
- o.version++
- }
- func Write(o *Ops, n int) []byte {
- if o.multipOp {
- panic("cannot mix multi ops with single ones")
- }
- o.data = append(o.data, make([]byte, n)...)
- return o.data[len(o.data)-n:]
- }
- func BeginMulti(o *Ops) {
- if o.multipOp {
- panic("cannot interleave multi ops")
- }
- o.multipOp = true
- }
- func EndMulti(o *Ops) {
- if !o.multipOp {
- panic("cannot end non multi ops")
- }
- o.multipOp = false
- }
- func WriteMulti(o *Ops, n int) []byte {
- if !o.multipOp {
- panic("cannot use multi ops in single ops")
- }
- o.data = append(o.data, make([]byte, n)...)
- return o.data[len(o.data)-n:]
- }
- func PushMacro(o *Ops) StackID {
- return o.macroStack.push()
- }
- func PopMacro(o *Ops, id StackID) {
- o.macroStack.pop(id)
- }
- func FillMacro(o *Ops, startPC PC) {
- pc := PCFor(o)
- // Fill out the macro definition reserved in Record.
- data := o.data[startPC.data:]
- data = data[:TypeMacroLen]
- data[0] = byte(TypeMacro)
- bo := binary.LittleEndian
- bo.PutUint32(data[1:], uint32(pc.data))
- bo.PutUint32(data[5:], uint32(pc.refs))
- }
- func AddCall(o *Ops, callOps *Ops, pc PC, end PC) {
- data := Write1(o, TypeCallLen, callOps)
- data[0] = byte(TypeCall)
- bo := binary.LittleEndian
- bo.PutUint32(data[1:], uint32(pc.data))
- bo.PutUint32(data[5:], uint32(pc.refs))
- bo.PutUint32(data[9:], uint32(end.data))
- bo.PutUint32(data[13:], uint32(end.refs))
- }
- func PushOp(o *Ops, kind StackKind) (StackID, uint32) {
- return o.stacks[kind].push(), o.macroStack.currentID
- }
- func PopOp(o *Ops, kind StackKind, sid StackID, macroID uint32) {
- if o.macroStack.currentID != macroID {
- panic("stack push and pop must not cross macro boundary")
- }
- o.stacks[kind].pop(sid)
- }
- func Write1(o *Ops, n int, ref1 interface{}) []byte {
- o.data = append(o.data, make([]byte, n)...)
- o.refs = append(o.refs, ref1)
- return o.data[len(o.data)-n:]
- }
- func Write1String(o *Ops, n int, ref1 string) []byte {
- o.data = append(o.data, make([]byte, n)...)
- o.stringRefs = append(o.stringRefs, ref1)
- o.refs = append(o.refs, &o.stringRefs[len(o.stringRefs)-1])
- return o.data[len(o.data)-n:]
- }
- func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte {
- o.data = append(o.data, make([]byte, n)...)
- o.refs = append(o.refs, ref1, ref2)
- return o.data[len(o.data)-n:]
- }
- func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte {
- o.data = append(o.data, make([]byte, n)...)
- o.stringRefs = append(o.stringRefs, ref2)
- o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
- return o.data[len(o.data)-n:]
- }
- func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
- o.data = append(o.data, make([]byte, n)...)
- o.refs = append(o.refs, ref1, ref2, ref3)
- return o.data[len(o.data)-n:]
- }
- func PCFor(o *Ops) PC {
- return PC{data: uint32(len(o.data)), refs: uint32(len(o.refs))}
- }
- func (s *stack) push() StackID {
- s.nextID++
- sid := StackID{
- id: s.nextID,
- prev: s.currentID,
- }
- s.currentID = s.nextID
- return sid
- }
- func (s *stack) check(sid StackID) {
- if s.currentID != sid.id {
- panic("unbalanced operation")
- }
- }
- func (s *stack) pop(sid StackID) {
- s.check(sid)
- s.currentID = sid.prev
- }
- // Save the effective transformation.
- func Save(o *Ops) StateOp {
- o.nextStateID++
- s := StateOp{
- ops: o,
- id: o.nextStateID,
- macroID: o.macroStack.currentID,
- }
- bo := binary.LittleEndian
- data := Write(o, TypeSaveLen)
- data[0] = byte(TypeSave)
- bo.PutUint32(data[1:], uint32(s.id))
- return s
- }
- // Load a previously saved operations state given
- // its ID.
- func (s StateOp) Load() {
- bo := binary.LittleEndian
- data := Write(s.ops, TypeLoadLen)
- data[0] = byte(TypeLoad)
- bo.PutUint32(data[1:], uint32(s.id))
- }
- func DecodeCommand(d []byte) scene.Command {
- var cmd scene.Command
- copy(byteslice.Uint32(cmd[:]), d)
- return cmd
- }
- func EncodeCommand(out []byte, cmd scene.Command) {
- copy(out, byteslice.Uint32(cmd[:]))
- }
- func DecodeTransform(data []byte) (t f32.Affine2D, push bool) {
- if OpType(data[0]) != TypeTransform {
- panic("invalid op")
- }
- push = data[1] != 0
- data = data[2:]
- data = data[:4*6]
- bo := binary.LittleEndian
- a := math.Float32frombits(bo.Uint32(data))
- b := math.Float32frombits(bo.Uint32(data[4*1:]))
- c := math.Float32frombits(bo.Uint32(data[4*2:]))
- d := math.Float32frombits(bo.Uint32(data[4*3:]))
- e := math.Float32frombits(bo.Uint32(data[4*4:]))
- f := math.Float32frombits(bo.Uint32(data[4*5:]))
- return f32.NewAffine2D(a, b, c, d, e, f), push
- }
- func DecodeOpacity(data []byte) float32 {
- if OpType(data[0]) != TypePushOpacity {
- panic("invalid op")
- }
- bo := binary.LittleEndian
- return math.Float32frombits(bo.Uint32(data[1:]))
- }
- // DecodeSave decodes the state id of a save op.
- func DecodeSave(data []byte) int {
- if OpType(data[0]) != TypeSave {
- panic("invalid op")
- }
- bo := binary.LittleEndian
- return int(bo.Uint32(data[1:]))
- }
- // DecodeLoad decodes the state id of a load op.
- func DecodeLoad(data []byte) int {
- if OpType(data[0]) != TypeLoad {
- panic("invalid op")
- }
- bo := binary.LittleEndian
- return int(bo.Uint32(data[1:]))
- }
- type opProp struct {
- Size byte
- NumRefs byte
- }
- var opProps = [0x100]opProp{
- TypeMacro: {Size: TypeMacroLen, NumRefs: 0},
- TypeCall: {Size: TypeCallLen, NumRefs: 1},
- TypeDefer: {Size: TypeDeferLen, NumRefs: 0},
- TypeTransform: {Size: TypeTransformLen, NumRefs: 0},
- TypePopTransform: {Size: TypePopTransformLen, NumRefs: 0},
- TypePushOpacity: {Size: TypePushOpacityLen, NumRefs: 0},
- TypePopOpacity: {Size: TypePopOpacityLen, NumRefs: 0},
- TypeImage: {Size: TypeImageLen, NumRefs: 2},
- TypePaint: {Size: TypePaintLen, NumRefs: 0},
- TypeColor: {Size: TypeColorLen, NumRefs: 0},
- TypeLinearGradient: {Size: TypeLinearGradientLen, NumRefs: 0},
- TypePass: {Size: TypePassLen, NumRefs: 0},
- TypePopPass: {Size: TypePopPassLen, NumRefs: 0},
- TypeInput: {Size: TypeInputLen, NumRefs: 1},
- TypeKeyInputHint: {Size: TypeKeyInputHintLen, NumRefs: 1},
- TypeSave: {Size: TypeSaveLen, NumRefs: 0},
- TypeLoad: {Size: TypeLoadLen, NumRefs: 0},
- TypeAux: {Size: TypeAuxLen, NumRefs: 0},
- TypeClip: {Size: TypeClipLen, NumRefs: 0},
- TypePopClip: {Size: TypePopClipLen, NumRefs: 0},
- TypeCursor: {Size: TypeCursorLen, NumRefs: 0},
- TypePath: {Size: TypePathLen, NumRefs: 0},
- TypeStroke: {Size: TypeStrokeLen, NumRefs: 0},
- TypeSemanticLabel: {Size: TypeSemanticLabelLen, NumRefs: 1},
- TypeSemanticDesc: {Size: TypeSemanticDescLen, NumRefs: 1},
- TypeSemanticClass: {Size: TypeSemanticClassLen, NumRefs: 0},
- TypeSemanticSelected: {Size: TypeSemanticSelectedLen, NumRefs: 0},
- TypeSemanticEnabled: {Size: TypeSemanticEnabledLen, NumRefs: 0},
- TypeActionInput: {Size: TypeActionInputLen, NumRefs: 0},
- }
- func (t OpType) props() (size, numRefs uint32) {
- v := opProps[t]
- return uint32(v.Size), uint32(v.NumRefs)
- }
- func (t OpType) Size() uint32 {
- return uint32(opProps[t].Size)
- }
- func (t OpType) NumRefs() uint32 {
- return uint32(opProps[t].NumRefs)
- }
- func (t OpType) String() string {
- switch t {
- case TypeMacro:
- return "Macro"
- case TypeCall:
- return "Call"
- case TypeDefer:
- return "Defer"
- case TypeTransform:
- return "Transform"
- case TypePopTransform:
- return "PopTransform"
- case TypePushOpacity:
- return "PushOpacity"
- case TypePopOpacity:
- return "PopOpacity"
- case TypeImage:
- return "Image"
- case TypePaint:
- return "Paint"
- case TypeColor:
- return "Color"
- case TypeLinearGradient:
- return "LinearGradient"
- case TypePass:
- return "Pass"
- case TypePopPass:
- return "PopPass"
- case TypeInput:
- return "Input"
- case TypeKeyInputHint:
- return "KeyInputHint"
- case TypeSave:
- return "Save"
- case TypeLoad:
- return "Load"
- case TypeAux:
- return "Aux"
- case TypeClip:
- return "Clip"
- case TypePopClip:
- return "PopClip"
- case TypeCursor:
- return "Cursor"
- case TypePath:
- return "Path"
- case TypeStroke:
- return "Stroke"
- case TypeSemanticLabel:
- return "SemanticDescription"
- default:
- panic("unknown OpType")
- }
- }
|