Recover verified thin terrain faces from zero integer NCLIP

This commit is contained in:
Matthew Stanley
2026-10-08 22:02:07 -07:00
parent 5478e5cfd1
commit bffd30fe81
6 changed files with 150 additions and 1 deletions
+8
View File
@@ -48,6 +48,14 @@ void gte_nclip_stats_reset(void);
* guest just read from MAC0), else the native sign. */
int32_t gte_nclip_exact_sign(int32_t native_mac0, uint32_t pc);
/* Opt-in thin-face rescue for exact-word BLEZ consumers. Unlike the separate
* horizontal-saturation policy, this only changes an architectural zero to a
* validated positive winding. Registration is host-side; the existing overlay
* ws_nclip_branch callback and its ABI remain unchanged. */
void psx_mod_set_native_wide_nclip_zero_sites(const uint32_t *addresses,
const uint32_t *expected, int count);
int gte_nclip_zero_positive(int32_t native_mac0, uint32_t pc);
/* Kill switch for the exact-site consumer (default on): off makes every exact
* site test the native sign, for same-build/same-state A/B and diagnosis. */
void gte_nclip_exact_set_enabled(int on);
+27
View File
@@ -26,6 +26,7 @@
#include "crash_trace.h"
#include "debug_server.h"
#include "cpu_state.h"
#include "gte_nclip_stats.h"
#include "event_ring.h"
#include "psx_video_timing.h"
#include "color_lut.h"
@@ -964,8 +965,25 @@ int psx_ws_masked_reject_site(uint32_t pc, uint32_t instr,
static WsMaskedRejectSite ws_nclip_sites[WS_EXPLICIT_CULL_SITES_MAX];
static uint8_t ws_nclip_previous[WS_EXPLICIT_CULL_SITES_MAX];
static int ws_nclip_site_count;
static WsMaskedRejectSite ws_nclip_zero_sites[WS_EXPLICIT_CULL_SITES_MAX];
static int ws_nclip_zero_site_count;
static uint64_t ws_nclip_rescues;
uint64_t gpu_ws_native_wide_nclip_rescues(void) { return ws_nclip_rescues; }
void psx_mod_set_native_wide_nclip_zero_sites(const uint32_t* addresses,
const uint32_t* expected, int count) {
if (count < 0 || count > WS_EXPLICIT_CULL_SITES_MAX ||
(count && (!addresses || !expected))) abort();
for (int i = 0; i < count; ++i) {
/* Only BLEZ rs,reject is proved here; positive/negative nonzero
* winding and all other branch shapes retain their native result. */
if ((addresses[i] & 3u) || expected[i] >> 26 != 6u ||
((expected[i] >> 16) & 31u) != 0u ||
((expected[i] >> 21) & 31u) == 0u) abort();
ws_nclip_zero_sites[i].address = addresses[i] & 0x1FFFFFFFu;
ws_nclip_zero_sites[i].expected = expected[i];
}
ws_nclip_zero_site_count = count;
}
void psx_mod_set_native_wide_nclip_sites(const uint32_t* addresses,
const uint32_t* expected, int count) {
if (count < 0 || count > WS_EXPLICIT_CULL_SITES_MAX) abort();
@@ -993,6 +1011,15 @@ extern int gte_nclip_native_wide_sign(int32_t mac0, int* sign);
extern int gte_nclip_native_wide_previous_sign(int32_t mac0, int* sign);
int psx_ws_nclip_branch(uint32_t pc, uint32_t instr, int32_t mac0, int vanilla) {
if (psx_ws_x_margin() <= 0 || !gpu_ws_precise_nclip_enabled()) return vanilla;
for (int i = 0; i < ws_nclip_zero_site_count; ++i) {
if (ws_nclip_zero_sites[i].address != (pc & 0x1FFFFFFFu) ||
ws_nclip_zero_sites[i].expected != instr) continue;
if (mac0 == 0 && gte_nclip_zero_positive(mac0, pc)) {
++ws_nclip_rescues;
return 0;
}
return vanilla;
}
for (int i = 0; i < ws_nclip_site_count; ++i) {
if (ws_nclip_sites[i].address != (pc & 0x1FFFFFFFu) ||
ws_nclip_sites[i].expected != instr) continue;
+37 -1
View File
@@ -1189,6 +1189,7 @@ static int32_t s_nclip_last_native = 0;
static int8_t s_nclip_last_precise_sign = 0;
static bool s_nclip_last_precise_valid = false;
static bool s_nclip_last_horizontal_saturated = false;
static bool s_nclip_last_zero_safe = false;
static uint32_t s_nclip_last_generation;
static int32_t s_nclip_previous_native;
static int8_t s_nclip_previous_precise_sign;
@@ -1197,6 +1198,7 @@ static bool s_nclip_previous_horizontal_saturated;
static uint32_t s_nclip_previous_generation;
static void gte_nclip_precision_invalidate() {
s_nclip_last_precise_valid = false;
s_nclip_last_zero_safe = false;
s_nclip_previous_precise_valid = false;
}
extern "C" void gte_nclip_precise_stats(uint64_t *hits, uint64_t *fallbacks,
@@ -1270,6 +1272,26 @@ extern "C" int gte_nclip_native_wide_sign(int32_t native_mac0, int* sign) {
*sign = s_nclip_last_precise_sign;
return 1;
}
extern "C" int gte_nclip_zero_positive(int32_t native_mac0, uint32_t pc) {
const bool usable = native_mac0 == 0 && s_nclip_last_native == 0 &&
s_nclip_exact_enabled && gpu_ws_precise_nclip_enabled() &&
s_nclip_last_precise_valid && s_nclip_last_zero_safe &&
s_nclip_last_generation == s_geom_generation &&
!s_gte_replay_sandbox && s_speculative_depth == 0 &&
!psx_overlay_shadow_diff_active();
const bool rescue = usable && s_nclip_last_precise_sign > 0;
if (!s_gte_replay_sandbox && s_speculative_depth == 0) {
if (GteNclipSiteStat *e = nclip_find(s_nclip_site, pc,
&GteNclipSiteStat::pc,
&s_nclip_site_dropped)) {
e->evals++;
if (!usable) e->fallbacks++;
else if (rescue) e->flips++;
e->last_frame = (uint32_t)s_frame_count;
}
}
return rescue;
}
/* Some guarded quad consumers save two MAC0 values before branching. Their
* first branch must use the first command's provenance, even when both native
* results are equal. This is explicitly selected by the title, never searched
@@ -1335,6 +1357,7 @@ void gte_nclip(GTEState* gte, uint32_t instr) {
s_nclip_last_native = out;
s_nclip_last_precise_valid = false;
s_nclip_last_horizontal_saturated = false;
s_nclip_last_zero_safe = false;
/* Compute an exact 16.16 determinant for configured branch consumers, but
* preserve native guest-visible MAC0 for every architectural reader. */
int32_t px0, py0, px1, py1, px2, py2;
@@ -1357,16 +1380,24 @@ void gte_nclip(GTEState* gte, uint32_t instr) {
* the matching saturation rail. Ordinary subpixel winding stays stock. */
const int raw_x[3] = {sx0, sx1, sx2}, raw_y[3] = {sy0, sy1, sy2};
const int32_t exact_x[3] = {px0, px1, px2}, exact_y[3] = {py0, py1, py2};
bool checked = true, saturated = false;
bool checked = true, saturated = false, zero_safe = true;
for (int i = 0; i < 3; ++i) {
const int x = exact_x[i] >> 16;
checked &= (exact_y[i] >> 16) == raw_y[i] && x > -4096 && x < 4096;
checked &= gte->SZ[i + 1] >= gte_h_scaled(gte) / 2 && gte->SZ[i + 1] != 0;
zero_safe &= raw_x[i] > -1024 && raw_x[i] < 1023 &&
raw_y[i] > -1024 && raw_y[i] < 1023 &&
2u * gte->SZ[i + 1] > gte_h_scaled(gte);
if (raw_x[i] == 1023 && x > 1023) saturated = true;
else if (raw_x[i] == -1024 && x < -1024) saturated = true;
else checked &= x == raw_x[i];
}
s_nclip_last_horizontal_saturated = checked && saturated;
/* A separate, explicitly registered BLEZ policy may recover a thin
* face rounded to zero. Do not extend it to clamp/near-plane cases or
* nonzero architectural winding, and retain the checked FIFO/depth
* provenance used above. */
s_nclip_last_zero_safe = out == 0 && checked && !saturated && zero_safe;
const bool disagree = (cross > 0 && out <= 0) || (cross < 0 && out >= 0);
if (disagree)
s_nclip_disagreements++;
@@ -2196,6 +2227,9 @@ static void gte_export_cpu_state(CPUState* cpu,
static void gte_run_command(PSXRecomp::GTE::GTEState* gte, uint32_t cmd) {
using namespace PSXRecomp::GTE;
/* A zero-rescue consumer must immediately follow its own NCLIP, not a
* later GTE command that happens to leave another zero in MAC0. */
s_nclip_last_zero_safe = false;
switch (cmd & 0x3Fu) {
case 0x01: gte_rtps(gte, cmd); break;
case 0x06: gte_nclip(gte, cmd); break;
@@ -2482,6 +2516,7 @@ extern "C" uint32_t gte_read_ctrl(CPUState* cpu, uint8_t reg) {
extern "C" void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val) {
if (reg >= 32) return;
PSXRecomp::GTE::s_nclip_last_zero_safe = false;
gte_cpu_canonicalize_backing(cpu);
switch (reg) {
case 7: case 16: case 17: case 18: case 19:
@@ -2548,6 +2583,7 @@ extern "C" void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val) {
extern "C" void gte_write_ctrl(CPUState* cpu, uint8_t reg, uint32_t val) {
if (reg >= 32) return;
PSXRecomp::GTE::s_nclip_last_zero_safe = false;
gte_cpu_canonicalize_backing(cpu);
switch (reg) {
case 4: case 12: case 20: case 26: case 27: case 29: case 30:
+3
View File
@@ -415,6 +415,9 @@ int gte_nclip_native_wide_sign(int32_t mac0, int* sign) {
int gte_nclip_native_wide_previous_sign(int32_t mac0, int* sign) {
(void)mac0; (void)sign; return 0;
}
int gte_nclip_zero_positive(int32_t mac0, uint32_t pc) {
(void)mac0; (void)pc; return 0;
}
static int fixture_triangle_recovered;
void gl_renderer_note_wide_triangle_recovery(int enabled) {
fixture_triangle_recovered=enabled;
@@ -66,6 +66,8 @@ extern "C" int gpu_ws_precise_nclip_enabled(void) {
return g_test_precise_nclip_enabled;
}
extern "C" void gpu_pgxp_rederive_enable(void) {}
/* This fixture has ordinary RAM only; no optional GPU DMA arena is allocated. */
extern "C" int psx_mod_gpu_dma_memory_contains(uint32_t, uint32_t) { return 0; }
static int g_test_shadow_diff = 0;
extern "C" int psx_overlay_shadow_diff_active(void) { return g_test_shadow_diff; }
extern "C" void psx_ws_note_gte_project(int) {}
@@ -773,6 +775,7 @@ int test_saturated_nclip_keeps_architectural_result() {
if (cpu.gte_data[24] != 0 || !gte_nclip_native_wide_sign(0, &sign) || sign != 1 ||
gte_nclip_native_wide_sign(1, &sign))
return fail_value("saturated winding rescue preserves native zero MAC0",0,6,0,0,cpu.gte_data[24]);
CHECK(!gte_nclip_zero_positive(0, 0x80011008u));
/* Both quad MAC0 values can saturate to the same zero. Keep each exact
* sign separately: the preceding triangle points forward, latest back. */
for (unsigned i=0;i<3;i++) {
@@ -805,6 +808,74 @@ int test_saturated_nclip_keeps_architectural_result() {
return 0;
}
int test_zero_nclip_requires_current_safe_projection() {
CPUState cpu{};
gte_precision_tracking_set(1);
g_test_precise_nclip_enabled = 1;
pgxp_set_culling(0);
gte_nclip_exact_set_enabled(1);
/* Captured terrain quad801ACC44, first three vertices. Both first vertices
* quantize to (221,45), although their precise face has positive area. */
const int16_t rt[] = {3518,0,-2096,-375,4029,-629,2061,732,3460};
for (unsigned i = 0; i < 4; ++i)
cpu.gte_ctrl[i] = (uint16_t)rt[2*i] | ((uint32_t)(uint16_t)rt[2*i+1] << 16);
cpu.gte_ctrl[4] = (uint32_t)(int32_t)rt[8];
cpu.gte_ctrl[5] = (uint32_t)-4521; cpu.gte_ctrl[6] = 2122;
cpu.gte_ctrl[7] = (uint32_t)-5132;
cpu.gte_ctrl[24] = 160u << 16; cpu.gte_ctrl[25] = 120u << 16;
cpu.gte_ctrl[26] = 350;
const int16_t v[3][3] = {{9970,-1315,6223},{9938,-1315,6172},{9970,-894,6223}};
auto project = [&](bool reverse = false) {
for (unsigned i = 0; i < 3; ++i) {
unsigned j = reverse ? 2-i : i;
cpu.gte_data[2*i] = (uint16_t)v[j][0] | ((uint32_t)(uint16_t)v[j][1] << 16);
cpu.gte_data[2*i+1] = (uint32_t)(int32_t)v[j][2];
}
gte_execute(&cpu, 0x00280030u);
gte_execute(&cpu, 6);
};
const uint32_t site = 0x8013FF14u;
gte_nclip_stats_reset();
project();
CHECK(cpu.gte_data[24] == 0);
CHECK(gte_nclip_zero_positive(0, site));
GteNclipSiteStat stat{};
CHECK(gte_nclip_site_stats(&stat, 1) == 1 && stat.pc == site &&
stat.evals == 1 && stat.flips == 1 && stat.fallbacks == 0);
CHECK(!gte_nclip_zero_positive(-1, site) && !gte_nclip_zero_positive(1, site));
CHECK(cpu.gte_data[24] == 0);
gte_nclip_exact_set_enabled(0);
CHECK(!gte_nclip_zero_positive(0, site));
gte_nclip_exact_set_enabled(1);
project(true);
CHECK(cpu.gte_data[24] == 0 && !gte_nclip_zero_positive(0, site));
project();
gte_execute(&cpu, 0x2D);
CHECK(!gte_nclip_zero_positive(0, site));
project();
gte_write_data(&cpu, 24, 0);
CHECK(!gte_nclip_zero_positive(0, site));
project();
gte_write_ctrl(&cpu, 26, 350);
CHECK(!gte_nclip_zero_positive(0, site));
project();
cpu.gte_data[17] = 10;
gte_execute(&cpu, 6);
CHECK(!gte_nclip_zero_positive(0, site));
project();
gte_precision_speculative_begin();
CHECK(!gte_nclip_zero_positive(0, site));
gte_precision_speculative_end();
gte_precision_timeline_invalidate();
CHECK(!gte_nclip_zero_positive(0, site));
project();
gte_test_seed_precise_projection(0, cpu.gte_data[12] ^ 1u, 221 << 16, 45 << 16, 4906);
gte_execute(&cpu, 6);
CHECK(!gte_nclip_zero_positive(0, site));
g_test_precise_nclip_enabled = 0;
return 0;
}
/* PGXP precise culling (docs/ENHANCEMENTS.md G1.12): NCLIP's MAC0 takes the
* exact sign only while geometry correction and culling are armed, only when
* the three SXY shadows are believed, and never in a compared pass. */
@@ -1366,6 +1437,7 @@ int main() {
if (int rc = test_precise_sxy_invalidation()) return rc;
if (int rc = test_precise_nclip_is_title_scoped()) return rc;
if (int rc = test_saturated_nclip_keeps_architectural_result()) return rc;
if (int rc = test_zero_nclip_requires_current_safe_projection()) return rc;
if (int rc = test_precision_speculative_transaction()) return rc;
if (int rc = test_pgxp_probe_does_not_count_geometry_lookup()) return rc;
if (int rc = test_preserve_projection_is_shadow_only()) return rc;
+3
View File
@@ -74,6 +74,9 @@ int mdec_recently_active(uint32_t f) { (void)f; return 0; }
void gte_execute(CPUState *cpu, uint32_t cmd) { (void)cpu; (void)cmd; }
static int precise_sign_valid, precise_sign;
static int previous_sign_valid, previous_sign;
int gte_nclip_zero_positive(int32_t mac0, uint32_t pc) {
(void)mac0; (void)pc; return 0;
}
int gte_nclip_native_wide_sign(int32_t mac0, int* sign) {
(void)mac0; *sign = precise_sign; return precise_sign_valid;
}