diff --git a/runtime/src/gpu_gl_renderer.c b/runtime/src/gpu_gl_renderer.c index 66ed85df..cbd7d2e1 100644 --- a/runtime/src/gpu_gl_renderer.c +++ b/runtime/src/gpu_gl_renderer.c @@ -5859,6 +5859,18 @@ static int s_pass_verify = -1; static uint8_t *s_pv_hr = NULL, *s_pv_raw = NULL; static size_t s_pv_hr_cap = 0, s_pv_raw_cap = 0; static int s_pv_ok = 1; +static uint64_t s_pv_cpu_hash = 0; + +/* The whole CPU VRAM: the guest-visible VRAM (GPUREAD, savestates, netplay + * digests and, in dual raster, the authoritative surface). */ +static uint64_t pass_cpu_vram_hash(void) { + uint64_t h = 1469598103934665603ULL; + if (!s_vram) return 0; + const uint64_t *w = (const uint64_t *)(const void *)s_vram; + for (size_t i = 0; i < (size_t)VRAM_W * VRAM_H / 4u; i++) + h = (h ^ w[i]) * 1099511628211ULL; + return h; +} uint64_t gl_renderer_perf_ticks(void) { return SDL_GetPerformanceCounter(); } uint64_t gl_renderer_perf_frequency(void) { return SDL_GetPerformanceFrequency(); } @@ -5886,8 +5898,11 @@ uint32_t gl_renderer_pass_unavailable(void) { if (!s_ctx || !s_raster_ok || !s_interp_enabled || s_interp_suspended || s_interp_source != 1 || !(s_interp_source_hz > 0.0)) return PSX_MOD_RENDER_PASS_NO_PRESENTER; - /* Dual raster (netplay CPU-authoritative VRAM) or a debug refusal. */ - if (s_cpu_auth_dual || s_pass_force_refuse) + /* Dual raster (netplay) hosts passes too: its authoritative surface is + * the CPU VRAM the pass backs up and restores (rect rows, journaled + * out-of-rect writes), and the FBO it presents is the one passes capture. + * PSX_RENDER_PASS_VERIFY=1 checks the whole CPU VRAM after each pass. */ + if (s_pass_force_refuse) return PSX_MOD_RENDER_PASS_BACKEND; /* Windowed high-resolution mode: the presented surfaces are the window * tiles at s_out_scale, which a pass (backing up s_hr_fbo at @@ -6287,7 +6302,10 @@ backed_up: const char *e = getenv("PSX_RENDER_PASS_VERIFY"); s_pass_verify = (e && e[0] && e[0] != '0') ? 1 : 0; } - if (s_pass_verify) pass_verify_read(&s_pv_hr, &s_pv_hr_cap, &s_pv_raw, &s_pv_raw_cap); + if (s_pass_verify) { + pass_verify_read(&s_pv_hr, &s_pv_hr_cap, &s_pv_raw, &s_pv_raw_cap); + s_pv_cpu_hash = pass_cpu_vram_hash(); + } s_pj_cpu.n = 0; s_pass_active = 1; return 1; @@ -6354,7 +6372,8 @@ void gl_renderer_pass_end(uint32_t alpha_q16, int keep) { pass_verify_read(&after_hr, &after_hr_cap, &after_raw, &after_raw_cap); s_pv_ok = s_pv_hr && after_hr && s_pv_raw && after_raw && memcmp(s_pv_hr, after_hr, hn) == 0 && - memcmp(s_pv_raw, after_raw, rn) == 0; + memcmp(s_pv_raw, after_raw, rn) == 0 && + pass_cpu_vram_hash() == s_pv_cpu_hash; } }