mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 02:56:36 -04:00
08bb5a2728
The faithful dequant/IDCT/YUV transcription was correct, but the 15bpp pack did (c+4)>>3 on `r ^ 0x80` as a SIGNED int. For negative Mask9ClampS8 results that yields a negative int (r=-1 → -129) → garbage 5-bit channel → B saturated, R/G doubled = the rainbow fringing on real FMV. Beetle's RGB_to_RGB555 takes uint8 params (truncates to 0..255); the 24bpp path already cast to uint8_t. Fix: rgb_to_555_chan(uint8_t) so the caller's int truncates on the call. Caught only by comparing our VRAM to the REAL Beetle process (new vram_peek on the Beetle side, held-FMV diff) — both standalone pins shared the bug in their self-transcribed reference and falsely passed. After the fix the per-channel VRAM delta vs Beetle collapsed from mean (12,16,27) to (0.7,0.9,1.3) and the FMV renders natural colours. Full-path pin (mdec_e2e, corrected uint8 oracle) is byte-exact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
643 lines
22 KiB
C
643 lines
22 KiB
C
#include "mdec.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
extern uint64_t s_frame_count;
|
|
|
|
/* FMV-activity detector: frame stamp of the newest colour (15/24-bit) MDEC
|
|
* decode. Streamed video decodes every frame; texture decompression uses the
|
|
* 4/8-bit luma path and does not stamp. */
|
|
static uint64_t mdec_last_color_decode_frame = (uint64_t)0 - 1000u;
|
|
|
|
enum {
|
|
MDEC_CMD_NOP = 0,
|
|
MDEC_CMD_DECODE = 1,
|
|
MDEC_CMD_SET_QUANT = 2,
|
|
MDEC_CMD_SET_SCALE = 3
|
|
};
|
|
|
|
enum {
|
|
MDEC_EVT_RESET = 1,
|
|
MDEC_EVT_CTRL_WRITE,
|
|
MDEC_EVT_CMD_BEGIN,
|
|
MDEC_EVT_CMD_DONE,
|
|
MDEC_EVT_DECODE_DONE,
|
|
MDEC_EVT_DMA_IN_START,
|
|
MDEC_EVT_DMA_IN_END,
|
|
MDEC_EVT_DMA_OUT_START,
|
|
MDEC_EVT_DMA_OUT_END,
|
|
MDEC_EVT_OUTPUT_DRAINED,
|
|
MDEC_EVT_READ_UNDERFLOW
|
|
};
|
|
|
|
enum {
|
|
MDEC_STOP_NONE = 0,
|
|
MDEC_STOP_INPUT_END = 1,
|
|
MDEC_STOP_CR = 2,
|
|
MDEC_STOP_CB = 3,
|
|
MDEC_STOP_Y0 = 4,
|
|
MDEC_STOP_Y1 = 5,
|
|
MDEC_STOP_Y2 = 6,
|
|
MDEC_STOP_Y3 = 7
|
|
};
|
|
|
|
/* Zig-zag scatter table — Beetle ZigZag[64] (mdec.cpp:115), the column-major
|
|
* order that pairs with Beetle's IDCT matrix transpose + IDCT_1D_Multi below.
|
|
* (Our old table was the row-major transpose of this; it only decoded correctly
|
|
* because our old IDCT was correspondingly transposed. The faithful pipeline
|
|
* uses Beetle's table + matrix + IDCT together so the result is byte-exact.) */
|
|
static const uint8_t zigzag_to_linear[64] = {
|
|
0x00, 0x08, 0x01, 0x02, 0x09, 0x10, 0x18, 0x11,
|
|
0x0a, 0x03, 0x04, 0x0b, 0x12, 0x19, 0x20, 0x28,
|
|
0x21, 0x1a, 0x13, 0x0c, 0x05, 0x06, 0x0d, 0x14,
|
|
0x1b, 0x22, 0x29, 0x30, 0x38, 0x31, 0x2a, 0x23,
|
|
0x1c, 0x15, 0x0e, 0x07, 0x0f, 0x16, 0x1d, 0x24,
|
|
0x2b, 0x32, 0x39, 0x3a, 0x33, 0x2c, 0x25, 0x1e,
|
|
0x17, 0x1f, 0x26, 0x2d, 0x34, 0x3b, 0x3c, 0x35,
|
|
0x2e, 0x27, 0x2f, 0x36, 0x3d, 0x3e, 0x37, 0x3f
|
|
};
|
|
|
|
typedef struct MDECState {
|
|
uint32_t command;
|
|
uint32_t expected_halfwords;
|
|
uint32_t input_count;
|
|
uint16_t *input;
|
|
uint32_t input_cap;
|
|
|
|
uint8_t *output;
|
|
uint32_t output_size;
|
|
uint32_t output_pos;
|
|
uint32_t output_cap;
|
|
|
|
uint8_t y_quant[64];
|
|
uint8_t uv_quant[64];
|
|
int16_t scale[64];
|
|
|
|
uint8_t output_bit15;
|
|
uint8_t output_signed;
|
|
uint8_t output_depth;
|
|
uint8_t current_block;
|
|
uint8_t busy;
|
|
uint8_t input_full;
|
|
uint8_t enable_dma_in;
|
|
uint8_t enable_dma_out;
|
|
|
|
uint32_t last_status;
|
|
uint32_t decode_macroblocks;
|
|
uint32_t decode_blocks;
|
|
uint32_t decode_stop_reason;
|
|
uint32_t decode_input_pos;
|
|
uint32_t decode_input_end;
|
|
uint32_t dma_in_words;
|
|
uint32_t dma_out_words;
|
|
uint32_t dma_read_underflows;
|
|
} MDECState;
|
|
|
|
static MDECState mdec;
|
|
|
|
#define MDEC_TRACE_CAP 4096u
|
|
static MDECDebugEvent mdec_trace[MDEC_TRACE_CAP];
|
|
static uint64_t mdec_trace_seq;
|
|
static uint32_t mdec_trace_head;
|
|
|
|
static void trace_event(uint32_t kind, uint32_t value) {
|
|
MDECDebugEvent *e = &mdec_trace[mdec_trace_head];
|
|
e->seq = mdec_trace_seq++;
|
|
e->frame = (uint32_t)s_frame_count;
|
|
e->kind = kind;
|
|
e->value = value;
|
|
e->command = mdec.command;
|
|
e->input_count = mdec.input_count;
|
|
e->expected_halfwords = mdec.expected_halfwords;
|
|
e->output_size = mdec.output_size;
|
|
e->output_pos = mdec.output_pos;
|
|
e->macroblocks = mdec.decode_macroblocks;
|
|
e->blocks = mdec.decode_blocks;
|
|
e->stop_reason = mdec.decode_stop_reason;
|
|
e->underruns = mdec.dma_read_underflows;
|
|
mdec_trace_head = (mdec_trace_head + 1u) % MDEC_TRACE_CAP;
|
|
}
|
|
|
|
static int16_t sign_extend_10(uint16_t value) {
|
|
return (int16_t)((int16_t)(value << 6) >> 6);
|
|
}
|
|
|
|
static int clamp_int(int value, int lo, int hi) {
|
|
if (value < lo) return lo;
|
|
if (value > hi) return hi;
|
|
return value;
|
|
}
|
|
|
|
static void clear_output(void) {
|
|
mdec.output_size = 0;
|
|
mdec.output_pos = 0;
|
|
}
|
|
|
|
static int ensure_input_capacity(uint32_t halfwords) {
|
|
if (halfwords <= mdec.input_cap) return 1;
|
|
uint32_t new_cap = mdec.input_cap ? mdec.input_cap : 256u;
|
|
while (new_cap < halfwords) new_cap *= 2u;
|
|
uint16_t *new_input = (uint16_t *)realloc(mdec.input, new_cap * sizeof(uint16_t));
|
|
if (!new_input) return 0;
|
|
mdec.input = new_input;
|
|
mdec.input_cap = new_cap;
|
|
return 1;
|
|
}
|
|
|
|
static int ensure_output_capacity(uint32_t bytes) {
|
|
if (bytes <= mdec.output_cap) return 1;
|
|
uint32_t new_cap = mdec.output_cap ? mdec.output_cap : 4096u;
|
|
while (new_cap < bytes) new_cap *= 2u;
|
|
uint8_t *new_output = (uint8_t *)realloc(mdec.output, new_cap);
|
|
if (!new_output) return 0;
|
|
mdec.output = new_output;
|
|
mdec.output_cap = new_cap;
|
|
return 1;
|
|
}
|
|
|
|
static void append_byte(uint8_t value) {
|
|
if (!ensure_output_capacity(mdec.output_size + 1u)) return;
|
|
mdec.output[mdec.output_size++] = value;
|
|
}
|
|
|
|
static uint8_t input_byte(uint32_t byte_index) {
|
|
uint16_t hw = mdec.input[byte_index >> 1];
|
|
return (byte_index & 1u) ? (uint8_t)(hw >> 8) : (uint8_t)hw;
|
|
}
|
|
|
|
static void finish_command(void) {
|
|
mdec.expected_halfwords = 0;
|
|
mdec.input_count = 0;
|
|
mdec.busy = 0;
|
|
mdec.input_full = 0;
|
|
trace_event(MDEC_EVT_CMD_DONE, mdec.command);
|
|
}
|
|
|
|
static void soft_reset(void) {
|
|
uint16_t *input = mdec.input;
|
|
uint32_t input_cap = mdec.input_cap;
|
|
uint8_t *output = mdec.output;
|
|
uint32_t output_cap = mdec.output_cap;
|
|
uint8_t y_quant[64];
|
|
uint8_t uv_quant[64];
|
|
int16_t scale[64];
|
|
|
|
memcpy(y_quant, mdec.y_quant, sizeof(y_quant));
|
|
memcpy(uv_quant, mdec.uv_quant, sizeof(uv_quant));
|
|
memcpy(scale, mdec.scale, sizeof(scale));
|
|
|
|
memset(&mdec, 0, sizeof(mdec));
|
|
mdec.input = input;
|
|
mdec.input_cap = input_cap;
|
|
mdec.output = output;
|
|
mdec.output_cap = output_cap;
|
|
memcpy(mdec.y_quant, y_quant, sizeof(mdec.y_quant));
|
|
memcpy(mdec.uv_quant, uv_quant, sizeof(mdec.uv_quant));
|
|
memcpy(mdec.scale, scale, sizeof(mdec.scale));
|
|
mdec.output_depth = 3;
|
|
mdec.current_block = 4;
|
|
}
|
|
|
|
/* Sign-extend the low `bits` of v to a full int (Beetle sign_x_to_s32). */
|
|
static int sign_x_to_s32(int bits, int v) {
|
|
int shift = 32 - bits;
|
|
return (int)(((int32_t)((uint32_t)v << shift)) >> shift);
|
|
}
|
|
|
|
/* 9-bit mask then clamp to int8 (Beetle Mask9ClampS8, mdec.cpp:230). The MDEC
|
|
* keeps intermediate samples to 9 bits before the ±127 clamp, so a value outside
|
|
* the 9-bit window WRAPS before clamping — reproducing the hardware ringing. */
|
|
static int mask9_clamp_s8(int v) {
|
|
v = sign_x_to_s32(9, v);
|
|
if (v < -128) v = -128;
|
|
if (v > 127) v = 127;
|
|
return v;
|
|
}
|
|
|
|
/* Faithful R3000A MDEC IDCT (Beetle IDCT/IDCT_1D_Multi, mdec.cpp:243-291).
|
|
* Two separable 1-D passes over the >>3 scale matrix: pass 1 keeps int16 and
|
|
* transposes (out[x*8+col]); pass 2 clamps to int8 via Mask9ClampS8. Rounding
|
|
* is (sum + 0x4000) >> 15 (vs the old (sum+0xFFF)/0x2000 + per-tap /8, whose
|
|
* bias differed from hardware → the washed/wrong-contrast FMV). The block buffer
|
|
* holds int8-range samples on return. */
|
|
static void idct_block(int16_t *block) {
|
|
int16_t tmp[64];
|
|
/* pass 1 — int16 out, transposed */
|
|
for (int col = 0; col < 8; col++) {
|
|
for (int x = 0; x < 8; x++) {
|
|
int sum = 0;
|
|
for (int u = 0; u < 8; u++)
|
|
sum += (int)block[col * 8 + u] * (int)mdec.scale[x * 8 + u];
|
|
tmp[x * 8 + col] = (int16_t)((sum + 0x4000) >> 15);
|
|
}
|
|
}
|
|
/* pass 2 — int8 out (Mask9ClampS8), no transpose */
|
|
for (int col = 0; col < 8; col++) {
|
|
for (int x = 0; x < 8; x++) {
|
|
int sum = 0;
|
|
for (int u = 0; u < 8; u++)
|
|
sum += (int)tmp[col * 8 + u] * (int)mdec.scale[x * 8 + u];
|
|
block[col * 8 + x] = (int16_t)mask9_clamp_s8((sum + 0x4000) >> 15);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int decode_rle_block(int16_t *block, const uint8_t *quant,
|
|
uint32_t *pos, uint32_t end) {
|
|
memset(block, 0, 64 * sizeof(int16_t));
|
|
if (*pos >= end) return 0;
|
|
mdec.decode_blocks++;
|
|
|
|
uint16_t word = mdec.input[(*pos)++];
|
|
while (word == 0xFE00u && *pos < end) {
|
|
word = mdec.input[(*pos)++];
|
|
}
|
|
|
|
/* Dequant in Beetle's <<4 fixed-point domain (mdec.cpp:439-485), clamp
|
|
* ±0x4000. DC uses quant[0] with no qscale; AC uses qscale*quant[k]. Each
|
|
* nonzero coeff gets the sign-magnitude rounding bias (ci<0 ? +8 : -8) the
|
|
* old +4/÷8 model omitted, and the <<4 domain feeds the >>3 IDCT matrix. */
|
|
uint32_t qscale = (word >> 10) & 0x3Fu;
|
|
uint32_t k = 0;
|
|
int ci = sign_extend_10(word & 0x03FFu);
|
|
int q = (int)quant[0];
|
|
int tmp = (q != 0) ? (((ci * q) << 4) + (ci ? (ci < 0 ? 8 : -8) : 0))
|
|
: ((ci * 2) << 4);
|
|
block[0] = (int16_t)clamp_int(tmp, -0x4000, 0x3FFF);
|
|
|
|
while (*pos < end && k < 63u) {
|
|
word = mdec.input[(*pos)++];
|
|
if (word == 0xFE00u) break;
|
|
|
|
k += ((word >> 10) & 0x3Fu) + 1u;
|
|
if (k >= 64u) break;
|
|
|
|
ci = sign_extend_10(word & 0x03FFu);
|
|
q = (int)qscale * (int)quant[k];
|
|
tmp = (q != 0) ? ((((ci * q) >> 3) << 4) + (ci ? (ci < 0 ? 8 : -8) : 0))
|
|
: ((ci * 2) << 4);
|
|
block[zigzag_to_linear[k]] = (int16_t)clamp_int(tmp, -0x4000, 0x3FFF);
|
|
}
|
|
|
|
idct_block(block);
|
|
return 1;
|
|
}
|
|
|
|
static uint8_t to_output_u8(int value) {
|
|
value = clamp_int(value, -128, 127);
|
|
if (mdec.output_signed) return (uint8_t)(int8_t)value;
|
|
return (uint8_t)(value + 128);
|
|
}
|
|
|
|
/* 8-bit unsigned channel → 5-bit, Beetle RGB_to_RGB555 rounding (mdec.cpp:306).
|
|
* Beetle's RGB_to_RGB555 takes uint8 params, so the ^0x80 result is truncated to
|
|
* 0..255 BEFORE the round/shift — `c` here is already that uint8. */
|
|
static int rgb_to_555_chan(uint8_t c) {
|
|
int v = (c + 4) >> 3;
|
|
if (v > 0x1F) v = 0x1F;
|
|
return v;
|
|
}
|
|
|
|
static void append_rgb_pixel(int y, int cr, int cb) {
|
|
/* Beetle YCbCr_to_RGB (mdec.cpp:293-304): /256 coeffs (359,-88/-183,454),
|
|
* +0x80 rounding, the reduced-precision GREEN mask (-88*cb &~0x1F, -183*cr
|
|
* &~0x07) — the hardware quirk our old /1024 path lacked, the main green-hue
|
|
* error — Mask9ClampS8, then ^0x80 to unsigned 0..255. */
|
|
int r = mask9_clamp_s8(y + (((359 * cr) + 0x80) >> 8));
|
|
int g = mask9_clamp_s8(y + ((((-88 * cb) & ~0x1F) + ((-183 * cr) & ~0x07) + 0x80) >> 8));
|
|
int b = mask9_clamp_s8(y + (((454 * cb) + 0x80) >> 8));
|
|
int ru = r ^ 0x80, gu = g ^ 0x80, bu = b ^ 0x80; /* signed → unsigned */
|
|
|
|
if (mdec.output_depth == 3) {
|
|
/* 16bpp (mdec.cpp:397-418): RGB555 then pixel_xor = bit15(0x8000) |
|
|
* signed(0x4210 = MSB of each 5-bit channel). */
|
|
uint16_t packed = (uint16_t)(rgb_to_555_chan(ru)
|
|
| (rgb_to_555_chan(gu) << 5)
|
|
| (rgb_to_555_chan(bu) << 10));
|
|
uint16_t pixel_xor = (uint16_t)((mdec.output_bit15 ? 0x8000u : 0u)
|
|
| (mdec.output_signed ? 0x4210u : 0u));
|
|
packed ^= pixel_xor;
|
|
append_byte((uint8_t)packed);
|
|
append_byte((uint8_t)(packed >> 8));
|
|
} else {
|
|
/* 24bpp (mdec.cpp:370-393): rgb_xor = signed ? 0x80 : 0x00. */
|
|
uint8_t rgb_xor = mdec.output_signed ? 0x80u : 0x00u;
|
|
append_byte((uint8_t)(ru ^ rgb_xor));
|
|
append_byte((uint8_t)(gu ^ rgb_xor));
|
|
append_byte((uint8_t)(bu ^ rgb_xor));
|
|
}
|
|
}
|
|
|
|
static void append_luma_block(const int16_t *yblk) {
|
|
for (int i = 0; i < 64; i++) {
|
|
append_byte(to_output_u8(yblk[i]));
|
|
}
|
|
}
|
|
|
|
static void append_color_macroblock(const int16_t *crblk, const int16_t *cbblk,
|
|
const int16_t yblk[4][64]) {
|
|
for (int py = 0; py < 16; py++) {
|
|
for (int px = 0; px < 16; px++) {
|
|
int y_index = (py >= 8 ? 2 : 0) + (px >= 8 ? 1 : 0);
|
|
int lx = px & 7;
|
|
int ly = py & 7;
|
|
int chroma = (px >> 1) + (py >> 1) * 8;
|
|
append_rgb_pixel(yblk[y_index][lx + ly * 8], crblk[chroma], cbblk[chroma]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Monotonic count of decode invocations — the frontend FMV detector samples
|
|
* this per display-frame to tell "MDEC is actively producing frames" (FMV)
|
|
* from idle. */
|
|
static volatile uint32_t g_mdec_decode_count = 0;
|
|
uint32_t mdec_get_decode_count(void) { return g_mdec_decode_count; }
|
|
|
|
static void execute_decode(void) {
|
|
uint32_t pos = 0;
|
|
uint32_t end = mdec.input_count;
|
|
g_mdec_decode_count++;
|
|
clear_output();
|
|
mdec.decode_macroblocks = 0;
|
|
mdec.decode_blocks = 0;
|
|
mdec.decode_stop_reason = MDEC_STOP_NONE;
|
|
mdec.decode_input_pos = 0;
|
|
mdec.decode_input_end = end;
|
|
mdec.dma_out_words = 0;
|
|
mdec.dma_read_underflows = 0;
|
|
|
|
if (mdec.output_depth < 2) {
|
|
int16_t yblk[64];
|
|
while (pos < end && decode_rle_block(yblk, mdec.y_quant, &pos, end)) {
|
|
append_luma_block(yblk);
|
|
mdec.decode_macroblocks++;
|
|
}
|
|
mdec.decode_stop_reason = (pos >= end) ? MDEC_STOP_INPUT_END : MDEC_STOP_Y0;
|
|
mdec.decode_input_pos = pos;
|
|
trace_event(MDEC_EVT_DECODE_DONE, mdec.output_size);
|
|
return;
|
|
}
|
|
|
|
while (pos < end) {
|
|
int16_t crblk[64];
|
|
int16_t cbblk[64];
|
|
int16_t yblk[4][64];
|
|
if (!decode_rle_block(crblk, mdec.uv_quant, &pos, end)) { mdec.decode_stop_reason = MDEC_STOP_CR; break; }
|
|
if (!decode_rle_block(cbblk, mdec.uv_quant, &pos, end)) { mdec.decode_stop_reason = MDEC_STOP_CB; break; }
|
|
if (!decode_rle_block(yblk[0], mdec.y_quant, &pos, end)) { mdec.decode_stop_reason = MDEC_STOP_Y0; break; }
|
|
if (!decode_rle_block(yblk[1], mdec.y_quant, &pos, end)) { mdec.decode_stop_reason = MDEC_STOP_Y1; break; }
|
|
if (!decode_rle_block(yblk[2], mdec.y_quant, &pos, end)) { mdec.decode_stop_reason = MDEC_STOP_Y2; break; }
|
|
if (!decode_rle_block(yblk[3], mdec.y_quant, &pos, end)) { mdec.decode_stop_reason = MDEC_STOP_Y3; break; }
|
|
append_color_macroblock(crblk, cbblk, yblk);
|
|
mdec.decode_macroblocks++;
|
|
}
|
|
if (pos >= end && mdec.decode_stop_reason == MDEC_STOP_NONE) {
|
|
mdec.decode_stop_reason = MDEC_STOP_INPUT_END;
|
|
}
|
|
mdec.decode_input_pos = pos;
|
|
/* FMV detector: stamp colour (15/24-bit) decodes only — streamed video.
|
|
* The 4/8-bit luma path above is texture decompression, not video. */
|
|
mdec_last_color_decode_frame = s_frame_count;
|
|
trace_event(MDEC_EVT_DECODE_DONE, mdec.output_size);
|
|
}
|
|
|
|
static void execute_command(void) {
|
|
uint32_t op = mdec.command >> 29;
|
|
if (op == MDEC_CMD_SET_QUANT) {
|
|
for (uint32_t i = 0; i < 64u; i++) {
|
|
mdec.y_quant[i] = input_byte(i);
|
|
}
|
|
if (mdec.command & 1u) {
|
|
for (uint32_t i = 0; i < 64u; i++) {
|
|
mdec.uv_quant[i] = input_byte(64u + i);
|
|
}
|
|
} else {
|
|
memcpy(mdec.uv_quant, mdec.y_quant, sizeof(mdec.uv_quant));
|
|
}
|
|
} else if (op == MDEC_CMD_SET_SCALE) {
|
|
/* Load the IDCT matrix exactly as Beetle (mdec.cpp:647): store each entry
|
|
* TRANSPOSED ([(i&7)<<3 | (i>>3)&7]) and pre-shifted >>3 (arithmetic), so
|
|
* IDCT_1D_Multi above can index it [x*8+u] directly with no per-tap /8. */
|
|
for (uint32_t i = 0; i < 64u; i++) {
|
|
uint32_t t = ((i & 7u) << 3) | ((i >> 3) & 7u);
|
|
mdec.scale[t] = (int16_t)((int16_t)mdec.input[i] >> 3);
|
|
}
|
|
} else if (op == MDEC_CMD_DECODE) {
|
|
execute_decode();
|
|
}
|
|
|
|
finish_command();
|
|
}
|
|
|
|
static void begin_command(uint32_t value) {
|
|
mdec.command = value;
|
|
mdec.output_bit15 = (uint8_t)((value >> 25) & 1u);
|
|
mdec.output_signed = (uint8_t)((value >> 26) & 1u);
|
|
mdec.output_depth = (uint8_t)((value >> 27) & 3u);
|
|
mdec.current_block = 4;
|
|
mdec.input_count = 0;
|
|
mdec.input_full = 0;
|
|
mdec.busy = 1;
|
|
|
|
switch (value >> 29) {
|
|
case MDEC_CMD_DECODE:
|
|
mdec.expected_halfwords = (value & 0xFFFFu) * 2u;
|
|
break;
|
|
case MDEC_CMD_SET_QUANT:
|
|
mdec.expected_halfwords = (value & 1u) ? 64u : 32u;
|
|
break;
|
|
case MDEC_CMD_SET_SCALE:
|
|
mdec.expected_halfwords = 64u;
|
|
break;
|
|
default:
|
|
mdec.expected_halfwords = 0;
|
|
break;
|
|
}
|
|
|
|
if (mdec.expected_halfwords == 0 || !ensure_input_capacity(mdec.expected_halfwords)) {
|
|
finish_command();
|
|
} else {
|
|
trace_event(MDEC_EVT_CMD_BEGIN, value);
|
|
}
|
|
}
|
|
|
|
static void write_data(uint32_t value) {
|
|
if (mdec.busy && mdec.input_count < mdec.expected_halfwords) {
|
|
mdec.input[mdec.input_count++] = (uint16_t)value;
|
|
if (mdec.input_count < mdec.expected_halfwords) {
|
|
mdec.input[mdec.input_count++] = (uint16_t)(value >> 16);
|
|
}
|
|
if (mdec.input_count >= mdec.expected_halfwords) {
|
|
execute_command();
|
|
}
|
|
return;
|
|
}
|
|
|
|
begin_command(value);
|
|
}
|
|
|
|
int mdec_recently_active(uint32_t within_frames) {
|
|
return (uint64_t)(s_frame_count - mdec_last_color_decode_frame) <= within_frames;
|
|
}
|
|
|
|
void mdec_init(void) {
|
|
memset(&mdec, 0, sizeof(mdec));
|
|
memset(mdec_trace, 0, sizeof(mdec_trace));
|
|
mdec_trace_seq = 0;
|
|
mdec_trace_head = 0;
|
|
for (int i = 0; i < 64; i++) {
|
|
mdec.y_quant[i] = 1;
|
|
mdec.uv_quant[i] = 1;
|
|
}
|
|
mdec.output_depth = 3;
|
|
mdec.current_block = 4;
|
|
}
|
|
|
|
uint32_t mdec_read(uint32_t addr) {
|
|
uint32_t offset = addr & 7u;
|
|
if (offset == 0) {
|
|
return mdec_dma_read_word();
|
|
}
|
|
|
|
uint32_t remaining_words = 0;
|
|
if (mdec.busy && mdec.expected_halfwords > mdec.input_count) {
|
|
remaining_words = (mdec.expected_halfwords - mdec.input_count + 1u) / 2u;
|
|
}
|
|
|
|
uint32_t status = remaining_words ? ((remaining_words - 1u) & 0xFFFFu) : 0xFFFFu;
|
|
status |= ((uint32_t)mdec.current_block & 7u) << 16;
|
|
status |= ((uint32_t)mdec.output_bit15 & 1u) << 23;
|
|
status |= ((uint32_t)mdec.output_signed & 1u) << 24;
|
|
status |= ((uint32_t)mdec.output_depth & 3u) << 25;
|
|
int write_ready = mdec_dma_write_ready();
|
|
if (!write_ready) status |= 1u << 30;
|
|
if (mdec.enable_dma_out && mdec_dma_read_ready()) status |= 1u << 27;
|
|
if (mdec.enable_dma_in && write_ready) status |= 1u << 28;
|
|
if (mdec.busy) status |= 1u << 29;
|
|
if (mdec.output_pos >= mdec.output_size) status |= 1u << 31;
|
|
mdec.last_status = status;
|
|
return status;
|
|
}
|
|
|
|
void mdec_write(uint32_t addr, uint32_t value) {
|
|
uint32_t offset = addr & 7u;
|
|
if (offset == 0) {
|
|
write_data(value);
|
|
return;
|
|
}
|
|
|
|
if (value & 0x80000000u) {
|
|
soft_reset();
|
|
trace_event(MDEC_EVT_RESET, value);
|
|
}
|
|
mdec.enable_dma_in = (uint8_t)((value >> 30) & 1u);
|
|
mdec.enable_dma_out = (uint8_t)((value >> 29) & 1u);
|
|
trace_event(MDEC_EVT_CTRL_WRITE, value);
|
|
}
|
|
|
|
void mdec_dma_write_word(uint32_t value) {
|
|
write_data(value);
|
|
}
|
|
|
|
uint32_t mdec_dma_read_word(void) {
|
|
uint32_t value = 0;
|
|
uint32_t start_pos = mdec.output_pos;
|
|
for (uint32_t i = 0; i < 4u; i++) {
|
|
if (mdec.output_pos < mdec.output_size) {
|
|
value |= (uint32_t)mdec.output[mdec.output_pos++] << (i * 8u);
|
|
}
|
|
}
|
|
mdec.dma_out_words++;
|
|
if (start_pos >= mdec.output_size) {
|
|
mdec.dma_read_underflows++;
|
|
if (mdec.dma_read_underflows == 1u) {
|
|
trace_event(MDEC_EVT_READ_UNDERFLOW, mdec.dma_out_words);
|
|
}
|
|
}
|
|
if (mdec.output_pos >= mdec.output_size) {
|
|
trace_event(MDEC_EVT_OUTPUT_DRAINED, mdec.output_size);
|
|
clear_output();
|
|
}
|
|
return value;
|
|
}
|
|
|
|
int mdec_dma_write_ready(void) {
|
|
if (mdec.output_pos < mdec.output_size) return 0;
|
|
return !mdec.busy || mdec.input_count < mdec.expected_halfwords;
|
|
}
|
|
|
|
int mdec_dma_read_ready(void) {
|
|
return mdec.output_pos < mdec.output_size;
|
|
}
|
|
|
|
void mdec_debug_get_state(MDECDebugState *out) {
|
|
if (!out) return;
|
|
out->command = mdec.command;
|
|
out->expected_halfwords = mdec.expected_halfwords;
|
|
out->input_count = mdec.input_count;
|
|
out->output_size = mdec.output_size;
|
|
out->output_pos = mdec.output_pos;
|
|
out->output_depth = mdec.output_depth;
|
|
out->output_signed = mdec.output_signed;
|
|
out->output_bit15 = mdec.output_bit15;
|
|
out->busy = mdec.busy;
|
|
out->input_full = mdec.input_full;
|
|
out->enable_dma_in = mdec.enable_dma_in;
|
|
out->enable_dma_out = mdec.enable_dma_out;
|
|
out->last_status = mdec.last_status;
|
|
out->decode_macroblocks = mdec.decode_macroblocks;
|
|
out->decode_blocks = mdec.decode_blocks;
|
|
out->decode_stop_reason = mdec.decode_stop_reason;
|
|
out->decode_input_pos = mdec.decode_input_pos;
|
|
out->decode_input_end = mdec.decode_input_end;
|
|
out->dma_in_words = mdec.dma_in_words;
|
|
out->dma_out_words = mdec.dma_out_words;
|
|
out->dma_read_underflows = mdec.dma_read_underflows;
|
|
}
|
|
|
|
uint64_t mdec_debug_get_event_total(void) {
|
|
return mdec_trace_seq;
|
|
}
|
|
|
|
uint32_t mdec_debug_copy_events(uint64_t seq_lo, uint64_t seq_hi,
|
|
MDECDebugEvent *out, uint32_t max_count) {
|
|
if (!out || max_count == 0) return 0;
|
|
uint64_t oldest = (mdec_trace_seq > MDEC_TRACE_CAP) ? mdec_trace_seq - MDEC_TRACE_CAP : 0;
|
|
if (seq_lo < oldest) seq_lo = oldest;
|
|
if (seq_hi > mdec_trace_seq) seq_hi = mdec_trace_seq;
|
|
uint32_t n = 0;
|
|
for (uint64_t seq = seq_lo; seq < seq_hi && n < max_count; seq++) {
|
|
out[n++] = mdec_trace[seq % MDEC_TRACE_CAP];
|
|
}
|
|
return n;
|
|
}
|
|
|
|
void mdec_debug_clear(void) {
|
|
memset(mdec_trace, 0, sizeof(mdec_trace));
|
|
mdec_trace_seq = 0;
|
|
mdec_trace_head = 0;
|
|
}
|
|
|
|
void mdec_debug_dma_in_start(uint32_t addr, uint32_t words) {
|
|
(void)addr;
|
|
trace_event(MDEC_EVT_DMA_IN_START, words);
|
|
}
|
|
|
|
void mdec_debug_dma_in_end(uint32_t addr, uint32_t words) {
|
|
(void)addr;
|
|
mdec.dma_in_words += words;
|
|
trace_event(MDEC_EVT_DMA_IN_END, words);
|
|
}
|
|
|
|
void mdec_debug_dma_out_start(uint32_t addr, uint32_t words) {
|
|
(void)addr;
|
|
trace_event(MDEC_EVT_DMA_OUT_START, words);
|
|
}
|
|
|
|
void mdec_debug_dma_out_end(uint32_t addr, uint32_t words) {
|
|
(void)addr;
|
|
trace_event(MDEC_EVT_DMA_OUT_END, words);
|
|
}
|