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

242 lines
7.5 KiB
Go

package compression_test
import (
"context"
"fmt"
"io/ioutil"
"math/rand"
"path/filepath"
"testing"
"time"
"github.com/inkyblackness/hacked/ss1/content/movie/internal/compression"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type CompressionSuite struct {
suite.Suite
}
func TestTileSideLength(t *testing.T) {
assert.Equal(t, 4, compression.TileSideLength, "The following tests assume a tile side length of 4")
}
func TestCompressionTest(t *testing.T) {
suite.Run(t, new(CompressionSuite))
}
func (suite *CompressionSuite) SetupTest() {
}
func (suite *CompressionSuite) TestOneFrameOneTile() {
frame0 := []byte{
0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C,
0x0D, 0x0E, 0x0F, 0x10,
}
suite.verifyCompression(4, 4, frame0)
}
func (suite *CompressionSuite) TestTwoFramesOneTile() {
frame0 := []byte{
0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C,
0x0D, 0x0E, 0x0F, 0x10,
}
frame1 := []byte{
0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2A, 0x2B,
0x2C, 0x2D, 0x2E, 0x2F,
}
suite.verifyCompression(4, 4, frame0, frame1)
}
func (suite *CompressionSuite) TestTwoTiles() {
frame0 := []byte{
0x01, 0x02, 0x03, 0x04, 0x20, 0x21, 0x22, 0x23,
0x05, 0x06, 0x07, 0x08, 0x24, 0x25, 0x26, 0x27,
0x09, 0x0A, 0x0B, 0x0C, 0x28, 0x29, 0x2A, 0x2B,
0x0D, 0x0E, 0x0F, 0x10, 0x2C, 0x2D, 0x2E, 0x2F,
}
suite.verifyCompression(8, 4, frame0)
}
func (suite *CompressionSuite) verifyCompression(width, height int, inFrames ...[]byte) {
suite.T().Helper()
verifyCompression(suite.T(), width, height, inFrames...)
}
func BenchmarkRecompressionDeth00(b *testing.B) {
recompress(b, "deth", "scene00")
}
func BenchmarkRecompressionDeth01(b *testing.B) {
recompress(b, "deth", "scene01")
}
func BenchmarkRecompressionDeth02(b *testing.B) {
recompress(b, "deth", "scene02")
}
func recompress(tb testing.TB, dir1, dir2 string) {
tb.Helper()
scenepath := filepath.Join(".", "_testdata", dir1, dir2)
read := func(name string) []byte {
data, err := ioutil.ReadFile(filepath.Join(scenepath, name))
require.Nil(tb, err, "no error expected reading %v", name)
return data
}
controlWordsData := read("controlDict.bin")
controlWords, err := compression.UnpackControlWords(controlWordsData)
require.Nil(tb, err, "no error expected unpacking control words")
paletteLookup := read("paletteLookup.bin")
width := 600
height := 300
frame := make([]byte, width*height)
decoderBuilder := compression.NewFrameDecoderBuilder(600, 300)
logStatistics(tb, "In", controlWords, paletteLookup)
decoderBuilder.WithControlWords(controlWords)
decoderBuilder.WithPaletteLookupList(paletteLookup)
decoderBuilder.ForStandardFrame(frame, width)
decoder := decoderBuilder.Build()
canDecodeFrame := func(index int) bool {
bitstream, err := ioutil.ReadFile(filepath.Join(scenepath, fmt.Sprintf("frame%02d_bitstream.bin", index)))
if err != nil {
return false
}
maskstream, err := ioutil.ReadFile(filepath.Join(scenepath, fmt.Sprintf("frame%02d_maskstream.bin", index)))
if err != nil {
return false
}
tb.Logf("InStatistics F%2d: Bitstream: %v, Maskstream: %v", index, len(bitstream), len(maskstream))
err = decoder.Decode(bitstream, maskstream)
if err != nil {
tb.Logf("Failed to decode frame F%2d: %v", index, err)
return false
}
return true
}
frameIndex := 0
var frames [][]byte
for canDecodeFrame(frameIndex) {
frameCopy := make([]byte, len(frame))
copy(frameCopy, frame)
frames = append(frames, frameCopy)
frameIndex++
}
tb.Logf("verifying compression of %v frames\n", len(frames))
verifyCompression(tb, width, height, frames...)
}
func BenchmarkRandomFrames(b *testing.B) {
// With a frame size of 600x300 we have 150*75=11250 tiles per frame.
// The maximum palette offset is 0x1FFFF, allowing for 8191 entries of full 16 byte (fully random tile) palettes.
// This means not even one frame of completely randomized tiles can be encoded.
// As a result, this test will first create a set of 4000 random tiles, and then create a sequence of frames
// using this set of tiles, instead of creating complete random frames.
// Still, this test is rather slow, so it is moved to be a benchmark for now.
seed := time.Now().UnixNano()
b.Logf("Running with seed %v", seed)
hTiles := 150
vTiles := 75
width := 4 * hTiles
height := 4 * vTiles
r := rand.New(rand.NewSource(seed))
tiles := make([][4][4]byte, 4000)
for tileIndex := 0; tileIndex < len(tiles); tileIndex++ {
tile := &tiles[tileIndex]
for y := 0; y < 4; y++ {
for x := 0; x < 4; x++ {
tile[y][x] = byte(1 + r.Intn(255)) // value of 0x00 may not be encoded.
}
}
}
frames := make([][]byte, 5)
for frameIndex := 0; frameIndex < len(frames); frameIndex++ {
frame := make([]byte, width*height)
for y := 0; y < height; y += 4 {
for x := 0; x < width; x += 4 {
tile := &tiles[r.Intn(len(tiles))]
for line := 0; line < 4; line++ {
copy(frame[(y+line)*width+x:], tile[line][:])
}
}
}
frames[frameIndex] = frame
}
b.ResetTimer()
verifyCompression(b, width, height, frames...)
}
func verifyCompression(tb testing.TB, width, height int, inFrames ...[]byte) {
tb.Helper()
encoder := compression.NewSceneEncoder(width, height)
for frameIndex, frame := range inFrames {
require.Equal(tb, width*height, len(frame), fmt.Sprintf("Length of frame %d is wrong for dimension", frameIndex))
err := encoder.AddFrame(frame)
require.Nil(tb, err, fmt.Sprintf("no error expected adding frame %d: %v", frameIndex, err))
}
controlWords, paletteLookup, encodedFrames, err := encoder.Encode(context.Background())
require.Equal(tb, len(inFrames), len(encodedFrames), "expected equal amount of encoded frames for input frames")
require.Nil(tb, err, fmt.Sprintf("no error expected encoding: %v", err))
logStatistics(tb, "Out", controlWords, paletteLookup)
decoderBuilder := compression.NewFrameDecoderBuilder(width, height)
decoderBuilder.WithControlWords(controlWords)
decoderBuilder.WithPaletteLookupList(paletteLookup)
frameBuffer := make([]byte, width*height)
decoderBuilder.ForStandardFrame(frameBuffer, width)
for frameIndex, encodedFrame := range encodedFrames {
decoder := decoderBuilder.Build()
tb.Logf("Statistics F%2d: Bitstream: %v, Maskstream: %v",
frameIndex, len(encodedFrame.Bitstream), len(encodedFrame.Maskstream))
err = decoder.Decode(encodedFrame.Bitstream, encodedFrame.Maskstream)
assert.Nil(tb, err, fmt.Sprintf("no error expected re-decoding: %v", err))
assert.Equal(tb, inFrames[frameIndex], frameBuffer, fmt.Sprintf("Frame content mismatch for frame %d", frameIndex))
}
}
func logStatistics(tb testing.TB, prefix string, controlWords []compression.ControlWord, paletteLookup []byte) {
tb.Helper()
controls := make(map[compression.ControlType]int)
skips := make(map[int]int)
longOffsets := 0
for _, word := range controlWords {
if word.IsLongOffset() {
longOffsets++
} else {
skips[word.Count()]++
controls[word.Type()]++
}
}
firstWords := ""
for i := 0; i < 16; i++ {
if i < len(controlWords) {
word := controlWords[i]
if word.IsLongOffset() {
firstWords += fmt.Sprintf(" LO: %v", word.LongOffset())
} else {
firstWords += fmt.Sprintf(" [%v %v %v]", word.Count(), word.Type(), word.Parameter())
}
}
}
tb.Logf("%vStatistics: PaletteLookup: %vB, ControlWords: %v, LO: %v, CTRL: %v, SKIP: %v -- firstWords: %v",
prefix, len(paletteLookup), len(controlWords), longOffsets, controls, skips, firstWords)
}