-
Notifications
You must be signed in to change notification settings - Fork 7
/
ControlWordSequencer.go
190 lines (173 loc) · 5.59 KB
/
ControlWordSequencer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package compression
import (
"errors"
"fmt"
"sort"
)
// TileColorOp describes one operation how a tile should be colored.
type TileColorOp struct {
Type ControlType
Offset uint32
}
// ControlWordSequencer receives a list of requested tile coloring operations
// and produces a sequence of low-level control words to reproduce the requested list.
type ControlWordSequencer struct {
// BitstreamIndexLimit specifies the highest value the sequencer may use to store offset values in the bitstream.
// If this value is 0, then the default of 0xFFF (12 bits) is used.
BitstreamIndexLimit uint32
// DirectIndexLimit specifies the amount of direct pointers into the bitstream.
// If this value is 0, then the default of BitstreamIndexLimit / 4 is used.
DirectIndexLimit uint32
ops map[TileColorOp]uint32
}
// Add extends the list of requested coloring operations with the given entry.
// The offset of the entry must be a value less than or equal ControlWordParamLimit, otherwise the function returns an error.
func (seq *ControlWordSequencer) Add(op TileColorOp) error {
if op.Offset > ControlWordParamLimit {
return errors.New("too high operation offset")
}
if seq.ops == nil {
seq.ops = make(map[TileColorOp]uint32)
}
seq.ops[op]++
return nil
}
// Sequence packs the requested list of entries into a sequence of control words.
// An error is returned if the operations would exceed the possible storage space.
func (seq ControlWordSequencer) Sequence() (ControlWordSequence, error) {
var result ControlWordSequence
var sortedOps []TileColorOp
for op := range seq.ops {
sortedOps = append(sortedOps, op)
}
sort.Slice(sortedOps, func(a, b int) bool {
opA := sortedOps[a]
opB := sortedOps[b]
countA := seq.ops[opA]
countB := seq.ops[opB]
if countA != countB {
return countA > countB
}
if opA.Offset != opB.Offset {
return opA.Offset < opB.Offset
}
if opA.Type != opB.Type {
return opA.Type < opB.Type
}
return false
})
bitstreamIndexLimit := seq.BitstreamIndexLimit
if bitstreamIndexLimit == 0 {
bitstreamIndexLimit = 0xFFF
}
maximumDirect := seq.DirectIndexLimit
if maximumDirect > bitstreamIndexLimit {
maximumDirect = bitstreamIndexLimit
} else if maximumDirect == 0 {
maximumDirect = bitstreamIndexLimit / 4
}
requiredExtensions := uint32(0)
totalOps := uint32(len(sortedOps))
if totalOps > maximumDirect {
requiredExtensions = (totalOps - maximumDirect + 15) / 16
if requiredExtensions > (bitstreamIndexLimit - maximumDirect) {
return result, fmt.Errorf("too many words: total=%v, maxDirect=%v, indexLimit=%v",
totalOps, maximumDirect, bitstreamIndexLimit)
}
}
extensionStart := maximumDirect + requiredExtensions
result.opPaths = make(map[TileColorOp]nestedTileColorOp)
for opIndex, op := range sortedOps {
if uint32(opIndex) == maximumDirect {
// write required amount of long offset entries
for i := uint32(0); i < requiredExtensions; i++ {
result.words = append(result.words, LongOffsetOf(extensionStart+i*16))
}
}
if uint32(opIndex) >= maximumDirect {
offset := uint32(opIndex) - maximumDirect
result.opPaths[op] = nestedTileColorOp{
parent: &nestedTileColorOp{relOffsetBits: 12, relOffset: maximumDirect + offset/16},
relOffsetBits: 4,
relOffset: offset % 16}
result.words = append(result.words, ControlWordOf(4, op.Type, op.Offset))
} else {
result.opPaths[op] = nestedTileColorOp{parent: nil, relOffsetBits: 12, relOffset: uint32(opIndex)}
result.words = append(result.words, ControlWordOf(12, op.Type, op.Offset))
}
}
return result, nil
}
type nestedTileColorOp struct {
parent *nestedTileColorOp
relOffsetBits uint
relOffset uint32
}
func (op nestedTileColorOp) writeTo(w *BitstreamWriter) {
if op.parent != nil {
op.parent.writeTo(w)
}
w.Write(op.relOffsetBits, op.relOffset)
}
// ControlWordSequence is a finalized set of control words to reproduce a list of
// tile coloring operations. Based on this sequence, a bitstream can be created based
// on a selection of such coloring operations (i.e., per frame).
type ControlWordSequence struct {
// HTiles specifies the amount of horizontal tiles (= operations) a frame has.
// This number is relevant for skip operations. If 0, then no compression of skip-operations is done.
HTiles uint32
words []ControlWord
opPaths map[TileColorOp]nestedTileColorOp
}
// ControlWords returns the list of low-level control words of the sequence.
func (seq ControlWordSequence) ControlWords() []ControlWord {
return seq.words
}
// BitstreamFor returns the bitstream to reproduce the provided list of coloring operations
// from this sequence.
func (seq ControlWordSequence) BitstreamFor(ops []TileColorOp) ([]byte, error) {
var writer BitstreamWriter
writeOp := func(op TileColorOp) {
nested := seq.opPaths[op]
nested.writeTo(&writer)
}
pendingSkips := uint32(0)
writePendingSkips := func() {
for pendingSkips != 0 {
toSkip := pendingSkips
if toSkip >= 0x1F {
toSkip = 0x1E
}
writeOp(TileColorOp{Type: CtrlSkip})
writer.Write(5, toSkip-1)
pendingSkips -= toSkip
}
}
writeLineSkip := func() {
if pendingSkips > 0 {
writeOp(TileColorOp{Type: CtrlSkip})
writer.Write(5, 0x1F)
pendingSkips = 0
}
}
hasWidthConfig := seq.HTiles > 0
for opIndex, op := range ops {
atRowStart := hasWidthConfig && ((uint32(opIndex) % seq.HTiles) == 0)
if (pendingSkips > 0) && atRowStart {
writeLineSkip()
}
if op.Type == CtrlSkip {
pendingSkips++
if !hasWidthConfig {
writePendingSkips()
}
} else {
writePendingSkips()
writeOp(op)
}
}
if pendingSkips > 0 {
writeLineSkip()
}
return writer.Buffer(), nil
}