mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
mdec: keep "never decoded" never across a snapshot load
Fixes the false FMV flag after every snapshot load made before the first video (#475 cause 1); v1 payloads still load. Drops the unread host-frame stamp. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,9 @@ if(BUILD_TESTING)
|
||||
add_executable(ws_scene_hold_test tests/test_ws_scene_hold.c)
|
||||
target_include_directories(ws_scene_hold_test PRIVATE include)
|
||||
add_test(NAME ws_scene_hold_test COMMAND ws_scene_hold_test)
|
||||
add_executable(mdec_snapshot_activity_test tests/test_mdec_snapshot_activity.c)
|
||||
target_include_directories(mdec_snapshot_activity_test PRIVATE include src)
|
||||
add_test(NAME mdec_snapshot_activity_test COMMAND mdec_snapshot_activity_test)
|
||||
add_executable(mod_texture_banks_test tests/test_mod_texture_banks.c src/mod_texture_banks.c)
|
||||
target_include_directories(mod_texture_banks_test PRIVATE include)
|
||||
add_test(NAME mod_texture_banks_test COMMAND mod_texture_banks_test)
|
||||
|
||||
+34
-37
@@ -20,13 +20,12 @@
|
||||
|
||||
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;
|
||||
/* FMV-activity detector: guest-cycle stamp of the newest colour (15/24-bit)
|
||||
* MDEC decode, read by mdec_recently_active() / mdec_color_age_cycles(). The
|
||||
* snapshot carries its age, so peers with identical FIFO/tables but different
|
||||
* present rates hash equal. MDEC_NEVER_DECODED = no colour decode yet. */
|
||||
#define MDEC_NEVER_DECODED ((uint64_t)0 - 1000u)
|
||||
static uint64_t mdec_last_color_decode_cycle = MDEC_NEVER_DECODED;
|
||||
|
||||
enum {
|
||||
MDEC_CMD_NOP = 0,
|
||||
@@ -691,7 +690,6 @@ static void execute_decode(void) {
|
||||
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);
|
||||
}
|
||||
@@ -784,7 +782,7 @@ int mdec_recently_active(uint32_t within_frames) {
|
||||
}
|
||||
|
||||
uint64_t mdec_color_age_cycles(void) {
|
||||
if (mdec_last_color_decode_cycle == (uint64_t)0 - 1000u)
|
||||
if (mdec_last_color_decode_cycle == MDEC_NEVER_DECODED)
|
||||
return (uint64_t)0 - 1u;
|
||||
if (psx_cycle_count < mdec_last_color_decode_cycle)
|
||||
return (uint64_t)0 - 1u;
|
||||
@@ -796,10 +794,9 @@ void mdec_init(void) {
|
||||
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;
|
||||
/* A rematch resets the guest clock; a stale stamp would make
|
||||
* mdec_recently_active lie for the whole next session. */
|
||||
mdec_last_color_decode_cycle = MDEC_NEVER_DECODED;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
mdec.y_quant[i] = 1;
|
||||
mdec.uv_quant[i] = 1;
|
||||
@@ -1057,7 +1054,14 @@ void mdec_debug_dma_out_end(uint32_t addr, uint32_t words) {
|
||||
}
|
||||
|
||||
/* ---- boot_state snapshot (variable-length input/output FIFOs) ------------ */
|
||||
#define MDEC_SNAP_VER 1u
|
||||
/* v2: the colour-decode age is MDEC_SNAP_AGE_NEVER when nothing was decoded.
|
||||
* v1 wrote age 1000 for that case, which a load turned into "decoded 1000
|
||||
* cycles ago" -- a false FMV for the next ~8 frames after every snapshot load
|
||||
* made before the first video (issue #475). v1 payloads still load. */
|
||||
#define MDEC_SNAP_VER 2u
|
||||
#define MDEC_SNAP_VER_V1 1u
|
||||
#define MDEC_SNAP_AGE_NEVER UINT64_MAX
|
||||
#define MDEC_SNAP_V1_AGE_NEVER 1000ull
|
||||
#define MDEC_SNAP_INPUT_MAX (4u * 1024u * 1024u) /* halfwords */
|
||||
#define MDEC_SNAP_OUTPUT_MAX (8u * 1024u * 1024u) /* bytes */
|
||||
|
||||
@@ -1116,10 +1120,11 @@ void mdec_snapshot_write(uint8_t *p) {
|
||||
(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;
|
||||
if (mdec_last_color_decode_cycle == MDEC_NEVER_DECODED ||
|
||||
psx_cycle_count < mdec_last_color_decode_cycle)
|
||||
age = MDEC_SNAP_AGE_NEVER;
|
||||
else
|
||||
age = 1000ull;
|
||||
age = psx_cycle_count - mdec_last_color_decode_cycle;
|
||||
(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);
|
||||
@@ -1144,7 +1149,8 @@ static uint32_t mdec_wire_u32(const uint8_t *p, uint32_t off) {
|
||||
int mdec_snapshot_validate(const uint8_t *p, uint32_t len) {
|
||||
uint32_t input_count, output_size;
|
||||
if (!p || len < mdec_snap_fixed_bytes()) return 0;
|
||||
if (mdec_wire_u32(p, 0u) != MDEC_SNAP_VER) return 0;
|
||||
if (mdec_wire_u32(p, 0u) != MDEC_SNAP_VER &&
|
||||
mdec_wire_u32(p, 0u) != MDEC_SNAP_VER_V1) return 0;
|
||||
input_count = mdec_wire_u32(p, MDEC_SNAP_OFF_COUNTS);
|
||||
output_size = mdec_wire_u32(p, MDEC_SNAP_OFF_COUNTS + 4u);
|
||||
if (input_count > MDEC_SNAP_INPUT_MAX || output_size > MDEC_SNAP_OUTPUT_MAX)
|
||||
@@ -1157,11 +1163,12 @@ int mdec_snapshot_validate(const uint8_t *p, uint32_t len) {
|
||||
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;
|
||||
uint64_t age = MDEC_SNAP_AGE_NEVER;
|
||||
int16_t s16;
|
||||
if (!mdec_snapshot_validate(p, len)) 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, &ver) ||
|
||||
(ver != MDEC_SNAP_VER && ver != MDEC_SNAP_VER_V1)) return 0;
|
||||
if (!pst_r_u32(&r, &mdec.command) ||
|
||||
!pst_r_u32(&r, &mdec.expected_halfwords) ||
|
||||
!pst_r_u32(&r, &mdec.last_status) ||
|
||||
@@ -1213,24 +1220,14 @@ int mdec_snapshot_read(const uint8_t *p, uint32_t len) {
|
||||
}
|
||||
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;
|
||||
/* Age is guest cycles since the last colour decode. "Never" stays never:
|
||||
* restoring it as a recent decode reports FMV that is not playing. A decode
|
||||
* older than the restored clock's origin cannot be recent either. */
|
||||
if (ver == MDEC_SNAP_VER_V1 && age == MDEC_SNAP_V1_AGE_NEVER)
|
||||
age = MDEC_SNAP_AGE_NEVER;
|
||||
if (age == MDEC_SNAP_AGE_NEVER || age > psx_cycle_count)
|
||||
mdec_last_color_decode_cycle = MDEC_NEVER_DECODED;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#undef NDEBUG /* the Release test target must execute its assertions */
|
||||
/* The MDEC FMV-activity stamp survives a snapshot round trip as itself:
|
||||
* "never decoded" stays never (issue #475: it used to load as "decoded 1000
|
||||
* cycles ago", a false FMV for ~8 frames after every early snapshot load),
|
||||
* a real recent decode stays recent, and v1 payloads still load. */
|
||||
#include "../src/mdec.c"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
uint64_t psx_cycle_count;
|
||||
uint64_t s_frame_count;
|
||||
int debug_server_fmv_quiet(void) { return 1; }
|
||||
|
||||
#define FRAME 338688ull
|
||||
|
||||
static uint8_t *snap(uint32_t *len) {
|
||||
*len = mdec_snapshot_bytes();
|
||||
uint8_t *p = (uint8_t *)malloc(*len);
|
||||
assert(p);
|
||||
mdec_snapshot_write(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void put_u32(uint8_t *p, uint32_t off, uint32_t v) {
|
||||
for (int i = 0; i < 4; i++) p[off + i] = (uint8_t)(v >> (8 * i));
|
||||
}
|
||||
static void put_u64(uint8_t *p, uint32_t off, uint64_t v) {
|
||||
for (int i = 0; i < 8; i++) p[off + i] = (uint8_t)(v >> (8 * i));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
uint32_t len;
|
||||
uint8_t *p;
|
||||
const uint32_t age_off = MDEC_SNAP_OFF_COUNTS + 8u;
|
||||
|
||||
/* 1. Nothing decoded: save, load later in the session -> still idle. */
|
||||
mdec_init();
|
||||
psx_cycle_count = 100ull * FRAME;
|
||||
assert(!mdec_recently_active(8));
|
||||
p = snap(&len);
|
||||
psx_cycle_count = 200ull * FRAME;
|
||||
assert(mdec_snapshot_read(p, len));
|
||||
assert(!mdec_recently_active(8));
|
||||
assert(mdec_color_age_cycles() == (uint64_t)0 - 1u);
|
||||
free(p);
|
||||
|
||||
/* 2. A real decode 3 frames before the save stays 3 frames old. */
|
||||
mdec_init();
|
||||
psx_cycle_count = 500ull * FRAME;
|
||||
mdec_last_color_decode_cycle = psx_cycle_count - 3ull * FRAME;
|
||||
p = snap(&len);
|
||||
mdec_init();
|
||||
assert(!mdec_recently_active(8));
|
||||
psx_cycle_count = 500ull * FRAME;
|
||||
assert(mdec_snapshot_read(p, len));
|
||||
assert(mdec_recently_active(8));
|
||||
assert(mdec_color_age_cycles() == 3ull * FRAME);
|
||||
free(p);
|
||||
|
||||
/* 3. A v1 payload's "never" (age 1000) loads as never... */
|
||||
mdec_init();
|
||||
psx_cycle_count = 50ull * FRAME;
|
||||
p = snap(&len);
|
||||
put_u32(p, 0u, MDEC_SNAP_VER_V1);
|
||||
put_u64(p, age_off, MDEC_SNAP_V1_AGE_NEVER);
|
||||
assert(mdec_snapshot_validate(p, len));
|
||||
assert(mdec_snapshot_read(p, len));
|
||||
assert(!mdec_recently_active(8));
|
||||
/* ...and any other v1 age is a real decode. */
|
||||
put_u64(p, age_off, 2ull * FRAME);
|
||||
assert(mdec_snapshot_read(p, len));
|
||||
assert(mdec_recently_active(8));
|
||||
assert(mdec_color_age_cycles() == 2ull * FRAME);
|
||||
free(p);
|
||||
|
||||
/* 4. A decode older than the restored clock's origin is not recent. */
|
||||
mdec_init();
|
||||
psx_cycle_count = 900ull * FRAME;
|
||||
mdec_last_color_decode_cycle = 100ull * FRAME;
|
||||
p = snap(&len);
|
||||
psx_cycle_count = 10ull * FRAME;
|
||||
assert(mdec_snapshot_read(p, len));
|
||||
assert(!mdec_recently_active(8));
|
||||
free(p);
|
||||
|
||||
/* 5. Unknown versions are still refused. */
|
||||
mdec_init();
|
||||
p = snap(&len);
|
||||
put_u32(p, 0u, 3u);
|
||||
assert(!mdec_snapshot_validate(p, len));
|
||||
assert(!mdec_snapshot_read(p, len));
|
||||
free(p);
|
||||
|
||||
puts("mdec_snapshot_activity_test: PASS");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user