Files
questshock/modding/hacked/ss1/content/movie/internal/compression/ControlWordSequencer.go
T

199 lines
5.7 KiB
Go

package compression
import (
"sort"
"github.com/inkyblackness/hacked/ss1"
)
const (
errTooHighOperationOffset ss1.StringError = "too high operation offset"
)
// 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 errTooHighOperationOffset
}
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, tooManyWordsError{
Total: totalOps,
MaximumDirect: maximumDirect,
BitstreamIndexLimit: 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
}