mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 10:38:46 -04:00
pgxp: measure the Z distribution instead of guessing znear
Adds a per-frame census of the Z the title actually emits (min/max/mean plus log2 buckets), read over the wire via pgxp_depth. This should have existed before any znear was chosen; two wrong values shipped because it did not. What it says for WipEout 3, on the demo savestate: min 125, max 28443, mean 5761, with the mass spread across 2^9..2^13. That immediately falsifies both earlier choices. znear=1 maps the whole scene into NDC [0.984, 0.99993] -- 0.8% of the depth range, so the test between any two objects is decided by float noise, which is exactly what the depth readout showed (ship 254-255, scene 254-255). And znear=128 does NOT "swallow the near field" as the previous commit message claimed: only 0.3% of vertices sit below 128. Sweeping znear at 32/64/100 against ship visibility gives 0.52/0.55/0.52 mean coverage versus 1.38 with depth off -- identical within noise. So the mapping is not the discriminator at all, and the reciprocal-vs-linear and znear work, while individually correct, were never the reason the ship vanished. Where it actually stands: at znear=64 the ship's median depth is 219 against 227 for the track around it, i.e. correctly NEARER. But the surrounding sample bottoms out at 0 -- fragments claiming the very near plane, which under GL_LEQUAL punch holes straight through the ship. Since the census says no vertex is below 125, those cannot come from real geometry, so the next step is validating which primitives get armed rather than tuning the projection any further. Depth stays OFF. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user