From 2547f1bb61d5fe32a00a81326ddb9cbe814cd557 Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Wed, 21 Oct 2026 05:54:10 -0700 Subject: [PATCH] Elide identity VRAM copies without draining deferred scenes --- runtime/include/gpu_gl_renderer.h | 2 +- runtime/src/gpu_gl_renderer.c | 21 +++++++++++++++++++++ runtime/src/main.cpp | 4 ++-- runtime/tests/run_gl_render_thread.py | 5 +++-- runtime/tests/test_gl_render_thread.c | 4 ++++ 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/runtime/include/gpu_gl_renderer.h b/runtime/include/gpu_gl_renderer.h index bafb370f..29502940 100644 --- a/runtime/include/gpu_gl_renderer.h +++ b/runtime/include/gpu_gl_renderer.h @@ -571,7 +571,7 @@ int gl_renderer_perf_aggregate(int wide_filter, double out[18]); /* Bounded benchmark attribution. CPU spans require PSX_GL_CPU_PERF=1; * reading drains the render queue, so read outside the measured interval. */ typedef struct { - uint64_t counts[9]; + uint64_t counts[10]; uint64_t calls[8], ticks[8], max_ticks[8], frequency; uint64_t prior_ticks[8], prior_max_ticks[8]; } GlRenderWorkStats; diff --git a/runtime/src/gpu_gl_renderer.c b/runtime/src/gpu_gl_renderer.c index 66a18996..1ec7d79c 100644 --- a/runtime/src/gpu_gl_renderer.c +++ b/runtime/src/gpu_gl_renderer.c @@ -5410,8 +5410,28 @@ static void gpu_fill(int x,int y,int w,int h,uint16_t c) { /* VRAM->VRAM copy: blit the source region to the scratch texture (resolves * overlap), then draw it back at the destination through the BLIT program so * mask set/check and the stencil mirror apply, exactly like sw_copy_rect. */ +static uint64_t s_identity_copies; +static int identity_copy_fast(void) { + static int enabled = -1; + if (enabled < 0) { + const char *e = getenv("PSX_GL_IDENTITY_COPY_FAST"); + enabled = !(e && e[0] == '0'); + } + return enabled; +} static void gpu_copy_rect(int sx,int sy,int dx,int dy,int w,int h) { if (w <= 0 || h <= 0) return; + if (identity_copy_fast() && sx == dx && sy == dy && !s_mask_set && + sx >= 0 && sy >= 0 && sx < VRAM_W && sy < VRAM_H) { + /* No pixel can change. Retain the existing primitive batch boundary + * (PGXP's two-pass depth policy is batch-sensitive), but don't drain + * deferred high-resolution scenes or stage identical pixels. + * The guest GPU command and its scheduled device timing still run. */ + flush_flat_batch(); + flush_tex_batch(); + s_identity_copies++; + return; + } if (gl_cpu_mode()) { static int seen[32][6], used; int key[6] = {sx,sy,dx,dy,w,h}, i; @@ -6903,6 +6923,7 @@ void gl_renderer_work_stats(GlRenderWorkStats *out) { out->counts[4] = s_hq_center_pruned; out->counts[5] = s_tc_hits; out->counts[6] = s_tc_misses; out->counts[7] = s_tc_evictions; out->counts[8] = s_hiw_copy_blits; + out->counts[9] = s_identity_copies; } void gl_renderer_shutdown(void) { gl_renderer_render_thread_stop(); /* drains; the context comes back to this thread */ diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 2c1a92b3..1d907a1d 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -5081,8 +5081,8 @@ static void input_replay_frontend_tick(uint64_t frame) { f = std::fopen(path.c_str(), "w"); if (!f) throw std::runtime_error("cannot write input replay render report"); std::fputs("stat,count,total_us,max_us\n", f); - const char *counts[9] = {"queue_uploads", "replay_draws", "bloom_fused", "coverage_depth", "centre_pruned", "texel_cache_hits", "texel_cache_misses", "texel_cache_evictions", "hiw_copy_blits"}; - for (int i=0; i<9; ++i) std::fprintf(f, "%s,%llu,0,0\n", counts[i], (unsigned long long)stats.counts[i]); + const char *counts[10] = {"queue_uploads", "replay_draws", "bloom_fused", "coverage_depth", "centre_pruned", "texel_cache_hits", "texel_cache_misses", "texel_cache_evictions", "hiw_copy_blits", "identity_copies"}; + for (int i=0; i<10; ++i) std::fprintf(f, "%s,%llu,0,0\n", counts[i], (unsigned long long)stats.counts[i]); const char *spans[8] = {"depth_restore", "queue_replay", "vram_copy", "interp_capture", "interp_interval", "centre_copy", "depth_gpu_sample", "colour_gpu_sample"}; double us = stats.frequency ? 1e6 / stats.frequency : 0; for (int i=0; i<8; ++i) std::fprintf(f, "%s,%llu,%.3f,%.3f\n", spans[i], diff --git a/runtime/tests/run_gl_render_thread.py b/runtime/tests/run_gl_render_thread.py index af6daa3e..d0015f50 100644 --- a/runtime/tests/run_gl_render_thread.py +++ b/runtime/tests/run_gl_render_thread.py @@ -157,7 +157,7 @@ def main(): ok = False runs[threaded] = p if args.wide_present: - for margin, coverage, state, upload, bloom, cache, layers, copy in ((0,0,0,0,0,0,256,1), (0,0,1,1,1,1,256,1), (0,1,1,1,1,1,256,1), (1,0,1,1,1,1,256,1), (1,1,1,0,1,1,256,1), (1,1,1,1,0,1,256,1), (1,1,1,1,1,0,256,1), (1,1,1,1,1,1,4,1), (1,1,1,1,1,1,256,0)): + for margin, coverage, state, upload, bloom, cache, layers, copy, identity in ((0,0,0,0,0,0,256,1,1), (0,0,1,1,1,1,256,1,1), (0,1,1,1,1,1,256,1,1), (1,0,1,1,1,1,256,1,1), (1,1,1,0,1,1,256,1,1), (1,1,1,1,0,1,256,1,1), (1,1,1,1,1,0,256,1,1), (1,1,1,1,1,1,4,1,1), (1,1,1,1,1,1,256,0,1), (1,1,1,1,1,1,256,1,0)): reference = run([probe, s, threaded, args.frames, *mode], {"PSX_GL_WIDE_MARGIN_SCISSOR": str(margin), "PSX_GL_COVERAGE_FAST": str(coverage), @@ -166,7 +166,8 @@ def main(): "PSX_GL_BLOOM_FUSE_EXTEND": str(bloom), "PSX_GL_TEXEL_CACHE": str(cache), "PSX_GL_TEXEL_CACHE_LAYERS": str(layers), - "PSX_GL_HIW_COPY_FAST": str(copy)}) + "PSX_GL_HIW_COPY_FAST": str(copy), + "PSX_GL_IDENTITY_COPY_FAST": str(identity)}) baseline = parse(reference.stdout) if reference.returncode or baseline["failures"] != 0 or any(baseline[k] != p[k] for k in KEYS): print(f"FAIL scale {s}: margin={margin} coverage={coverage} changed rendered output", baseline, p) diff --git a/runtime/tests/test_gl_render_thread.c b/runtime/tests/test_gl_render_thread.c index f245e143..ccc91cc0 100644 --- a/runtime/tests/test_gl_render_thread.c +++ b/runtime/tests/test_gl_render_thread.c @@ -247,6 +247,10 @@ static void frame(int f, int buf_x) { gr_draw_flat_rect(576, 64, 32, 32, (uint16_t)(0x03e0 + f)); gr_draw_textured_rect(buf_x + 100, 200, 32, 32, 0, 64, 512, 256, 0x0009); gr_copy_rect(buf_x + 10, 10, buf_x + 14, 13, 80, 50); + /* Identity copies preserve colour/mask unless set-mask adds bit15. */ + mask_bits(f % 4 == 2, 0); + gr_copy_rect(buf_x + 14, 13, buf_x + 14, 13, 80, 50); + mask_bits(0, 0); if (quality_scene && f % 3 == 1) { /* Consume the copied bit15 mask before any later framebuffer clear. */ mask_bits(0, 1);