gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/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  	"gioui.org/f32"
    11  	"gioui.org/internal/byteslice"
    12  	"gioui.org/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  }