mirror of
https://github.com/ARMSX2/ARMSX1.git
synced 2026-08-24 16:53:35 -07:00
198 lines
6.0 KiB
C
198 lines
6.0 KiB
C
#include "psx/dev/mdec.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define CHECK(expr) \
|
|
do { \
|
|
if (!(expr)) { \
|
|
fprintf(stderr, "MDEC_BOUNDS failed line=%d expression=%s\n", __LINE__, #expr); \
|
|
exit(1); \
|
|
} \
|
|
} while (0)
|
|
|
|
static psx_mdec_t* new_mdec(void) {
|
|
psx_mdec_t* mdec = psx_mdec_create();
|
|
|
|
CHECK(mdec != NULL);
|
|
psx_mdec_init(mdec);
|
|
return mdec;
|
|
}
|
|
|
|
static void write_halfwords(psx_mdec_t* mdec, uint32_t command,
|
|
const uint16_t* values, size_t count) {
|
|
CHECK((count & 1u) == 0);
|
|
CHECK(count / 2u <= 0xffffu);
|
|
|
|
psx_mdec_write32(mdec, 0, command | (uint32_t)(count / 2u));
|
|
|
|
for (size_t i = 0; i < count; i += 2) {
|
|
uint32_t word = values[i] | ((uint32_t)values[i + 1] << 16u);
|
|
|
|
psx_mdec_write32(mdec, 0, word);
|
|
}
|
|
}
|
|
|
|
static void write_decode(psx_mdec_t* mdec, unsigned depth,
|
|
const uint16_t* values, size_t count) {
|
|
write_halfwords(mdec, UINT32_C(1) << 29u | (uint32_t)depth << 27u, values, count);
|
|
}
|
|
|
|
static void fill_minimal_blocks(uint16_t* values, size_t blocks, uint16_t terminator) {
|
|
for (size_t i = 0; i < blocks; i++) {
|
|
values[i * 2u] = 0;
|
|
values[i * 2u + 1u] = terminator;
|
|
}
|
|
}
|
|
|
|
static void expect_empty(const psx_mdec_t* mdec) {
|
|
CHECK(mdec->output == NULL);
|
|
CHECK(mdec->output_words_remaining == 0);
|
|
CHECK(mdec->output_empty == 1);
|
|
CHECK(mdec->output_request == 0);
|
|
}
|
|
|
|
static void test_exact_color_macroblock(void) {
|
|
uint16_t stream[12];
|
|
psx_mdec_t* mdec = new_mdec();
|
|
|
|
fill_minimal_blocks(stream, 6, 0xfe00);
|
|
write_decode(mdec, 2, stream, 12);
|
|
|
|
CHECK(mdec->output != NULL);
|
|
CHECK(mdec->output_words_remaining == 192);
|
|
CHECK(mdec->output_empty == 0);
|
|
|
|
for (unsigned i = 0; i < 192; i++)
|
|
CHECK(psx_mdec_read32(mdec, 0) == UINT32_C(0x80808080));
|
|
|
|
CHECK(mdec->output_words_remaining == 0);
|
|
CHECK(mdec->output_empty == 1);
|
|
CHECK((psx_mdec_read32(mdec, 4) >> 31u) == 1u);
|
|
psx_mdec_destroy(mdec);
|
|
}
|
|
|
|
static void test_partial_color_macroblocks(void) {
|
|
for (size_t blocks = 1; blocks < 6; blocks++) {
|
|
uint16_t stream[10];
|
|
psx_mdec_t* mdec = new_mdec();
|
|
|
|
fill_minimal_blocks(stream, blocks, 0xfe00);
|
|
write_decode(mdec, 2, stream, blocks * 2u);
|
|
expect_empty(mdec);
|
|
psx_mdec_destroy(mdec);
|
|
}
|
|
}
|
|
|
|
static void test_complete_then_partial(void) {
|
|
uint16_t stream[14];
|
|
psx_mdec_t* mdec = new_mdec();
|
|
|
|
fill_minimal_blocks(stream, 6, 0xfe00);
|
|
stream[12] = 0;
|
|
stream[13] = 0; /* coefficient 1, then the command ends without an EOB */
|
|
write_decode(mdec, 2, stream, 14);
|
|
|
|
CHECK(mdec->output != NULL);
|
|
CHECK(mdec->output_words_remaining == 192);
|
|
psx_mdec_destroy(mdec);
|
|
}
|
|
|
|
static void test_padding_only(void) {
|
|
const uint16_t stream[] = {0xfe00, 0xfe00};
|
|
psx_mdec_t* mdec = new_mdec();
|
|
|
|
write_decode(mdec, 2, stream, sizeof(stream) / sizeof(stream[0]));
|
|
expect_empty(mdec);
|
|
psx_mdec_destroy(mdec);
|
|
}
|
|
|
|
static void test_oversized_runs(void) {
|
|
uint16_t stream[12];
|
|
psx_mdec_t* mdec = new_mdec();
|
|
|
|
/* Run=63 advances k=0 to 64. The value bits must never be used to index quant[64]. */
|
|
fill_minimal_blocks(stream, 6, 0xfc01);
|
|
write_decode(mdec, 2, stream, 12);
|
|
CHECK(mdec->output_words_remaining == 192);
|
|
psx_mdec_destroy(mdec);
|
|
|
|
mdec = new_mdec();
|
|
/* Run=62 lands exactly on coefficient 63, which is stored and completes the block. */
|
|
fill_minimal_blocks(stream, 6, 0xf801);
|
|
write_decode(mdec, 3, stream, 12);
|
|
CHECK(mdec->output_words_remaining == 128);
|
|
psx_mdec_destroy(mdec);
|
|
}
|
|
|
|
static void test_mono_depths(void) {
|
|
const uint16_t stream[] = {0, 0xfe00};
|
|
psx_mdec_t* mdec = new_mdec();
|
|
|
|
write_decode(mdec, 1, stream, 2);
|
|
CHECK(mdec->output_words_remaining == 16);
|
|
for (unsigned i = 0; i < 16; i++)
|
|
CHECK(psx_mdec_read32(mdec, 0) == UINT32_C(0x80808080));
|
|
psx_mdec_destroy(mdec);
|
|
|
|
mdec = new_mdec();
|
|
write_decode(mdec, 0, stream, 2);
|
|
CHECK(mdec->output_words_remaining == 8);
|
|
for (unsigned i = 0; i < 8; i++)
|
|
CHECK(psx_mdec_read32(mdec, 0) == UINT32_C(0x88888888));
|
|
psx_mdec_destroy(mdec);
|
|
}
|
|
|
|
static void test_parameter_endianness(void) {
|
|
uint16_t quant_halfwords[64];
|
|
uint16_t scale_halfwords[64];
|
|
psx_mdec_t* mdec = new_mdec();
|
|
|
|
for (unsigned i = 0; i < 64; i++) {
|
|
unsigned byte = i * 2u;
|
|
quant_halfwords[i] = (uint16_t)(byte | ((byte + 1u) << 8u));
|
|
scale_halfwords[i] = (uint16_t)(0x8000u + i);
|
|
}
|
|
|
|
write_halfwords(mdec, (UINT32_C(2) << 29u) | 1u, quant_halfwords, 64);
|
|
for (unsigned i = 0; i < 64; i++) {
|
|
CHECK(mdec->y_quant_table[i] == i);
|
|
CHECK(mdec->uv_quant_table[i] == 64u + i);
|
|
}
|
|
|
|
write_halfwords(mdec, UINT32_C(3) << 29u, scale_halfwords, 64);
|
|
for (unsigned i = 0; i < 64; i++)
|
|
CHECK(mdec->scale_table[i] == (int16_t)(0x8000u + i));
|
|
|
|
psx_mdec_destroy(mdec);
|
|
}
|
|
|
|
static void test_reset_drops_live_buffers(void) {
|
|
const uint16_t stream[] = {0, 0xfe00};
|
|
psx_mdec_t* mdec = new_mdec();
|
|
|
|
write_decode(mdec, 1, stream, 2);
|
|
CHECK(mdec->output != NULL);
|
|
psx_mdec_write32(mdec, 4, UINT32_C(0x80000000));
|
|
expect_empty(mdec);
|
|
CHECK(mdec->input == NULL);
|
|
CHECK(mdec->input_size == 0);
|
|
psx_mdec_destroy(mdec);
|
|
}
|
|
|
|
int main(void) {
|
|
test_exact_color_macroblock();
|
|
test_partial_color_macroblocks();
|
|
test_complete_then_partial();
|
|
test_padding_only();
|
|
test_oversized_runs();
|
|
test_mono_depths();
|
|
test_parameter_endianness();
|
|
test_reset_drops_live_buffers();
|
|
|
|
puts("MDEC_BOUNDS all cases passed");
|
|
return 0;
|
|
}
|