diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index 0597f7f8..0d213623 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -7895,7 +7895,21 @@ static void handle_pgxp_depth(int id, const char *json) const int zdbg = json_get_int(json, "zdebug", -1); if (zdbg >= 0) gl_renderer_set_zdebug(zdbg); if (on >= 0) gl_renderer_set_pgxp_depth(on); - send_fmt("{\"id\":%d,\"ok\":true,\"on\":%d}", id, gl_renderer_pgxp_depth()); + /* Report the Z distribution alongside the flag: choosing znear without it + * is guesswork, and guessing it wrong is what broke this twice. */ + extern void gl_renderer_pgxp_zstats(float *, float *, double *, + unsigned long long *, + unsigned long long *, int); + float zmn = 0.0f, zmx = 0.0f; double zmean = 0.0; + unsigned long long zn = 0, hist[20] = {0}; + gl_renderer_pgxp_zstats(&zmn, &zmx, &zmean, &zn, hist, 20); + char hb[256]; int ho = 0; + for (int i = 0; i < 20 && ho < (int)sizeof(hb) - 24; i++) + ho += snprintf(hb + ho, sizeof(hb) - (size_t)ho, "%s%llu", + i ? "," : "", hist[i]); + send_fmt("{\"id\":%d,\"ok\":true,\"on\":%d,\"zmin\":%.1f,\"zmax\":%.1f," + "\"zmean\":%.1f,\"zn\":%llu,\"zhist\":[%s]}", + id, gl_renderer_pgxp_depth(), zmn, zmx, zmean, zn, hb); } /* ws_menu_edge_fill [on=0/1]: pillarbox edge fill for 4:3-pinned presents. diff --git a/runtime/src/gpu_gl_renderer.c b/runtime/src/gpu_gl_renderer.c index 5f5bc5b4..3c99d540 100644 --- a/runtime/src/gpu_gl_renderer.c +++ b/runtime/src/gpu_gl_renderer.c @@ -427,6 +427,10 @@ static GLint s_repl_uXscale = -1, s_repl_uXcenter = -1, s_repl_uShift = -1; static int s_pgxp_depth = 0; /* configured */ static int s_depth_armed = 0; /* this batch/prim may depth-test */ static int s_zdebug = 0; /* paint depth as greyscale */ +#define ZC_BUCKETS 20 +static float s_zc_min = 1e30f, s_zc_max = 0.0f; +static double s_zc_sum = 0.0; +static uint64_t s_zc_n = 0, s_zc_hist[ZC_BUCKETS]; static int s_depth_needs_clear = 1; /* armed at Swap; see hr_begin */ /* Near plane in GTE Z units for the reciprocal depth map. * @@ -2910,6 +2914,37 @@ static void glb_set_replacement(const void *repl) { static void glb_set_depth_triangle(int enabled, float z0, float z1, float z2) { s_pz_valid = (enabled && z0 > 0.0f && z1 > 0.0f && z2 > 0.0f) ? 1 : 0; s_pz[0] = z0; s_pz[1] = z1; s_pz[2] = z2; + /* Census of the Z this title actually emits. znear must sit just under the + * scene minimum: too high and the near field clamps onto one plane and + * occludes everything behind it; too low and the reciprocal crushes the + * whole scene into the last percent of the range, where the test between + * two objects is decided by float noise. Both failures were shipped before + * this counter existed, because both were guessed at, not measured. */ + if (s_pz_valid) { + for (int i = 0; i < 3; i++) { + const float z = s_pz[i]; + if (z < s_zc_min) s_zc_min = z; + if (z > s_zc_max) s_zc_max = z; + s_zc_sum += (double)z; s_zc_n++; + unsigned b = 0; float t = z; /* log2 buckets: shape, not mean */ + while (t >= 2.0f && b < ZC_BUCKETS - 1) { t *= 0.5f; b++; } + s_zc_hist[b]++; + } + } +} + +/* Z census readout (pgxp_depth). Resets on read so a sweep measures one scene + * at a time. */ +void gl_renderer_pgxp_zstats(float *mn, float *mx, double *mean, + unsigned long long *n, unsigned long long *hist, + int hist_len) { + if (mn) *mn = (s_zc_n ? s_zc_min : 0.0f); + if (mx) *mx = s_zc_max; + if (mean) *mean = (s_zc_n ? s_zc_sum / (double)s_zc_n : 0.0); + if (n) *n = s_zc_n; + for (int i = 0; i < hist_len && i < ZC_BUCKETS; i++) hist[i] = s_zc_hist[i]; + s_zc_min = 1e30f; s_zc_max = 0.0f; s_zc_sum = 0.0; s_zc_n = 0; + for (int i = 0; i < ZC_BUCKETS; i++) s_zc_hist[i] = 0; } static void glb_set_perspective_triangle(int enabled, float q0, float q1, float q2) { s_pq_valid = (enabled && q0 > 0.0f && q1 > 0.0f && q2 > 0.0f) ? 1 : 0;