From dc2cd301d350c082aa895928da6c062f2efd46bb Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Wed, 7 Oct 2026 22:17:28 -0700 Subject: [PATCH] PGXP depth: no cost when off; render-thread-safe setters; Smooth motion honours depth - gpu.c sends gr_set_depth_triangle only while a PGXP renderer feature is on (gl_renderer_pgxp_render_wanted): no RTH_DEPTH per triangle otherwise. - The SZ rides in the textured vertex's unused colour alpha, so TEXV stays 26 floats for every title. - gl_renderer_set_pgxp_depth / _threshold / pgxp_render_stats sync with a live render thread (GL_RT_SYNC) like their neighbours. - A depth-mode change is its own batch-break reason (7); batch_diag returns all eight reasons. - Smooth-motion in-between frames replay RTH_DEPTH and test depth from their own clear; gl_frame_gen_test gains a PGXP pass (overlapping 3D triangles out of painter order) where the in-between frame must match the real one. Co-Authored-By: Claude Opus 5.5 --- docs/ENHANCEMENTS.md | 12 ++++ runtime/include/gpu_gl_renderer.h | 5 +- runtime/src/debug_server.c | 5 +- runtime/src/gpu.c | 7 ++- runtime/src/gpu_gl_renderer.c | 64 ++++++++++++++++----- runtime/tests/gpu_exec_stubs.inc | 2 + runtime/tests/run_gl_frame_gen.py | 34 +++++++++++ runtime/tests/test_gl_frame_gen.c | 31 +++++++++- runtime/tests/test_gpu_gp0_history_guard.py | 1 + 9 files changed, 140 insertions(+), 21 deletions(-) diff --git a/docs/ENHANCEMENTS.md b/docs/ENHANCEMENTS.md index 69917852..96750b0d 100644 --- a/docs/ENHANCEMENTS.md +++ b/docs/ENHANCEMENTS.md @@ -1270,6 +1270,18 @@ differ by 801 / 92 / 25 / 0 px, none in the tachometer area. Diagnostic: `PSX_PGXP_TRI_LOG=` logs every triangle (depth mode, x y SZ) while `.on` exists. +**Off costs nothing; Smooth motion matches.** gpu.c sends +`gr_set_depth_triangle` only while a PGXP renderer feature is on +(`gl_renderer_pgxp_render_wanted`), so with them off the render thread +records no extra `RTH_DEPTH` per triangle. The SZ rides in the textured +vertex's unused colour alpha (negative when present), so the vertex stays 26 +floats for every title. The setters sync with a live render thread +(`GL_RT_SYNC`) like their neighbours. A depth-mode change ends a textured +batch as its own reason (`batch_diag` entry 8). Smooth-motion in-between +frames replay `RTH_DEPTH` and test depth like the real frame, from their +own clear: `gl_frame_gen_test` draws overlapping PGXP triangles out of painter +order and requires the in-between frame at phase 1 to equal the real one. + ## IR1 — Internal resolution presets (Native … 8K) and the GL scale ceiling (2026-09-26) **What the player gets.** Settings → Display → **Internal resolution**: Native, diff --git a/runtime/include/gpu_gl_renderer.h b/runtime/include/gpu_gl_renderer.h index 12f76a2e..238f6be2 100644 --- a/runtime/include/gpu_gl_renderer.h +++ b/runtime/include/gpu_gl_renderer.h @@ -267,6 +267,9 @@ float gl_renderer_get_post_gamma(void); void gl_renderer_set_wide_fast(int on); /* PGXP renderer features (docs/ENHANCEMENTS.md G1.14), all off by default: * depth buffer for opaque 3D polygons and the depth clear threshold in SZ units (DuckStation's default 4096). */ +/* Nonzero when a PGXP renderer feature is on: only then does gpu.c send + * gr_set_depth_triangle (no extra command per triangle otherwise). Any thread. */ +int gl_renderer_pgxp_render_wanted(void); void gl_renderer_set_pgxp_depth(int on); int gl_renderer_get_pgxp_depth(void); void gl_renderer_set_pgxp_depth_threshold(float sz); @@ -489,7 +492,7 @@ void gl_renderer_draw_projected_triangle(const PSXProjectedVertex vertices[3], int perspective); /* Cumulative textured-batch diagnostics: total, then flushes caused by * isolation, blend-mode, mask, filter, backdrop-gate, texture-window, capacity. */ -void gl_renderer_batch_diag(uint64_t out[8]); +void gl_renderer_batch_diag(uint64_t out[9]); /* Texture-window batching ([video] texture_window_batching; default off). * On: textured prims with different GP0(E2h) texture windows share a batch diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index a8e42dc7..d0baae5a 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -8574,11 +8574,11 @@ static void handle_frame_perf(int id, const char *json) double wcanon = wide[5] - wide[10]; if (wcanon < 0) wcanon = 0; double wmpp = wide[12] > 0 ? wide[10] * 1000.0 / wide[12] : 0.0; double tex_frac = 0.0; gl_renderer_perf_prim_split(&tex_frac); - uint64_t br[8]; extern void gl_renderer_batch_diag(uint64_t out[8]); + uint64_t br[9]; extern void gl_renderer_batch_diag(uint64_t out[9]); gl_renderer_batch_diag(br); send_fmt("{\"id\":%d,\"ok\":true,\"samples\":%d,\"wide_frames\":%d,\"frames_4_3\":%d," "\"tex_frac\":%.3f,\"ws_ablate\":%d," - "\"batch_diag\":[%llu,%llu,%llu,%llu,%llu,%llu,%llu,%llu]," + "\"batch_diag\":[%llu,%llu,%llu,%llu,%llu,%llu,%llu,%llu,%llu]," "\"all\":{\"total_ms_avg\":%.3f,\"total_ms_max\":%.3f,\"emu_cpu_ms_avg\":%.3f," "\"present_wall_ms_avg\":%.3f,\"scene_gpu_ms_avg\":%.3f,\"scene_gpu_ms_max\":%.3f," "\"present_gpu_ms_avg\":%.3f,\"present_gpu_ms_max\":%.3f,\"prims_avg\":%.0f}," @@ -8598,6 +8598,7 @@ static void handle_frame_perf(int id, const char *json) (unsigned long long)br[2], (unsigned long long)br[3], (unsigned long long)br[4], (unsigned long long)br[5], (unsigned long long)br[6], (unsigned long long)br[7], + (unsigned long long)br[8], all[1], all[2], all[3], all[4], all[5], all[6], all[7], all[8], all[9], (int)wide[0], wide[1], wide[3], wide[5], wide[6], wide[7], wide[9], wpp, wide[10], wide[11], wcanon, wide[12], wmpp, diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index fe49f7bf..dfc0fbaa 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -4612,7 +4612,10 @@ static void prepare_precise_triangle(int i0, int i1, int i2, gl_renderer_fg_source(id, pc, hd, vx, vy); } gr_set_perspective_triangle(0, 0.0f, 0.0f, 0.0f); - gr_set_depth_triangle(0, 0.0f, 0.0f, 0.0f); + /* PGXP depth (G1.14) only reaches the renderer while a PGXP renderer + * feature is on: with them off no depth command is sent per triangle. */ + const int want_depth = gl_renderer_pgxp_render_wanted(); + if (want_depth) gr_set_depth_triangle(0, 0.0f, 0.0f, 0.0f); const int geometry = gte_geometry_correction_enabled(); if (!geometry && !s_native_wide_projection_correction) { gr_set_precise_triangle(0, 0,0, 0,0, 0,0); @@ -4670,7 +4673,7 @@ static void prepare_precise_triangle(int i0, int i1, int i2, return; } gr_set_precise_triangle(1, fx[0],fy[0], fx[1],fy[1], fx[2],fy[2]); - if (vz[0] && vz[1] && vz[2]) + if (want_depth && vz[0] && vz[1] && vz[2]) gr_set_depth_triangle(1, (float)vz[0], (float)vz[1], (float)vz[2]); } diff --git a/runtime/src/gpu_gl_renderer.c b/runtime/src/gpu_gl_renderer.c index c66da12c..6accea90 100644 --- a/runtime/src/gpu_gl_renderer.c +++ b/runtime/src/gpu_gl_renderer.c @@ -715,7 +715,7 @@ static GLuint s_tex_prog = 0, s_tex_vao = 0, s_tex_vbo = 0; * fragment shader read the noperspective varying — i.e. bit-identical to the * pre-feature pipeline. twin is the prim's GP0(E2h) texture window, its low 20 * bits as a whole float (mask x, mask y, offset x, offset y; 5 bits each). */ -#define TEXV 27 /* + a_pz at 26 (PGXP depth, G1.14) */ +#define TEXV 26 static GLuint s_blit_prog = 0, s_blit_vao = 0, s_blit_vbo = 0; static GLuint s_blit_hi_prog = 0; /* windowed hi surface blit */ static GLint s_uBhSrc = -1, s_uBhPass = -1, s_uBhMaskset = -1, s_uBhSrcDiv = -1; @@ -1596,7 +1596,6 @@ static const char *TEX_VS = "layout(location=10) in float a_twin; /* GP0(E2h) bits 0..19 */\n" "layout(location=11) in vec4 a_hd_source; /* page origin + native extent */\n" "layout(location=12) in float a_hd_mode;\n" - "layout(location=13) in float a_pz; /* PGXP: sz; 0 = none */\n" "uniform float u_shift;\n" "uniform float u_xoff; /* native-wide x translation (px); 0 canonical */\n" "uniform float u_xhalf; /* x clip half-extent (px); 512 canonical */\n" @@ -1631,7 +1630,9 @@ static const char *TEX_VS = " * the rasterizer interpolates the smooth varying hyperbolically. With\n" " * a_q == 0 (feature off) w is exactly 1.0 and this is the old expression. */\n" " float w = (a_q > 0.0) ? (1.0 / a_q) : 1.0;\n" - " float sz = a_pz, zn = 0.0;\n" + " /* PGXP (G1.14): a_col.a is 1.0 (unused) unless a PGXP 3D vertex\n" + " * stores -sz there, so the vertex stays TEXV floats for every title. */\n" + " float sz = a_col.a < 0.0 ? -a_col.a : 0.0, zn = 0.0;\n" " if (sz > 0.5) { zn = 1.0 - 512.0 / (max(sz * (1.0 - u_zbias) - (u_zbias > 0.0 ? 48.0 : 0.0), 1.0) + 256.0); }\n" " vec2 ndc = vec2((xb+u_shift+u_xoff)/u_xhalf - 1.0, (a_pos.y+u_shift)/256.0 - 1.0);\n" " gl_Position = vec4(ndc * w, zn * w, w); }\n"; @@ -2921,15 +2922,29 @@ void gl_renderer_set_wide_fast(int on) { GL_RT_SYNC("set_wide_fast"); s_wide_fast = on ? 1 : 0; } int gl_renderer_get_wide_fast(void) { return s_wide_fast; } -/* PGXP renderer features (G1.14). Plain flags read at append time, so they - * can flip between any two primitives. */ +/* PGXP renderer features (G1.14). Flags read at append time, so they can + * flip between any two primitives. Like their neighbours the setters sync + * with a live render thread first (GL_RT_SYNC), so a change never lands in + * the middle of a replayed frame; they are safe at session_reboot. */ +static int s_pgxp_render_wanted = 0; /* any PGXP renderer feature on */ +static void pgxp_render_wanted_update(void) { + __atomic_store_n(&s_pgxp_render_wanted, s_pgxp_depth ? 1 : 0, __ATOMIC_RELEASE); +} +int gl_renderer_pgxp_render_wanted(void) { + return __atomic_load_n(&s_pgxp_render_wanted, __ATOMIC_ACQUIRE); +} void gl_renderer_set_pgxp_depth(int on) { + GL_RT_SYNC("set_pgxp_depth"); s_pgxp_depth = on ? 1 : 0; s_depth_need_clear = 1; + s_pz_valid = 0; + pgxp_render_wanted_update(); } int gl_renderer_get_pgxp_depth(void) { return s_pgxp_depth; } -void gl_renderer_set_pgxp_depth_threshold(float sz) { s_pgxp_depth_threshold = sz; } +void gl_renderer_set_pgxp_depth_threshold(float sz) { + GL_RT_SYNC("set_pgxp_depth_threshold"); s_pgxp_depth_threshold = sz; } void gl_renderer_pgxp_render_stats(uint64_t *depth_tris, uint64_t *depth_clears) { + GL_RT_SYNC("pgxp_render_stats"); if (depth_tris) *depth_tris = s_depth_tris; if (depth_clears) *depth_clears = s_depth_clears; } @@ -3128,10 +3143,10 @@ void gl_renderer_set_texture_window_batching(int on) { GL_RT_SYNC("set_texture_window_batching"); s_twin_batching = on ? 1 : 0; } int gl_renderer_get_texture_window_batching(void) { return s_twin_batching; } -void gl_renderer_batch_diag(uint64_t out[8]) { +void gl_renderer_batch_diag(uint64_t out[9]) { GL_RT_SYNC("batch_diag"); out[0] = s_batch_total; - for (int i = 0; i < 7; i++) out[i + 1] = s_batch_reason[i]; + for (int i = 0; i < 8; i++) out[i + 1] = s_batch_reason[i]; } /* Draw the queued textured batch with correct PSX mask-bit handling AND correct @@ -4169,7 +4184,7 @@ static void gpu_textured_triangle(const int *xs, const int *ys, else if ((!s_twin_batching || s_mask_check) && (twx != s_tb_twin[0] || twy != s_tb_twin[1] || tox != s_tb_twin[2] || toy != s_tb_twin[3])) reason = 5; - else if (tdmode != s_tb_depth) reason = 6; + else if (tdmode != s_tb_depth) reason = 7; } if (reason >= 0) { s_batch_reason[reason]++; @@ -4204,12 +4219,14 @@ static void gpu_textured_triangle(const int *xs, const int *ys, vp[21] = (float)hd.origin_u; vp[22] = (float)hd.origin_v; vp[23] = (float)hd.source_width; vp[24] = (float)hd.source_height; vp[25] = (float)hd.alpha_mode; - vp[26] = 0.0f; /* a_pz */ } if (pgxp_tri_is_3d()) { float *t0 = &s_tb[s_tb_n * TEXV]; float z[3] = { s_pz[0], s_pz[1], s_pz[2] }; - for (int i = 0; i < 3; i++) t0[i * TEXV + 26] = pgxp_vertex_code(z[i]); + for (int i = 0; i < 3; i++) { + const float code = pgxp_vertex_code(z[i]); + if (code > 0.0f) t0[i * TEXV + 7] = -code; /* a_col.a (TEX_VS) */ + } } pgxp_tri_log_tri('T', tdmode, semi, &s_tb[s_tb_n * TEXV], TEXV); s_tb_n += 3; @@ -5407,7 +5424,6 @@ static int init_gpu_raster(void) { p_glVertexAttribPointer(10, 1, GL_FLOAT, GL_FALSE, st, (void*)(20*sizeof(float))); p_glEnableVertexAttribArray(10); /* twin */ p_glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, st, (void*)(21*sizeof(float))); p_glEnableVertexAttribArray(11); /* HD source */ p_glVertexAttribPointer(12, 1, GL_FLOAT, GL_FALSE, st, (void*)(25*sizeof(float))); p_glEnableVertexAttribArray(12); /* HD alpha mode */ - p_glVertexAttribPointer(13, 1, GL_FLOAT, GL_FALSE, st, (void*)(26*sizeof(float))); p_glEnableVertexAttribArray(13); /* pz */ } p_glGenVertexArrays(1, &s_blit_vao); @@ -10897,7 +10913,9 @@ static void fg_draw_tri_at(uint16_t op, const int32_t *v, const void *payload, a[3], a[4], a[5]); return; } - if (!integral) { + /* A PGXP depth triangle (s_pz_valid) is 3D only with sub-pixel + * positions, as in the real frame: keep them even when integral. */ + if (!integral || s_pz_valid) { glb_set_precise_triangle(1, (int32_t)lrintf(x[0] * 65536.0f), (int32_t)lrintf(y[0] * 65536.0f), (int32_t)lrintf(x[1] * 65536.0f), (int32_t)lrintf(y[1] * 65536.0f), (int32_t)lrintf(x[2] * 65536.0f), (int32_t)lrintf(y[2] * 65536.0f)); @@ -10980,7 +10998,12 @@ static void fg_replay(const FgList *a, const FgList *b, double t, GLuint gen_wid glb_set_perspective_triangle(v[0], q[0], q[1], q[2]); break; } - case RTH_DEPTH: break; /* generated frames draw without PGXP depth */ + case RTH_DEPTH: { /* PGXP depth (G1.14): as the real frame */ + float z[3]; + memcpy(z, &v[1], sizeof z); + glb_set_depth_triangle(v[0], z[0], z[1], z[2]); + break; + } case RTH_AREA: glb_set_draw_area(v[0], v[1], v[2], v[3]); break; case RTH_OFFSET: glb_set_draw_offset(v[0], v[1]); break; case RTH_STATE: s_rths_flat_bd = v[0]; s_rths_vp_w = v[1]; s_rths_bg_full = v[2]; break; @@ -11034,6 +11057,7 @@ static void fg_replay(const FgList *a, const FgList *b, double t, GLuint gen_wid const int pi = b->rec2prim ? b->rec2prim[r] : -1; if (pi < 0) { /* outside the display: drawn into VRAM already */ pc_pending = 0; + precise_consumed(); break; } const FgPrim *pb = &b->prims.v[pi]; @@ -11046,6 +11070,7 @@ static void fg_replay(const FgList *a, const FgList *b, double t, GLuint gen_wid } if (gone) { /* crossed the camera plane: not in this frame */ pc_pending = 0; + precise_consumed(); break; } } else { @@ -11127,7 +11152,14 @@ static int fg_generate(double t, int swap) { fg_state_capture(&real); const GLuint real_wide_cur = g_wide_cur; const int real_pc = s_pc_valid, real_pq = s_pq_valid, real_pz = s_pz_valid; - s_pz_valid = 0; /* generated frames draw without PGXP depth */ + /* PGXP depth (G1.14): the generated frame tests depth like the real one, + * from its own clear (the surface's depth is not the real frame's), and + * leaves the real stream's depth bookkeeping and counters untouched. */ + const int real_dneed = s_depth_need_clear, real_dused = s_depth_used; + const float real_davg = s_depth_last_avg; + const uint64_t real_dtris = s_depth_tris, real_dclears = s_depth_clears; + s_pz_valid = 0; + s_depth_need_clear = 1; DirtyRect pack = s_pack_dirty, sten = s_stencil_stale, cpu = s_cpu_dirty; const int sten_valid = s_stencil_valid, gpu_dirty = s_gpu_dirty; uint64_t pres_dirty[PRES_ROWS]; @@ -11208,6 +11240,8 @@ static int fg_generate(double t, int swap) { } fg_state_apply(&real, real_wide_cur); s_pc_valid = real_pc; s_pq_valid = real_pq; s_pz_valid = real_pz; + s_depth_need_clear = real_dneed; s_depth_used = real_dused; s_depth_last_avg = real_davg; + s_depth_tris = real_dtris; s_depth_clears = real_dclears; s_pack_dirty = pack; s_stencil_stale = sten; s_cpu_dirty = cpu; s_stencil_valid = sten_valid; s_gpu_dirty = gpu_dirty; memcpy(s_present_dirty, pres_dirty, sizeof pres_dirty); diff --git a/runtime/tests/gpu_exec_stubs.inc b/runtime/tests/gpu_exec_stubs.inc index 476c8350..29e3dfaf 100644 --- a/runtime/tests/gpu_exec_stubs.inc +++ b/runtime/tests/gpu_exec_stubs.inc @@ -425,6 +425,8 @@ void gl_renderer_note_wide_triangle_recovery(int enabled) { } /* Smooth motion (frame generation) vertex sources: off in these fixtures. */ int gl_renderer_frame_generation(void) { return 0; } +/* PGXP renderer features: off in these fixtures. */ +int gl_renderer_pgxp_render_wanted(void) { return 0; } void gl_renderer_fg_source(const uint32_t id[3], const int32_t pc[9], const int32_t h[3], const int32_t x[3], const int32_t y[3]) { (void)id; (void)pc; (void)h; (void)x; (void)y; diff --git a/runtime/tests/run_gl_frame_gen.py b/runtime/tests/run_gl_frame_gen.py index 0a2d696d..c86b5e2b 100644 --- a/runtime/tests/run_gl_frame_gen.py +++ b/runtime/tests/run_gl_frame_gen.py @@ -28,6 +28,8 @@ WIN_LIBS = ["opengl32", "kernel32", "user32", "gdi32", "winmm", "imm32", "ole32" SKIP_EXIT = 77 WINDOWS = os.name == "nt" or platform.system().startswith(("MINGW", "MSYS", "CYGWIN")) KEYS = ("real",) +# FG_PGXP values: the scene without PGXP renderer features first. +PGXP_FEATURES = ("none", "depth") def parse(stdout): @@ -187,6 +189,38 @@ def main(): if worst > 1 or worst_px > 0.0005: print(f"FAIL scale {s} {path} {timing} fg={fg}: present thread changed real frames") ok = False + # PGXP renderer features (docs/ENHANCEMENTS.md G1.14): the scene as PGXP + # 3D triangles that overlap out of painter order. Per feature set, the + # real frames are the same with generation off and on, and the fixture's + # own check holds: the generated frame at phase 1 equals the real one + # (generated frames honour the features). Each feature changes the + # real image (it is visible in this scene), so the comparison means + # something. + for s in [int(v) for v in args.scales.split(",") if v]: + digests = {} + for feats in PGXP_FEATURES: + runs = {} + for fg in (0, 1): + sub = dest / f"pgxp-{s}-{feats}-{fg}" + sub.mkdir(exist_ok=True) + cmd = [str(c) for c in (probe, s, fg, "vram", args.frames, "flip")] + r = subprocess.run(cmd, cwd=sub, capture_output=True, text=True, encoding="utf-8", + errors="replace", env=dict(os.environ, FG_PGXP=feats)) + p = parse(r.stdout) + tail = r.stdout.strip().splitlines()[-5:] + print(f"scale {s} pgxp={feats} frame_generation={fg}: exit={r.returncode}", tail, + r.stderr.strip()[-800:]) + if r.returncode or p["failures"] != 0 or p["real"] is None: + ok = False + runs[fg] = p["real"] + if runs[0] != runs[1]: + print(f"FAIL scale {s} pgxp={feats}: the real frames differ with generation on") + ok = False + digests[feats] = runs[0] + for feats in PGXP_FEATURES[1:]: + if digests[feats] == digests[PGXP_FEATURES[0]]: + print(f"FAIL scale {s} pgxp={feats}: the feature does not change the real image") + ok = False print("PASS" if ok else "FAIL") return 0 if ok else 1 diff --git a/runtime/tests/test_gl_frame_gen.c b/runtime/tests/test_gl_frame_gen.c index c2950bd2..d57aee59 100644 --- a/runtime/tests/test_gl_frame_gen.c +++ b/runtime/tests/test_gl_frame_gen.c @@ -179,6 +179,14 @@ static void textures(void) { } static int wide_mode; +/* FG_PGXP= (run_gl_frame_gen.py's PGXP pass): every triangle is a + * PGXP 3D triangle (sub-pixel positions and per-vertex GTE SZ, as gpu.c + * sends them while a PGXP renderer feature is on), packed so neighbours + * overlap with later triangles farther away: the depth buffer, not painter + * order, decides the overlaps. "depth" turns the depth buffer on; "none" + * draws the same scene without it. The generated frame at phase 1 must still + * equal the real one. */ +static int g_pgxp, g_pgxp_depth; /* What gpu.c records for a GTE-projected triangle (gl_renderer_fg_source): * vertex identities and camera-space positions that project (H = z = 1000) * to the triangle's screen positions relative to its buffer. */ @@ -200,6 +208,12 @@ static void fixture_sources(int i, int bx, int x, int y) { pc[3 * k] = xs[k] - bx; pc[3 * k + 1] = ys[k]; pc[3 * k + 2] = 1000; h[k] = 1000; } gl_renderer_fg_source(id, pc, h, xs, ys); + if (g_pgxp) { + gr_set_precise_triangle(1, xs[0] * 65536, ys[0] * 65536, xs[1] * 65536, ys[1] * 65536, + xs[2] * 65536, ys[2] * 65536); + const float z = 2000.0f + 400.0f * (float)i; /* steps beyond the 2% tolerance */ + gr_set_depth_triangle(1, z, z + 7.0f * (float)(i % 3), z + 3.0f); + } } /* One game frame drawn into buffer bx. Positions move by whole pixels with @@ -218,7 +232,7 @@ static void game_frame_part(int g, int bx, int part) { for (int i = part ? 20 : 0; i < (part ? 40 : 20); i++) { /* The camera pans one pixel per game frame (every triangle moves * alike); triangle 5 is an object moving on its own. */ - int x = bx + 10 + (i % 8) * 36 + (i == 5 ? (g * 3) % 9 : g % 9) - 4; + int x = bx + 10 + (i % 8) * (g_pgxp ? 20 : 36) + (i == 5 ? (g * 3) % 9 : g % 9) - 4; int y = 12 + (i / 8) * 42 + (i == 5 ? g % 5 : 0); if (i == 0 && g % 5 == 0) { x = bx + 250 - (g * 37) % 200; y = 180; } fixture_sources(i, bx, x, y); @@ -251,6 +265,11 @@ int main(int argc, char **argv) { * the next one (as R4 does), so a flip is not a list boundary. */ int late = argc > 5 && !strcmp(argv[5], "late"); pt_mode = argc > 6 && !strcmp(argv[6], "pt"); + { + const char *e = getenv("FG_PGXP"); + g_pgxp = e && *e; + g_pgxp_depth = g_pgxp && strstr(e, "depth") != NULL; + } if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "SKIP no video (%s)\n", SDL_GetError()); return 77; @@ -283,6 +302,10 @@ int main(int argc, char **argv) { textures(); if (wide_mode) gr_wide_configure(426, 53); if (pt_mode) gl_renderer_set_present_thread(1, 3); + if (g_pgxp) { + gl_renderer_set_pgxp_depth(g_pgxp_depth); + check(gl_renderer_pgxp_render_wanted() == g_pgxp_depth, "PGXP renderer features as asked"); + } check(gl_renderer_render_thread_start(2) == 1, "render thread started"); if (pt_mode) check(gl_renderer_present_thread_active(), "present thread started"); if (fg) { @@ -371,6 +394,12 @@ int main(int argc, char **argv) { check(n_gen > 0, "generated frames were presented"); } check(glGetError() == GL_NO_ERROR, "GL error"); + if (g_pgxp_depth) { + uint64_t dt = 0, dc = 0; + gl_renderer_pgxp_render_stats(&dt, &dc); + printf("pgxp depth_tris=%llu depth_clears=%llu\n", (unsigned long long)dt, (unsigned long long)dc); + check(dt > 0 && dc > 0, "PGXP depth tested the triangles"); + } printf("real=%016llx\n", (unsigned long long)real_seq); printf("presents=%llu generated=%llu\n", (unsigned long long)n_real, (unsigned long long)n_gen); gl_renderer_shutdown(); /* stops the render and present threads: all shown */ diff --git a/runtime/tests/test_gpu_gp0_history_guard.py b/runtime/tests/test_gpu_gp0_history_guard.py index 9d66a156..9d7189e9 100644 --- a/runtime/tests/test_gpu_gp0_history_guard.py +++ b/runtime/tests/test_gpu_gp0_history_guard.py @@ -294,6 +294,7 @@ void gl_renderer_note_wide_triangle_recovery(int enabled) { if (enabled) abort(); } /* Smooth motion vertex sources are an OpenGL path: never reached here. */ int gl_renderer_frame_generation(void) { return 0; } +int gl_renderer_pgxp_render_wanted(void) { return 0; } void gl_renderer_fg_source(const uint32_t id[3], const int32_t pc[9], const int32_t h[3], const int32_t x[3], const int32_t y[3]) { (void)id; (void)pc; (void)h; (void)x; (void)y; abort(); }