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 }