From bc2106f7c75468cc8620fde4f621537a7eee06ab Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:51:13 -0700 Subject: [PATCH] Test: the window mode refuses render passes Once the frame-rate stack's render passes land, a pass backs up and restores only s_hr_fbo, at s_hr_scale. In the 8K window mode the presented surfaces are the tiles at s_out_scale, which a pass would not restore, and the queued native-wide replay has no pass clamp, so the backend must refuse passes there (gl_renderer_pass_unavailable: s_hiw returns PSX_MOD_RENDER_PASS_BACKEND). Nothing enforced that but prose. test_gl_internal_scale_guards: where the renderer defines gl_renderer_pass_unavailable, its s_hiw refusal must return PSX_MOD_RENDER_PASS_BACKEND and pass_begin and pass_plan must go through gl_renderer_pass_ready; skipped before render passes exist. The GL fixture gains a passes mode, built only when the renderer has render passes: with the flip-aware blend ready, the full-VRAM surface at 9x offers passes and the window mode at 9x and 18x refuses them and pass_begin opens nothing. Co-Authored-By: Claude Opus 5.5 --- runtime/tests/run_gl_scale_invariance.py | 21 +++++++++++ .../tests/test_gl_internal_scale_guards.py | 22 ++++++++++++ runtime/tests/test_gl_scale_invariance.c | 36 +++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/runtime/tests/run_gl_scale_invariance.py b/runtime/tests/run_gl_scale_invariance.py index faa7737b..0f1991ad 100644 --- a/runtime/tests/run_gl_scale_invariance.py +++ b/runtime/tests/run_gl_scale_invariance.py @@ -24,6 +24,9 @@ Mask runs (mode mask) change the GP0(E6) mask-check bit after a line, a flat triangle or an opaque textured rect and before its batch is drawn: the draw must keep the check bit it was submitted under, at 1x, above it and in the window mode. +Pass runs (mode passes, where the renderer has the frame-rate stack's render +passes) check that the window mode refuses them and the full-VRAM surface +offers them. macOS/Linux: pass the SDL3 include directory and static library (for example from a runtime build tree's _deps/sdl3-src/include and @@ -111,6 +114,11 @@ def main(): # drops them (-dead_strip, or per-function sections with --gc-sections). # Anything still unresolved is a link error, not a NULL call at run time. sections = [] if platform.system() == "Darwin" else ["-ffunction-sections", "-fdata-sections"] + # Render passes (the frame-rate stack): build the fixture's passes mode. + renderer = (framework / "runtime/src/gpu_gl_renderer.c").read_text(encoding="utf-8") + passes = "uint32_t gl_renderer_pass_unavailable(void)" in renderer + if passes: + sections.append("-DPSX_TEST_RENDER_PASSES=1") objs = [] for name, src in sources: o = dest / (name + ".o") @@ -247,6 +255,19 @@ def main(): r.stderr.strip()[-600:]) if r.returncode or not parsed or parsed[1]: ok = False + # Render passes: offered on the full-VRAM surface, refused in the window mode. + if not passes: + print("passes: skipped (this renderer has no render passes)") + for label, s, extra in ((("full", 9, {}), ("window", 9, {"PSX_GL_HIRES_WINDOW": "1"}), + ("window", 18, {"PSX_GL_HIRES_WINDOW": "1"})) if passes else ()): + e = dict(env) + e.update(extra) + r = run([dest / "probe", s, "passes"], env=e) + parsed = parse_run(r.stdout) + print(f"passes {label} {s}: exit={r.returncode}", r.stdout.strip().splitlines()[-2:], + r.stderr.strip()[-600:]) + if r.returncode or not parsed or parsed[1]: + ok = False for label, s, budget in (("over-limit", 32, None), ("budget", 12, "40")): e = dict(env) if budget is not None: diff --git a/runtime/tests/test_gl_internal_scale_guards.py b/runtime/tests/test_gl_internal_scale_guards.py index 54d6e922..0aa758cd 100644 --- a/runtime/tests/test_gl_internal_scale_guards.py +++ b/runtime/tests/test_gl_internal_scale_guards.py @@ -275,6 +275,28 @@ class ScaleContractGuards(unittest.TestCase): name + " binds s_hr_fbo at s_out_scale without handling the window") +class RenderPassGuards(unittest.TestCase): + """Render passes (the frame-rate stack) back up and restore only + s_hr_fbo, at s_hr_scale. In the window mode the presented surfaces are the + tiles at s_out_scale, which a pass would not restore, and the queued wide + mirror replay has no pass clamp: the backend must refuse passes there. + Skipped where the renderer has no render passes; the GL fixture's passes + runs check the same refusal on a real context.""" + + def test_window_mode_refuses_passes(self): + if "uint32_t gl_renderer_pass_unavailable(void)" not in GL: + self.skipTest("no render passes in this tree") + refuse = body(GL, "uint32_t gl_renderer_pass_unavailable(void)") + self.assertRegex(strip_comments(refuse), + r"if \([^;{}]*\bs_hiw\b[^;{}]*\)\s*return PSX_MOD_RENDER_PASS_BACKEND;") + # Every way in goes through that refusal. + for sig in ("int gl_renderer_pass_begin(", "uint32_t gl_renderer_pass_plan("): + if sig in GL: + self.assertIn("gl_renderer_pass_ready()", body(GL, sig), sig) + self.assertIn("gl_renderer_pass_unavailable() == PSX_MOD_RENDER_PASS_READY", + body(GL, "int gl_renderer_pass_ready(void)")) + + class InternalResolutionGuards(unittest.TestCase): def test_hidpi_window_only_when_opted_in(self): self.assertIn("win_flags |= PSX_SDL_WINDOW_HIGH_DENSITY;", MAIN) diff --git a/runtime/tests/test_gl_scale_invariance.c b/runtime/tests/test_gl_scale_invariance.c index a87e9c35..3205a9ec 100644 --- a/runtime/tests/test_gl_scale_invariance.c +++ b/runtime/tests/test_gl_scale_invariance.c @@ -44,6 +44,12 @@ * check on (then off) or off (then on) before their batch is drawn: with * the check on the rect's pixels stay, with it off they are overwritten, * in the native VRAM and in the frame at S. + * - mode "passes" (built only where the renderer has render passes, the + * frame-rate stack; the runner defines PSX_TEST_RENDER_PASSES): with the + * flip-aware frame blend ready, the full-VRAM surface offers render passes + * and the window mode refuses them (PSX_MOD_RENDER_PASS_BACKEND), and + * gl_renderer_pass_begin opens nothing there: a pass backs up and restores + * only the authoritative surface, never the window's tiles. * Original source-owned scene; no retail payload. */ #include "gpu_gl_renderer.c" #include "mod_texture_banks.c" @@ -375,6 +381,28 @@ static int mask_main(int scale, int window) { return failures ? 1 : 0; } +#if defined(PSX_TEST_RENDER_PASSES) +/* Mode "passes": see the header. */ +static int passes_main(int scale, int window) { + (void)scale; + gl_renderer_set_interpolation(1, 120.0, 120.0, 60.0, 2); + gl_renderer_set_interpolation_source(1); + s_interp_valid = 2; /* as after the first presented frame */ + uint32_t why = gl_renderer_pass_unavailable(); + printf("passes: unavailable=%u\n", (unsigned)why); + if (window) { + check(why == PSX_MOD_RENDER_PASS_BACKEND, "window mode refuses render passes"); + check(!gl_renderer_pass_begin(0, 0, FRAME_W, FRAME_H, 1, 1, 0) && !s_pass_active, + "window mode: pass_begin opens nothing"); + } else { + check(why == PSX_MOD_RENDER_PASS_READY, "full-VRAM surface offers render passes"); + } + check(glGetError() == GL_NO_ERROR, "GL error"); + printf("checks=%d failures=%d\n", checks, failures); + return failures ? 1 : 0; +} +#endif + static int capture_main(int scale, int window) { GLuint fbo = s_hr_fbo; int sx = 0; @@ -605,6 +633,14 @@ int main(int argc, char **argv) { gl_renderer_shutdown(); SDL_DestroyWindow(win); SDL_Quit(); return rc; } +#if defined(PSX_TEST_RENDER_PASSES) + if (!strcmp(mode, "passes")) { + if (si.windowed) check(si.hr_scale == 1, "window mode engaged"); + int rc = passes_main(scale, si.windowed); + gl_renderer_shutdown(); SDL_DestroyWindow(win); SDL_Quit(); + return rc; + } +#endif if (!strcmp(mode, "mask")) { if (si.windowed) check(si.hr_scale == 1, "window mode engaged"); int rc = mask_main(scale, si.windowed);