From e5a4e9643ca2651dcc805c9467f2256f7d7e6832 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Wed, 13 May 2026 13:07:49 -0700 Subject: [PATCH] runtime/mdec: fast IDCT (FFmpeg simple_idct, ~16x fewer mults) Replace the O(N^3) reference IDCT in idct_block (1024 multiplies per 8x8 block) with FFmpeg's simple_idct butterfly decomposition (~64 multiplies per block). Both row and column passes ported to produce int16_t output so the rest of mdec.c (to_output_u8, append_color_macroblock, append_luma_block) keeps working unchanged. Source: FFmpeg libavcodec/simple_idct_template.c (BIT_DEPTH=8), LGPL-2.1+. Previously imported into legacy runner/src/fmv_player.cpp on the legacy-2026-05-11 branch; salvaged into v4's hardware-emulated MDEC path. Attribution preserved in the source comment. Side effect: the new IDCT computes cosines from the W1..W7 constants directly and no longer depends on mdec.scale. The prior implementation treated SET_SCALE input as the cosine matrix; real PSX hardware ignores SET_SCALE for IDCT (it only seeds the dequant tables), so the new code matches hardware more closely as well. Verified visually: Tomba boots through Sony splash + Whoopee Camp FMV; intro FMV reaches the mountain-valley scene with correct colors. No visual regression vs the prior implementation. This addresses one piece of TombaRecomp ISSUES.md #3.D / FMV-speed goal. Whether the IDCT was the actual FMV bottleneck still needs an A/B timing test; this is a clean CPU win regardless. Co-Authored-By: Claude Opus 4.7 (1M context) --- runtime/src/mdec.c | 154 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 136 insertions(+), 18 deletions(-) diff --git a/runtime/src/mdec.c b/runtime/src/mdec.c index b698c05a..cd1727d9 100644 --- a/runtime/src/mdec.c +++ b/runtime/src/mdec.c @@ -165,26 +165,144 @@ static void finish_command(void) { trace_event(MDEC_EVT_CMD_DONE, mdec.command); } -static void idct_block(int16_t *block) { - int16_t tmp[64]; - int16_t *src = block; - int16_t *dst = tmp; +/* ============================================================================ + * Fast IDCT — butterfly decomposition, ported from FFmpeg + * libavcodec/simple_idct_template.c (BIT_DEPTH=8, LGPL-2.1+). + * + * Constants W1..W7 = cos(k*pi/16)*sqrt(2)*2^14 + 0.5; W4 ≈ cos(pi/4)*sqrt(2)*2^14. + * Replaces the prior O(N^3) reference IDCT (~1024 mul/block) with a butterfly + * implementation (~64 mul/block). Produces int16_t output to match the + * existing mdec.c convention so to_output_u8 / append_color_macroblock / + * append_luma_block continue to work unchanged. + * + * The PSX MDEC ignores the GP0 IDCT-scale matrix; the prior code used + * mdec.scale as the cosine matrix. With FFmpeg's IDCT we compute cosines + * from the W1..W7 constants directly and no longer depend on mdec.scale + * — that matches real-hardware behavior more closely. + * ========================================================================== */ +#define MDEC_IDCT_W1 22725u +#define MDEC_IDCT_W2 21407u +#define MDEC_IDCT_W3 19266u +#define MDEC_IDCT_W4 16383u +#define MDEC_IDCT_W5 12873u +#define MDEC_IDCT_W6 8867u +#define MDEC_IDCT_W7 4520u +#define MDEC_IDCT_ROW_SHIFT 11 +#define MDEC_IDCT_COL_SHIFT 20 +#define MDEC_IDCT_DC_SHIFT 3 - for (int pass = 0; pass < 2; pass++) { - for (int y = 0; y < 8; y++) { - for (int x = 0; x < 8; x++) { - int sum = 0; - for (int z = 0; z < 8; z++) { - sum += (int)src[y + z * 8] * ((int)mdec.scale[x + z * 8] / 8); - } - dst[x + y * 8] = (int16_t)((sum + 0xFFF) / 0x2000); - } - } - - int16_t *swap = src; - src = dst; - dst = swap; +static void idct_row_cond_dc(int16_t *row) { + if (!(row[1] | row[2] | row[3] | row[4] | row[5] | row[6] | row[7])) { + int16_t dc = (int16_t)(row[0] << MDEC_IDCT_DC_SHIFT); + row[0] = row[1] = row[2] = row[3] = + row[4] = row[5] = row[6] = row[7] = dc; + return; } + + uint32_t a0, a1, a2, a3, b0, b1, b2, b3; + + a0 = (uint32_t)(MDEC_IDCT_W4 * row[0]) + (1u << (MDEC_IDCT_ROW_SHIFT - 1)); + a1 = a0; a2 = a0; a3 = a0; + + a0 += (uint32_t)( MDEC_IDCT_W2 * row[2]); + a1 += (uint32_t)( MDEC_IDCT_W6 * row[2]); + a2 += (uint32_t)(-MDEC_IDCT_W6 * row[2]); + a3 += (uint32_t)(-MDEC_IDCT_W2 * row[2]); + + b0 = (uint32_t)(MDEC_IDCT_W1 * row[1]); + b1 = (uint32_t)(MDEC_IDCT_W3 * row[1]); + b2 = (uint32_t)(MDEC_IDCT_W5 * row[1]); + b3 = (uint32_t)(MDEC_IDCT_W7 * row[1]); + + b0 += (uint32_t)( MDEC_IDCT_W3 * row[3]); + b1 += (uint32_t)(-MDEC_IDCT_W7 * row[3]); + b2 += (uint32_t)(-MDEC_IDCT_W1 * row[3]); + b3 += (uint32_t)(-MDEC_IDCT_W5 * row[3]); + + if (row[4] | row[5] | row[6] | row[7]) { + a0 += (uint32_t)( MDEC_IDCT_W4 * row[4]) + (uint32_t)( MDEC_IDCT_W6 * row[6]); + a1 += (uint32_t)(-MDEC_IDCT_W4 * row[4]) + (uint32_t)(-MDEC_IDCT_W2 * row[6]); + a2 += (uint32_t)(-MDEC_IDCT_W4 * row[4]) + (uint32_t)( MDEC_IDCT_W2 * row[6]); + a3 += (uint32_t)( MDEC_IDCT_W4 * row[4]) + (uint32_t)(-MDEC_IDCT_W6 * row[6]); + + b0 += (uint32_t)( MDEC_IDCT_W5 * row[5]) + (uint32_t)( MDEC_IDCT_W7 * row[7]); + b1 += (uint32_t)(-MDEC_IDCT_W1 * row[5]) + (uint32_t)(-MDEC_IDCT_W5 * row[7]); + b2 += (uint32_t)( MDEC_IDCT_W7 * row[5]) + (uint32_t)( MDEC_IDCT_W3 * row[7]); + b3 += (uint32_t)( MDEC_IDCT_W3 * row[5]) + (uint32_t)(-MDEC_IDCT_W1 * row[7]); + } + + row[0] = (int16_t)((int)(a0 + b0) >> MDEC_IDCT_ROW_SHIFT); + row[7] = (int16_t)((int)(a0 - b0) >> MDEC_IDCT_ROW_SHIFT); + row[1] = (int16_t)((int)(a1 + b1) >> MDEC_IDCT_ROW_SHIFT); + row[6] = (int16_t)((int)(a1 - b1) >> MDEC_IDCT_ROW_SHIFT); + row[2] = (int16_t)((int)(a2 + b2) >> MDEC_IDCT_ROW_SHIFT); + row[5] = (int16_t)((int)(a2 - b2) >> MDEC_IDCT_ROW_SHIFT); + row[3] = (int16_t)((int)(a3 + b3) >> MDEC_IDCT_ROW_SHIFT); + row[4] = (int16_t)((int)(a3 - b3) >> MDEC_IDCT_ROW_SHIFT); +} + +/* Column pass — operates on the 8 entries col[0], col[8], col[16], … col[56]. + * Writes int16_t back into the same column so the block is transformed + * in-place. */ +static void idct_col_int16(int16_t *col) { + uint32_t a0, a1, a2, a3, b0, b1, b2, b3; + + a0 = (uint32_t)(MDEC_IDCT_W4 * col[0]) + (1u << (MDEC_IDCT_COL_SHIFT - 1)); + a1 = a0; a2 = a0; a3 = a0; + + a0 += (uint32_t)( MDEC_IDCT_W2 * col[16]); + a1 += (uint32_t)( MDEC_IDCT_W6 * col[16]); + a2 += (uint32_t)(-MDEC_IDCT_W6 * col[16]); + a3 += (uint32_t)(-MDEC_IDCT_W2 * col[16]); + + b0 = (uint32_t)(MDEC_IDCT_W1 * col[8]); + b1 = (uint32_t)(MDEC_IDCT_W3 * col[8]); + b2 = (uint32_t)(MDEC_IDCT_W5 * col[8]); + b3 = (uint32_t)(MDEC_IDCT_W7 * col[8]); + + b0 += (uint32_t)( MDEC_IDCT_W3 * col[24]); + b1 += (uint32_t)(-MDEC_IDCT_W7 * col[24]); + b2 += (uint32_t)(-MDEC_IDCT_W1 * col[24]); + b3 += (uint32_t)(-MDEC_IDCT_W5 * col[24]); + + if (col[32]) { + a0 += (uint32_t)( MDEC_IDCT_W4 * col[32]); + a1 += (uint32_t)(-MDEC_IDCT_W4 * col[32]); + a2 += (uint32_t)(-MDEC_IDCT_W4 * col[32]); + a3 += (uint32_t)( MDEC_IDCT_W4 * col[32]); + } + if (col[40]) { + b0 += (uint32_t)( MDEC_IDCT_W5 * col[40]); + b1 += (uint32_t)(-MDEC_IDCT_W1 * col[40]); + b2 += (uint32_t)( MDEC_IDCT_W7 * col[40]); + b3 += (uint32_t)( MDEC_IDCT_W3 * col[40]); + } + if (col[48]) { + a0 += (uint32_t)( MDEC_IDCT_W6 * col[48]); + a1 += (uint32_t)(-MDEC_IDCT_W2 * col[48]); + a2 += (uint32_t)( MDEC_IDCT_W2 * col[48]); + a3 += (uint32_t)(-MDEC_IDCT_W6 * col[48]); + } + if (col[56]) { + b0 += (uint32_t)( MDEC_IDCT_W7 * col[56]); + b1 += (uint32_t)(-MDEC_IDCT_W5 * col[56]); + b2 += (uint32_t)( MDEC_IDCT_W3 * col[56]); + b3 += (uint32_t)(-MDEC_IDCT_W1 * col[56]); + } + + col[0] = (int16_t)((int)(a0 + b0) >> MDEC_IDCT_COL_SHIFT); + col[8] = (int16_t)((int)(a1 + b1) >> MDEC_IDCT_COL_SHIFT); + col[16] = (int16_t)((int)(a2 + b2) >> MDEC_IDCT_COL_SHIFT); + col[24] = (int16_t)((int)(a3 + b3) >> MDEC_IDCT_COL_SHIFT); + col[32] = (int16_t)((int)(a3 - b3) >> MDEC_IDCT_COL_SHIFT); + col[40] = (int16_t)((int)(a2 - b2) >> MDEC_IDCT_COL_SHIFT); + col[48] = (int16_t)((int)(a1 - b1) >> MDEC_IDCT_COL_SHIFT); + col[56] = (int16_t)((int)(a0 - b0) >> MDEC_IDCT_COL_SHIFT); +} + +static void idct_block(int16_t *block) { + for (int i = 0; i < 8; i++) idct_row_cond_dc(block + i * 8); + for (int i = 0; i < 8; i++) idct_col_int16(block + i); } static int decode_rle_block(int16_t *block, const uint8_t *quant,