mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
1209 lines
43 KiB
C
1209 lines
43 KiB
C
#include "mdec.h"
|
||
#include "pst_wire.h"
|
||
#include "psx_cycles.h"
|
||
|
||
#include <stdint.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
/* x86-64 always has SSE2; MSVC x64 does not define __SSE2__. */
|
||
#if defined(__SSE2__) || defined(_M_X64) || defined(_M_AMD64) || \
|
||
(defined(_M_IX86_FP) && _M_IX86_FP >= 2)
|
||
#include <emmintrin.h>
|
||
#define MDEC_HAVE_SSE2 1
|
||
#endif
|
||
#if !defined(MDEC_HAVE_SSE2) && (defined(__ARM_NEON) || defined(__ARM_NEON__))
|
||
#include <arm_neon.h>
|
||
#define MDEC_HAVE_NEON 1
|
||
#endif
|
||
|
||
extern uint64_t s_frame_count;
|
||
|
||
/* FMV-activity detector: host display-frame stamp of the newest colour
|
||
* (15/24-bit) MDEC decode. Used only by mdec_recently_active() for local
|
||
* frontend / rewind policy — never folded into netplay digests. */
|
||
static uint64_t mdec_last_color_decode_frame = (uint64_t)0 - 1000u;
|
||
/* Guest-cycle stamp of the same event — snap age is relative to this so
|
||
* peers with identical FIFO/tables but different present rates hash equal. */
|
||
static uint64_t mdec_last_color_decode_cycle = (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) {
|
||
#ifdef PSX_NO_DEBUG_TOOLS
|
||
(void)kind;
|
||
(void)value;
|
||
return;
|
||
#else
|
||
extern int debug_server_fmv_quiet(void);
|
||
if (debug_server_fmv_quiet()) return;
|
||
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;
|
||
#endif
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
/* Reserve `bytes` of output room once (MotK FMV macroblocks write 512/768
|
||
* bytes each). Avoids ensure_output_capacity per channel byte. */
|
||
static uint8_t *output_reserve(uint32_t bytes) {
|
||
if (!ensure_output_capacity(mdec.output_size + bytes)) return NULL;
|
||
return mdec.output + mdec.output_size;
|
||
}
|
||
|
||
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). Two separable 1-D
|
||
* passes over the >>3 scale matrix: pass 1 keeps int16 and transposes
|
||
* (out[x*8+col]); pass 2 clamps via Mask9ClampS8. Rounding (sum+0x4000)>>15.
|
||
* SSE2 path matches Beetle's madd_epi16 reduce (bit-identical to scalar). */
|
||
|
||
static void idct_block_dc_only(int16_t *block)
|
||
{
|
||
int16_t tmp0[8];
|
||
int dc = block[0];
|
||
int x, col;
|
||
for (x = 0; x < 8; x++) {
|
||
int sum = dc * (int)mdec.scale[x * 8];
|
||
tmp0[x] = (int16_t)((sum + 0x4000) >> 15);
|
||
}
|
||
for (col = 0; col < 8; col++) {
|
||
for (x = 0; x < 8; x++) {
|
||
int sum = (int)tmp0[col] * (int)mdec.scale[x * 8];
|
||
block[col * 8 + x] =
|
||
(int16_t)mask9_clamp_s8((sum + 0x4000) >> 15);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void idct_block_scalar(int16_t *block)
|
||
{
|
||
int ac = 0;
|
||
int i, col, x, u;
|
||
int16_t tmp[64];
|
||
|
||
for (i = 1; i < 64; i++)
|
||
ac |= block[i];
|
||
if (!ac) {
|
||
idct_block_dc_only(block);
|
||
return;
|
||
}
|
||
|
||
for (col = 0; col < 8; col++) {
|
||
const int16_t *src = block + col * 8;
|
||
int col_or = (int)src[0] | (int)src[1] | (int)src[2] | (int)src[3] |
|
||
(int)src[4] | (int)src[5] | (int)src[6] | (int)src[7];
|
||
if (!col_or) {
|
||
for (x = 0; x < 8; x++)
|
||
tmp[x * 8 + col] = 0;
|
||
continue;
|
||
}
|
||
for (x = 0; x < 8; x++) {
|
||
int sum = 0;
|
||
const int16_t *sc = mdec.scale + x * 8;
|
||
for (u = 0; u < 8; u++)
|
||
sum += (int)src[u] * (int)sc[u];
|
||
tmp[x * 8 + col] = (int16_t)((sum + 0x4000) >> 15);
|
||
}
|
||
}
|
||
for (col = 0; col < 8; col++) {
|
||
const int16_t *src = tmp + col * 8;
|
||
int col_or = (int)src[0] | (int)src[1] | (int)src[2] | (int)src[3] |
|
||
(int)src[4] | (int)src[5] | (int)src[6] | (int)src[7];
|
||
if (!col_or) {
|
||
for (x = 0; x < 8; x++)
|
||
block[col * 8 + x] = 0;
|
||
continue;
|
||
}
|
||
for (x = 0; x < 8; x++) {
|
||
int sum = 0;
|
||
const int16_t *sc = mdec.scale + x * 8;
|
||
for (u = 0; u < 8; u++)
|
||
sum += (int)src[u] * (int)sc[u];
|
||
block[col * 8 + x] =
|
||
(int16_t)mask9_clamp_s8((sum + 0x4000) >> 15);
|
||
}
|
||
}
|
||
}
|
||
|
||
#if defined(MDEC_HAVE_SSE2)
|
||
/* Horizontal sum of 4×i32 after madd_epi16 — same reduce as Beetle mdec.c. */
|
||
static int idct_sse2_dot8(const int16_t *src8, const int16_t *scale8)
|
||
{
|
||
__m128i c = _mm_loadu_si128((const __m128i *)src8);
|
||
__m128i m = _mm_loadu_si128((const __m128i *)scale8);
|
||
__m128i sum = _mm_madd_epi16(m, c);
|
||
int32_t tmp[4] __attribute__((aligned(16)));
|
||
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, _MM_SHUFFLE(0, 1, 2, 3)));
|
||
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, _MM_SHUFFLE(0, 0, 0, 1)));
|
||
_mm_store_si128((__m128i *)tmp, sum);
|
||
return tmp[0];
|
||
}
|
||
|
||
static void idct_block_sse2(int16_t *block)
|
||
{
|
||
int ac = 0;
|
||
int i, col, x;
|
||
int16_t tmp[64] __attribute__((aligned(16)));
|
||
|
||
for (i = 1; i < 64; i++)
|
||
ac |= block[i];
|
||
if (!ac) {
|
||
idct_block_dc_only(block);
|
||
return;
|
||
}
|
||
|
||
for (col = 0; col < 8; col++) {
|
||
const int16_t *src = block + col * 8;
|
||
int col_or = (int)src[0] | (int)src[1] | (int)src[2] | (int)src[3] |
|
||
(int)src[4] | (int)src[5] | (int)src[6] | (int)src[7];
|
||
if (!col_or) {
|
||
for (x = 0; x < 8; x++)
|
||
tmp[x * 8 + col] = 0;
|
||
continue;
|
||
}
|
||
for (x = 0; x < 8; x++) {
|
||
int sum = idct_sse2_dot8(src, mdec.scale + x * 8);
|
||
tmp[x * 8 + col] = (int16_t)((sum + 0x4000) >> 15);
|
||
}
|
||
}
|
||
for (col = 0; col < 8; col++) {
|
||
const int16_t *src = tmp + col * 8;
|
||
int col_or = (int)src[0] | (int)src[1] | (int)src[2] | (int)src[3] |
|
||
(int)src[4] | (int)src[5] | (int)src[6] | (int)src[7];
|
||
if (!col_or) {
|
||
for (x = 0; x < 8; x++)
|
||
block[col * 8 + x] = 0;
|
||
continue;
|
||
}
|
||
for (x = 0; x < 8; x++) {
|
||
int sum = idct_sse2_dot8(src, mdec.scale + x * 8);
|
||
block[col * 8 + x] =
|
||
(int16_t)mask9_clamp_s8((sum + 0x4000) >> 15);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void idct_simd_selfcheck(void)
|
||
{
|
||
uint32_t seed = 0xC0FFEEu;
|
||
int n;
|
||
for (n = 0; n < 64; n++)
|
||
mdec.scale[n] = (int16_t)(((n % 9) - 4) * 256);
|
||
/* Deterministic patterns + PRNG — abort if SSE2 ever drifts from scalar. */
|
||
for (n = 0; n < 512; n++) {
|
||
int16_t a[64], b[64];
|
||
int i;
|
||
for (i = 0; i < 64; i++) {
|
||
seed = seed * 1664525u + 1013904223u;
|
||
a[i] = (int16_t)((int)(seed >> 16) - 32768);
|
||
if (n < 8)
|
||
a[i] = (i == 0) ? (int16_t)(n * 100) : 0; /* DC-only */
|
||
b[i] = a[i];
|
||
}
|
||
idct_block_scalar(a);
|
||
idct_block_sse2(b);
|
||
if (memcmp(a, b, sizeof(a)) != 0)
|
||
abort();
|
||
}
|
||
}
|
||
|
||
static void idct_block(int16_t *block)
|
||
{
|
||
idct_block_sse2(block);
|
||
}
|
||
#else
|
||
static void idct_block(int16_t *block)
|
||
{
|
||
idct_block_scalar(block);
|
||
}
|
||
#endif
|
||
|
||
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;
|
||
}
|
||
|
||
/* Emit one YCbCr pixel into a pre-reserved output cursor (bit-identical to
|
||
* the former append_byte path). Returns advanced cursor. */
|
||
static uint8_t *emit_rgb_pixel(uint8_t *out, 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;
|
||
out[0] = (uint8_t)packed;
|
||
out[1] = (uint8_t)(packed >> 8);
|
||
return out + 2;
|
||
}
|
||
/* 24bpp (mdec.cpp:370-393): rgb_xor = signed ? 0x80 : 0x00. */
|
||
uint8_t rgb_xor = mdec.output_signed ? 0x80u : 0x00u;
|
||
out[0] = (uint8_t)(ru ^ rgb_xor);
|
||
out[1] = (uint8_t)(gu ^ rgb_xor);
|
||
out[2] = (uint8_t)(bu ^ rgb_xor);
|
||
return out + 3;
|
||
}
|
||
|
||
static void append_luma_block(const int16_t *yblk) {
|
||
uint8_t *out = output_reserve(64u);
|
||
if (!out) return;
|
||
for (int i = 0; i < 64; i++) out[i] = to_output_u8(yblk[i]);
|
||
mdec.output_size += 64u;
|
||
}
|
||
|
||
#if defined(MDEC_HAVE_SSE2)
|
||
/* Beetle mdec.c EncodeRow24 — 8 luma + 4 chroma → 24 RGB bytes, bit-exact. */
|
||
#define MDEC_MUL32(a, b) \
|
||
_mm_or_si128( \
|
||
_mm_and_si128(_mm_mul_epu32((a), (b)), _mm_set1_epi64x(0xFFFFFFFFull)),\
|
||
_mm_slli_epi64( \
|
||
_mm_mul_epu32(_mm_srli_epi64((a), 32), _mm_srli_epi64((b), 32)), \
|
||
32))
|
||
#define MDEC_M9(v) \
|
||
_mm_min_epi16(_mm_max_epi16(_mm_srai_epi16(_mm_slli_epi16((v), 7), 7), \
|
||
_mm_set1_epi16(-128)), \
|
||
_mm_set1_epi16(127))
|
||
|
||
static void mdec_encode_row24_sse2(const int16_t *by, const int16_t *cb4,
|
||
const int16_t *cr4, uint8_t rgb_xor,
|
||
uint8_t *out)
|
||
{
|
||
int16_t cbd[8], crd[8];
|
||
int8_t by8[8];
|
||
int l, i;
|
||
__m128i R, G, B;
|
||
__m128i flip = _mm_set1_epi16((short)(0x80 ^ rgb_xor));
|
||
uint8_t r8[8], g8[8], b8[8];
|
||
__m128i y16, CB, CR, cb_lo, cb_hi, cr_lo, cr_hi, y_lo, y_hi;
|
||
const __m128i k359 = _mm_set1_epi32(359);
|
||
const __m128i k454 = _mm_set1_epi32(454);
|
||
const __m128i km88 = _mm_set1_epi32(-88);
|
||
const __m128i km183 = _mm_set1_epi32(-183);
|
||
const __m128i bias = _mm_set1_epi32(0x80);
|
||
__m128i r_lo, r_hi, b_lo, b_hi, a0, a1, c0, c1, g_lo, g_hi;
|
||
|
||
for (l = 0; l < 8; l++)
|
||
by8[l] = (int8_t)by[l];
|
||
for (l = 0; l < 4; l++) {
|
||
cbd[2 * l] = cbd[2 * l + 1] = (int16_t)cb4[l];
|
||
crd[2 * l] = crd[2 * l + 1] = (int16_t)cr4[l];
|
||
}
|
||
|
||
y16 = _mm_srai_epi16(
|
||
_mm_slli_epi16(
|
||
_mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)by8),
|
||
_mm_setzero_si128()),
|
||
8),
|
||
8);
|
||
CB = _mm_loadu_si128((const __m128i *)cbd);
|
||
CR = _mm_loadu_si128((const __m128i *)crd);
|
||
cb_lo = _mm_srai_epi32(_mm_unpacklo_epi16(CB, CB), 16);
|
||
cb_hi = _mm_srai_epi32(_mm_unpackhi_epi16(CB, CB), 16);
|
||
cr_lo = _mm_srai_epi32(_mm_unpacklo_epi16(CR, CR), 16);
|
||
cr_hi = _mm_srai_epi32(_mm_unpackhi_epi16(CR, CR), 16);
|
||
y_lo = _mm_srai_epi32(_mm_unpacklo_epi16(y16, y16), 16);
|
||
y_hi = _mm_srai_epi32(_mm_unpackhi_epi16(y16, y16), 16);
|
||
|
||
r_lo = _mm_add_epi32(
|
||
y_lo, _mm_srai_epi32(_mm_add_epi32(MDEC_MUL32(k359, cr_lo), bias), 8));
|
||
r_hi = _mm_add_epi32(
|
||
y_hi, _mm_srai_epi32(_mm_add_epi32(MDEC_MUL32(k359, cr_hi), bias), 8));
|
||
b_lo = _mm_add_epi32(
|
||
y_lo, _mm_srai_epi32(_mm_add_epi32(MDEC_MUL32(k454, cb_lo), bias), 8));
|
||
b_hi = _mm_add_epi32(
|
||
y_hi, _mm_srai_epi32(_mm_add_epi32(MDEC_MUL32(k454, cb_hi), bias), 8));
|
||
a0 = _mm_andnot_si128(_mm_set1_epi32(0x1F), MDEC_MUL32(km88, cb_lo));
|
||
a1 = _mm_andnot_si128(_mm_set1_epi32(0x1F), MDEC_MUL32(km88, cb_hi));
|
||
c0 = _mm_andnot_si128(_mm_set1_epi32(0x07), MDEC_MUL32(km183, cr_lo));
|
||
c1 = _mm_andnot_si128(_mm_set1_epi32(0x07), MDEC_MUL32(km183, cr_hi));
|
||
g_lo = _mm_add_epi32(
|
||
y_lo,
|
||
_mm_srai_epi32(_mm_add_epi32(_mm_add_epi32(a0, c0), bias), 8));
|
||
g_hi = _mm_add_epi32(
|
||
y_hi,
|
||
_mm_srai_epi32(_mm_add_epi32(_mm_add_epi32(a1, c1), bias), 8));
|
||
R = MDEC_M9(_mm_packs_epi32(r_lo, r_hi));
|
||
G = MDEC_M9(_mm_packs_epi32(g_lo, g_hi));
|
||
B = MDEC_M9(_mm_packs_epi32(b_lo, b_hi));
|
||
R = _mm_xor_si128(_mm_and_si128(R, _mm_set1_epi16(0xFF)), flip);
|
||
G = _mm_xor_si128(_mm_and_si128(G, _mm_set1_epi16(0xFF)), flip);
|
||
B = _mm_xor_si128(_mm_and_si128(B, _mm_set1_epi16(0xFF)), flip);
|
||
_mm_storel_epi64((__m128i *)r8, _mm_packus_epi16(R, R));
|
||
_mm_storel_epi64((__m128i *)g8, _mm_packus_epi16(G, G));
|
||
_mm_storel_epi64((__m128i *)b8, _mm_packus_epi16(B, B));
|
||
for (i = 0; i < 8; i++) {
|
||
out[i * 3 + 0] = r8[i];
|
||
out[i * 3 + 1] = g8[i];
|
||
out[i * 3 + 2] = b8[i];
|
||
}
|
||
}
|
||
#undef MDEC_MUL32
|
||
#undef MDEC_M9
|
||
#endif /* MDEC_HAVE_SSE2 */
|
||
|
||
static void append_color_macroblock(const int16_t *crblk, const int16_t *cbblk,
|
||
const int16_t yblk[4][64]) {
|
||
/* 16×16: 512 bytes @15bpp, 768 @24bpp — reserve once per MB. */
|
||
uint32_t need = (mdec.output_depth == 3) ? 512u : 768u;
|
||
uint8_t *out = output_reserve(need);
|
||
if (!out) return;
|
||
uint8_t *p = out;
|
||
#if defined(MDEC_HAVE_SSE2)
|
||
/* MotK FMV is 24bpp — row SIMD (Beetle EncodeRow24). 16bpp stays scalar. */
|
||
if (mdec.output_depth != 3) {
|
||
uint8_t rgb_xor = mdec.output_signed ? 0x80u : 0x00u;
|
||
int py;
|
||
for (py = 0; py < 16; py++) {
|
||
int y_left = (py >= 8 ? 2 : 0);
|
||
int y_right = y_left + 1;
|
||
int ly = py & 7;
|
||
int crow = (py >> 1) * 8;
|
||
mdec_encode_row24_sse2(&yblk[y_left][ly * 8], &cbblk[crow],
|
||
&crblk[crow], rgb_xor, p);
|
||
p += 24;
|
||
mdec_encode_row24_sse2(&yblk[y_right][ly * 8], &cbblk[crow + 4],
|
||
&crblk[crow + 4], rgb_xor, p);
|
||
p += 24;
|
||
}
|
||
mdec.output_size += need;
|
||
return;
|
||
}
|
||
#endif
|
||
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;
|
||
p = emit_rgb_pixel(p, yblk[y_index][lx + ly * 8],
|
||
crblk[chroma], cbblk[chroma]);
|
||
}
|
||
}
|
||
mdec.output_size += need;
|
||
}
|
||
|
||
/* 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;
|
||
mdec_last_color_decode_cycle = psx_cycle_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) {
|
||
/* Guest-cycle hysteresis (not host s_frame_count). Present-skip / Replay
|
||
* leave s_frame_count stale so a single decode looked "recent" forever
|
||
* (false FMV lockstep on rematch; tip episodes into MotK FMV entry). */
|
||
const uint64_t cycles_per_frame = 338688ull;
|
||
const uint64_t window =
|
||
(uint64_t)within_frames * cycles_per_frame + (cycles_per_frame / 2ull);
|
||
if (psx_cycle_count < mdec_last_color_decode_cycle)
|
||
return 0;
|
||
return (psx_cycle_count - mdec_last_color_decode_cycle) <= window;
|
||
}
|
||
|
||
uint64_t mdec_color_age_cycles(void) {
|
||
if (mdec_last_color_decode_cycle == (uint64_t)0 - 1000u)
|
||
return (uint64_t)0 - 1u;
|
||
if (psx_cycle_count < mdec_last_color_decode_cycle)
|
||
return (uint64_t)0 - 1u;
|
||
return psx_cycle_count - mdec_last_color_decode_cycle;
|
||
}
|
||
|
||
void mdec_init(void) {
|
||
memset(&mdec, 0, sizeof(mdec));
|
||
memset(mdec_trace, 0, sizeof(mdec_trace));
|
||
mdec_trace_seq = 0;
|
||
mdec_trace_head = 0;
|
||
/* Rematch resets s_frame_count; a stale stamp makes mdec_recently_active
|
||
* wrap and lie for the whole next session. */
|
||
mdec_last_color_decode_frame = (uint64_t)0 - 1000u;
|
||
mdec_last_color_decode_cycle = (uint64_t)0 - 1000u;
|
||
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;
|
||
#if defined(MDEC_HAVE_SSE2)
|
||
idct_simd_selfcheck();
|
||
memset(mdec.scale, 0, sizeof(mdec.scale));
|
||
/* YCbCr row vs scalar emit — MotK FMV path (depth 2 = 24bpp). */
|
||
{
|
||
uint32_t seed = 0xBADC0DEEu;
|
||
int n, i;
|
||
mdec.output_depth = 2;
|
||
mdec.output_signed = 0;
|
||
for (n = 0; n < 256; n++) {
|
||
int16_t by[8], cb4[4], cr4[4];
|
||
uint8_t sse[24], ref[24];
|
||
uint8_t *rp = ref;
|
||
for (i = 0; i < 8; i++) {
|
||
seed = seed * 1664525u + 1013904223u;
|
||
by[i] = (int16_t)((int)(seed % 255u) - 128);
|
||
}
|
||
for (i = 0; i < 4; i++) {
|
||
seed = seed * 1664525u + 1013904223u;
|
||
cb4[i] = (int16_t)((int)(seed % 255u) - 128);
|
||
seed = seed * 1664525u + 1013904223u;
|
||
cr4[i] = (int16_t)((int)(seed % 255u) - 128);
|
||
}
|
||
mdec_encode_row24_sse2(by, cb4, cr4, 0, sse);
|
||
for (i = 0; i < 8; i++)
|
||
rp = emit_rgb_pixel(rp, by[i], cr4[i >> 1], cb4[i >> 1]);
|
||
if (memcmp(sse, ref, 24) != 0)
|
||
abort();
|
||
}
|
||
mdec.output_signed = 1;
|
||
for (n = 0; n < 64; n++) {
|
||
int16_t by[8], cb4[4], cr4[4];
|
||
uint8_t sse[24], ref[24];
|
||
uint8_t *rp = ref;
|
||
for (i = 0; i < 8; i++) {
|
||
seed = seed * 1664525u + 1013904223u;
|
||
by[i] = (int16_t)((int)(seed % 255u) - 128);
|
||
}
|
||
for (i = 0; i < 4; i++) {
|
||
seed = seed * 1664525u + 1013904223u;
|
||
cb4[i] = (int16_t)((int)(seed % 255u) - 128);
|
||
seed = seed * 1664525u + 1013904223u;
|
||
cr4[i] = (int16_t)((int)(seed % 255u) - 128);
|
||
}
|
||
mdec_encode_row24_sse2(by, cb4, cr4, 0x80u, sse);
|
||
for (i = 0; i < 8; i++)
|
||
rp = emit_rgb_pixel(rp, by[i], cr4[i >> 1], cb4[i >> 1]);
|
||
if (memcmp(sse, ref, 24) != 0)
|
||
abort();
|
||
}
|
||
mdec.output_signed = 0;
|
||
mdec.output_depth = 3;
|
||
}
|
||
#endif
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
/* Feed up to `max_words` from a contiguous LE word source (DMA ch0). Stops
|
||
* early if the FIFO becomes not-ready after a decode completes mid-burst.
|
||
* Guest bytes + decode triggers match N× mdec_dma_write_word. */
|
||
uint32_t mdec_dma_write_words(const uint32_t *src, uint32_t max_words) {
|
||
uint32_t moved = 0;
|
||
while (moved < max_words) {
|
||
if (!mdec_dma_write_ready()) break;
|
||
/* Fast fill while a DECODE/QUANT/SCALE command is collecting input —
|
||
* same halfword packing + single execute_command as write_data. */
|
||
if (mdec.busy && mdec.input_count < mdec.expected_halfwords) {
|
||
uint32_t need_hw = mdec.expected_halfwords - mdec.input_count;
|
||
uint32_t need_words = (need_hw + 1u) / 2u;
|
||
uint32_t n = max_words - moved;
|
||
if (n > need_words) n = need_words;
|
||
for (uint32_t i = 0; i < n; i++) {
|
||
uint32_t value = src[moved++];
|
||
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();
|
||
}
|
||
continue;
|
||
}
|
||
write_data(src[moved++]);
|
||
}
|
||
return moved;
|
||
}
|
||
|
||
uint32_t mdec_dma_read_word(void) {
|
||
uint32_t value = 0;
|
||
uint32_t start_pos = mdec.output_pos;
|
||
uint32_t avail = (start_pos < mdec.output_size)
|
||
? (mdec.output_size - start_pos) : 0u;
|
||
if (avail >= 4u) {
|
||
memcpy(&value, mdec.output + start_pos, sizeof(value));
|
||
mdec.output_pos = start_pos + 4u;
|
||
} else {
|
||
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;
|
||
}
|
||
|
||
/* Drain up to `max_words` into a contiguous LE destination (DMA ch1). */
|
||
uint32_t mdec_dma_read_words(uint32_t *dst, uint32_t max_words) {
|
||
uint32_t moved = 0;
|
||
while (moved < max_words && mdec.output_pos < mdec.output_size) {
|
||
dst[moved] = mdec_dma_read_word();
|
||
moved++;
|
||
}
|
||
return moved;
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
/* ---- boot_state snapshot (variable-length input/output FIFOs) ------------ */
|
||
#define MDEC_SNAP_VER 1u
|
||
#define MDEC_SNAP_INPUT_MAX (4u * 1024u * 1024u) /* halfwords */
|
||
#define MDEC_SNAP_OUTPUT_MAX (8u * 1024u * 1024u) /* bytes */
|
||
|
||
static uint32_t mdec_snap_fixed_bytes(void) {
|
||
/* ver + scalars + tables + counts + last_color_age (guest cycles) */
|
||
return 4u + /* ver */
|
||
4u * 14u + /* u32 scalars */
|
||
1u * 8u + /* u8 flags */
|
||
64u + 64u + /* y/uv quant */
|
||
64u * 2u + /* scale i16 */
|
||
4u + 4u + /* input_count, output_size */
|
||
8u; /* last_color_age in guest cycles */
|
||
}
|
||
|
||
uint32_t mdec_snapshot_bytes(void) {
|
||
uint64_t n = (uint64_t)mdec_snap_fixed_bytes() +
|
||
(uint64_t)mdec.input_count * 2u +
|
||
(uint64_t)mdec.output_size;
|
||
if (n > 0xffffffffu) return 0;
|
||
return (uint32_t)n;
|
||
}
|
||
|
||
void mdec_snapshot_write(uint8_t *p) {
|
||
PstW w;
|
||
uint32_t n = mdec_snapshot_bytes();
|
||
uint64_t age;
|
||
if (!p || n == 0) return;
|
||
pst_w_init(&w, p, n);
|
||
(void)pst_w_u32(&w, MDEC_SNAP_VER);
|
||
(void)pst_w_u32(&w, mdec.command);
|
||
(void)pst_w_u32(&w, mdec.expected_halfwords);
|
||
(void)pst_w_u32(&w, mdec.last_status);
|
||
(void)pst_w_u32(&w, mdec.decode_macroblocks);
|
||
(void)pst_w_u32(&w, mdec.decode_blocks);
|
||
(void)pst_w_u32(&w, mdec.decode_stop_reason);
|
||
(void)pst_w_u32(&w, mdec.decode_input_pos);
|
||
(void)pst_w_u32(&w, mdec.decode_input_end);
|
||
(void)pst_w_u32(&w, mdec.dma_in_words);
|
||
(void)pst_w_u32(&w, mdec.dma_out_words);
|
||
(void)pst_w_u32(&w, mdec.dma_read_underflows);
|
||
(void)pst_w_u32(&w, mdec.output_pos);
|
||
(void)pst_w_u32(&w, 0u); /* reserved */
|
||
(void)pst_w_u32(&w, 0u); /* reserved */
|
||
(void)pst_w_u8(&w, mdec.output_bit15);
|
||
(void)pst_w_u8(&w, mdec.output_signed);
|
||
(void)pst_w_u8(&w, mdec.output_depth);
|
||
(void)pst_w_u8(&w, mdec.current_block);
|
||
(void)pst_w_u8(&w, mdec.busy);
|
||
(void)pst_w_u8(&w, mdec.input_full);
|
||
(void)pst_w_u8(&w, mdec.enable_dma_in);
|
||
(void)pst_w_u8(&w, mdec.enable_dma_out);
|
||
(void)pst_w_bytes(&w, mdec.y_quant, 64u);
|
||
(void)pst_w_bytes(&w, mdec.uv_quant, 64u);
|
||
for (int i = 0; i < 64; i++)
|
||
(void)pst_w_i16(&w, mdec.scale[i]);
|
||
(void)pst_w_u32(&w, mdec.input_count);
|
||
(void)pst_w_u32(&w, mdec.output_size);
|
||
/* Guest-cycle age (not host s_frame_count) — netplay aux digests this blob. */
|
||
if (psx_cycle_count >= mdec_last_color_decode_cycle)
|
||
age = psx_cycle_count - mdec_last_color_decode_cycle;
|
||
else
|
||
age = 1000ull;
|
||
(void)pst_w_u64(&w, age);
|
||
for (uint32_t i = 0; i < mdec.input_count; i++)
|
||
(void)pst_w_u16(&w, mdec.input ? mdec.input[i] : 0u);
|
||
if (mdec.output_size && mdec.output)
|
||
(void)pst_w_bytes(&w, mdec.output, mdec.output_size);
|
||
}
|
||
|
||
int mdec_snapshot_read(const uint8_t *p, uint32_t len) {
|
||
PstR r;
|
||
uint32_t ver = 0, input_count = 0, output_size = 0, reserved;
|
||
uint64_t age = 1000ull;
|
||
int16_t s16;
|
||
if (!p || len < mdec_snap_fixed_bytes()) return 0;
|
||
pst_r_init(&r, p, len);
|
||
if (!pst_r_u32(&r, &ver) || ver != MDEC_SNAP_VER) return 0;
|
||
if (!pst_r_u32(&r, &mdec.command) ||
|
||
!pst_r_u32(&r, &mdec.expected_halfwords) ||
|
||
!pst_r_u32(&r, &mdec.last_status) ||
|
||
!pst_r_u32(&r, &mdec.decode_macroblocks) ||
|
||
!pst_r_u32(&r, &mdec.decode_blocks) ||
|
||
!pst_r_u32(&r, &mdec.decode_stop_reason) ||
|
||
!pst_r_u32(&r, &mdec.decode_input_pos) ||
|
||
!pst_r_u32(&r, &mdec.decode_input_end) ||
|
||
!pst_r_u32(&r, &mdec.dma_in_words) ||
|
||
!pst_r_u32(&r, &mdec.dma_out_words) ||
|
||
!pst_r_u32(&r, &mdec.dma_read_underflows) ||
|
||
!pst_r_u32(&r, &mdec.output_pos) ||
|
||
!pst_r_u32(&r, &reserved) ||
|
||
!pst_r_u32(&r, &reserved))
|
||
return 0;
|
||
if (!pst_r_u8(&r, &mdec.output_bit15) ||
|
||
!pst_r_u8(&r, &mdec.output_signed) ||
|
||
!pst_r_u8(&r, &mdec.output_depth) ||
|
||
!pst_r_u8(&r, &mdec.current_block) ||
|
||
!pst_r_u8(&r, &mdec.busy) ||
|
||
!pst_r_u8(&r, &mdec.input_full) ||
|
||
!pst_r_u8(&r, &mdec.enable_dma_in) ||
|
||
!pst_r_u8(&r, &mdec.enable_dma_out))
|
||
return 0;
|
||
if (!pst_r_bytes(&r, mdec.y_quant, 64u) ||
|
||
!pst_r_bytes(&r, mdec.uv_quant, 64u))
|
||
return 0;
|
||
for (int i = 0; i < 64; i++) {
|
||
if (!pst_r_i16(&r, &s16)) return 0;
|
||
mdec.scale[i] = s16;
|
||
}
|
||
if (!pst_r_u32(&r, &input_count) || !pst_r_u32(&r, &output_size) ||
|
||
!pst_r_u64(&r, &age))
|
||
return 0;
|
||
if (input_count > MDEC_SNAP_INPUT_MAX || output_size > MDEC_SNAP_OUTPUT_MAX)
|
||
return 0;
|
||
if (mdec.output_pos > output_size) return 0;
|
||
if ((size_t)(r.end - r.p) <
|
||
(size_t)input_count * 2u + (size_t)output_size)
|
||
return 0;
|
||
if (!ensure_input_capacity(input_count ? input_count : 1u)) return 0;
|
||
if (!ensure_output_capacity(output_size ? output_size : 1u)) return 0;
|
||
mdec.input_count = input_count;
|
||
mdec.output_size = output_size;
|
||
for (uint32_t i = 0; i < input_count; i++) {
|
||
uint16_t hw;
|
||
if (!pst_r_u16(&r, &hw)) return 0;
|
||
mdec.input[i] = hw;
|
||
}
|
||
if (output_size && !pst_r_bytes(&r, mdec.output, output_size))
|
||
return 0;
|
||
/* Age is guest cycles since last colour decode (SNAP_VER=1 payload). */
|
||
if (age > (1ull << 40))
|
||
age = (1ull << 40);
|
||
if (age >= psx_cycle_count)
|
||
mdec_last_color_decode_cycle = 0;
|
||
else
|
||
mdec_last_color_decode_cycle = psx_cycle_count - age;
|
||
/* Refresh host-frame hysteresis for local FMV policy only (~1 frame ≈
|
||
* 338688 cycles @ NTSC). Cap so recently_active stays meaningful. */
|
||
{
|
||
const uint64_t cycles_per_frame = 338688ull;
|
||
uint64_t frames_ago = age / cycles_per_frame;
|
||
if (frames_ago > 100000ull)
|
||
frames_ago = 100000ull;
|
||
if (frames_ago >= s_frame_count)
|
||
mdec_last_color_decode_frame = 0;
|
||
else
|
||
mdec_last_color_decode_frame = s_frame_count - frames_ago;
|
||
}
|
||
return 1;
|
||
}
|