mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Recover verified thin terrain faces from zero integer NCLIP
(cherry picked from commit bffd30fe81)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
@@ -1007,8 +1008,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();
|
||||
@@ -1036,6 +1054,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
@@ -1358,6 +1358,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;
|
||||
@@ -1366,6 +1367,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,
|
||||
@@ -1439,6 +1441,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
|
||||
@@ -1504,6 +1526,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;
|
||||
@@ -1526,16 +1549,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++;
|
||||
@@ -2382,6 +2413,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;
|
||||
@@ -2670,6 +2704,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:
|
||||
@@ -2736,6 +2771,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:
|
||||
|
||||
@@ -460,6 +460,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;
|
||||
|
||||
@@ -68,6 +68,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) {}
|
||||
@@ -775,6 +777,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++) {
|
||||
@@ -807,6 +810,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. */
|
||||
@@ -1368,6 +1439,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;
|
||||
|
||||
@@ -81,6 +81,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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user