diff --git a/runtime/src/gpu_gl_renderer.c b/runtime/src/gpu_gl_renderer.c index c7f4c34f..bc6d6559 100644 --- a/runtime/src/gpu_gl_renderer.c +++ b/runtime/src/gpu_gl_renderer.c @@ -9795,13 +9795,13 @@ static void gl_rth_exec(void *user, const RtCmd *c, const void *payload) { break; #ifndef PSX_NO_DEBUG_TOOLS case RTH_RING_CAPTURE: rth_ring_capture_now(v); break; +#endif case RTH_DYN_STEP: s_dyn_rth_disp_valid = 1; memcpy(s_dyn_rth_disp, v + 1, sizeof s_dyn_rth_disp); (void)dyn_apply(v[0]); s_dyn_rth_disp_valid = 0; break; -#endif case RTH_PEEK: { uint64_t ptr = (uint64_t)(uint32_t)v[4] | ((uint64_t)(uint32_t)v[5] << 32); (void)gl_renderer_fbo_peek(v[0], v[1], v[2], v[3], (uint16_t *)(uintptr_t)ptr); diff --git a/runtime/tests/run_gl_render_thread.py b/runtime/tests/run_gl_render_thread.py index e78b39fd..e338ba7f 100644 --- a/runtime/tests/run_gl_render_thread.py +++ b/runtime/tests/run_gl_render_thread.py @@ -140,6 +140,24 @@ def main(): print(f"FAIL scale {s}: {k} differs with the render thread on " f"({runs[0][k]} off, {runs[1][k]} on)") ok = False + # Dynamic resolution: the same level steps between frames, recorded with + # the thread on (scales above 1 only; 1x has no levels below it). + for s in [int(v) for v in args.scales.split(",") if v and int(v) > 1]: + runs = {} + for threaded in (0, 1): + r = run([probe, s, threaded, args.frames, "dynres"]) + p = parse(r.stdout) + tail = r.stdout.strip().splitlines()[-7:] + print(f"scale {s} dynres render_thread={threaded}: exit={r.returncode}", tail, + r.stderr.strip()[-800:]) + if r.returncode or p["failures"] != 0 or any(p[k] is None for k in KEYS): + ok = False + runs[threaded] = p + for k in KEYS: + if runs[0][k] != runs[1][k]: + print(f"FAIL scale {s} dynres: {k} differs with the render thread on " + f"({runs[0][k]} off, {runs[1][k]} on)") + ok = False print("PASS" if ok else "FAIL") return 0 if ok else 1 diff --git a/runtime/tests/test_gl_render_thread.c b/runtime/tests/test_gl_render_thread.c index 11e79573..d7cd46f1 100644 --- a/runtime/tests/test_gl_render_thread.c +++ b/runtime/tests/test_gl_render_thread.c @@ -18,6 +18,13 @@ * stream latch that flips mid-frame (sent as state); * - presents and frame boundaries every frame, with readbacks (GPUREAD * style gr_vram_read, VRAM->CPU transfer_out) in some frames only. + * With argv[4] == "dynres" ([video] dynamic_resolution) the surfaces are + * allocated at the scale and the level steps between frames + * (gl_renderer_step_internal_scale_now after a frame boundary, as the + * controller does): recorded in stream order with the thread on, run at once + * with it off, or at once when the frame is held at a sync point; the + * render thread's frame cost is measured too. Back at the ceiling before the + * final readbacks. * Printed: rb (digest of every value the "guest" read back), digest (native * VRAM at the end), hires (frame at internal resolution), wide (native-wide * surface), and with the thread on its record/acquire counters. @@ -220,6 +227,7 @@ int main(int argc, char **argv) { int scale = argc > 1 ? atoi(argv[1]) : 1; int threaded = argc > 2 && argv[2][0] == '1'; int frames = argc > 3 ? atoi(argv[3]) : 80; + int dynres = argc > 4 && !strcmp(argv[4], "dynres"); /* No video device, window or GL context (headless, Windows over SSH): * exit 77, which the harness reports as a CTest skip. */ if (SDL_Init(SDL_INIT_VIDEO) != 0) { @@ -250,6 +258,7 @@ int main(int argc, char **argv) { gr_set_backend(GR_BACKEND_OPENGL); gr_init(vram); gr_set_scale(scale); + if (dynres) gl_renderer_set_dynamic_resolution(1); gl_renderer_set_swap_interval(0); if (!gl_renderer_init_context(win)) { fprintf(stderr, "FAIL context\n"); return 2; } check(gr_backend() == GR_BACKEND_OPENGL, "OpenGL backend"); @@ -259,7 +268,44 @@ int main(int argc, char **argv) { check(gl_renderer_render_thread_start(2) == 1, "render thread started"); check(gl_renderer_render_thread_active(), "render thread active"); } - for (int f = 0; f < frames; f++) frame(f, (f & 1) ? 512 : 0); + if (dynres) { + check(gl_renderer_dynamic_resolution_ceiling() == scale, "dynres ceiling"); + if (threaded) gl_renderer_render_thread_measure(1); + } + static const int levels[] = { 2, 1, 3, 1, 4, 2 }; + int nsteps = 0, level = scale; + for (int f = 0; f < frames; f++) { + frame(f, (f & 1) ? 512 : 0); + if (dynres && f % 7 == 6) { /* includes frames held at a sync point */ + int l = levels[(f / 7) % 6]; + if (l > scale) l = scale; + if (l != level) nsteps++; + level = l; + check(gl_renderer_step_internal_scale_now(l) == 1, "dynres step"); + GlDynresStats ds; + gl_renderer_dynres_stats(&ds); + check(ds.level == l, "dynres level as asked"); + check(gr_scale() == l, "gr_scale follows the level"); + } + } + if (dynres) { + if (level != scale) nsteps++; + check(gl_renderer_step_internal_scale_now(scale) == 1, "dynres back to the ceiling"); + gl_renderer_render_thread_sync("test"); + GlDynresStats ds; + gl_renderer_dynres_stats(&ds); + printf("dyn_steps=%llu asked=%d\n", (unsigned long long)ds.steps, nsteps); + check(ds.steps == (uint64_t)nsteps && ds.level == scale, "every step applied"); + if (threaded) { + GlRthCosts co; + gl_renderer_render_thread_costs(&co); + printf("rth_cost_frames=%llu gpu_frames=%llu dropped=%llu mean_ms=%.3f\n", + (unsigned long long)co.frames, (unsigned long long)co.gpu_frames, + (unsigned long long)co.dropped, + co.frames ? (double)co.cost_ns / (double)co.frames * 1e-6 : 0.0); + check(co.frames > 0, "render-thread frame costs measured"); + } + } /* Guest-visible VRAM, the frame at internal resolution, the wide surface. */ static uint16_t native[1024 * 512];