From fed7568f073559cdcdac7523c1091430a47f5cf1 Mon Sep 17 00:00:00 2001 From: tetrisgm <30949000+tetrisgm@users.noreply.github.com> Date: Thu, 27 Aug 2026 12:42:14 -0700 Subject: [PATCH] Widescreen: restore title-scoped exact NCLIP branches --- recompiler/include/code_generator.h | 4 ++++ recompiler/src/code_generator.cpp | 3 +++ recompiler/src/config_loader.cpp | 4 ++++ recompiler/src/config_loader.h | 3 +++ recompiler/src/main_psx.cpp | 4 ++++ recompiler/tests/recompiler_patch_test.cpp | 19 +++++++++++++++++++ runtime/include/cpu_state.h | 4 ++++ runtime/src/gte.cpp | 12 ++++++++++++ runtime/tests/test_gte_register_access.cpp | 4 ++++ 9 files changed, 57 insertions(+) diff --git a/recompiler/include/code_generator.h b/recompiler/include/code_generator.h index ab643737..7cf10cec 100644 --- a/recompiler/include/code_generator.h +++ b/recompiler/include/code_generator.h @@ -150,6 +150,10 @@ struct CodeGenConfig { // widescreen reveals extra world. 4:3 keeps the original branch predicate. std::set ws_cull_nclip_keep_sites; + // Exact `bltz MAC0, reject`-style NCLIP/backface sites that consume the + // validated precise sign while wide, without changing guest-visible MAC0. + std::set ws_cull_nclip_exact_sites; + // Exact branch PCs whose reject target is suppressed while widescreen // reveals extra world. 4:3 keeps the original branch predicate. std::set ws_cull_branch_keep_sites; diff --git a/recompiler/src/code_generator.cpp b/recompiler/src/code_generator.cpp index 17dbe770..a25c7cdd 100644 --- a/recompiler/src/code_generator.cpp +++ b/recompiler/src/code_generator.cpp @@ -806,6 +806,9 @@ std::string CodeGenerator::generate_branch_condition(uint32_t instr, uint32_t ad if (regimm_op == 0x00 && config_.ws_cull_nclip_keep_sites.count(addr)) return fmt::format("psx_ws_x_margin() > 0 ? 0 : ((int32_t){} < 0) /* ws nclip keep */", reg_name(rs)); + if (regimm_op == 0x00 && config_.ws_cull_nclip_exact_sites.count(addr)) + return fmt::format("psx_ws_x_margin() > 0 ? gte_nclip_precise_bltz((int32_t){}) : ((int32_t){} < 0) /* ws exact nclip */", + reg_name(rs), reg_name(rs)); return keep_branch_if_wide( fmt::format("(int32_t){} < 0", reg_name(rs))); } else { // bgez family (incl. bgezal + undefined mirrors) diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index a90feb6f..8f084314 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -73,6 +73,7 @@ uint32_t overlay_codegen_config_hash(const GameConfig& c) { h.words("cull_plane_nx", c.ws_cull_plane_nx_sites); h.words("cull_xclip_load", c.ws_cull_xclip_load_sites); h.words("cull_nclip_keep", c.ws_cull_nclip_keep_sites); + h.words("cull_nclip_exact", c.ws_cull_nclip_exact_sites); h.words("cull_branch_keep", c.ws_cull_branch_keep_sites); h.words("cull_w_imms", c.ws_cull_w_imms); h.words("cull_h_imms", c.ws_cull_h_imms); @@ -1680,6 +1681,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { std::vector ws_cull_plane_nx_sites; std::vector ws_cull_xclip_load_sites; std::vector ws_cull_nclip_keep_sites; + std::vector ws_cull_nclip_exact_sites; std::vector ws_cull_branch_keep_sites; std::vector ws_cull_keep_sites; std::vector ws_cull_widen_sites; @@ -1716,6 +1718,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { load_sites("plane_nx_sites", ws_cull_plane_nx_sites); load_sites("xclip_load_sites", ws_cull_xclip_load_sites); load_sites("nclip_keep_sites", ws_cull_nclip_keep_sites); + load_sites("nclip_exact_sites", ws_cull_nclip_exact_sites); load_sites("branch_keep_sites", ws_cull_branch_keep_sites); if (cull.contains("keep")) { std::set seen; @@ -2195,6 +2198,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { /*ws_cull_plane_nx_sites*/ ws_cull_plane_nx_sites, /*ws_cull_xclip_load_sites*/ ws_cull_xclip_load_sites, /*ws_cull_nclip_keep_sites*/ ws_cull_nclip_keep_sites, + /*ws_cull_nclip_exact_sites*/ ws_cull_nclip_exact_sites, /*ws_cull_branch_keep_sites*/ ws_cull_branch_keep_sites, /*ws_cull_keep_sites*/ ws_cull_keep_sites, /*ws_cull_widen_sites*/ ws_cull_widen_sites, diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index cad27c89..b2c03842 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -999,6 +999,9 @@ struct GameConfig { // not-taken only while widescreen reveals extra world. This is deliberately // separate from bltz_sites, whose helper adjusts screen-X edge thresholds. std::vector ws_cull_nclip_keep_sites; + // Exact NCLIP/backface branch sites that use the validated tracked sign + // only while wide. Guest MAC0 remains native and 4:3 remains bit-exact. + std::vector ws_cull_nclip_exact_sites; // Exact branch PCs whose reject path is forced not-taken only while // widescreen reveals extra world. Use only after screenshot-validated // evidence that the target is a visibility reject. diff --git a/recompiler/src/main_psx.cpp b/recompiler/src/main_psx.cpp index 6c051283..aca815c4 100644 --- a/recompiler/src/main_psx.cpp +++ b/recompiler/src/main_psx.cpp @@ -210,6 +210,7 @@ int main(int argc, char** argv) { std::set ws_cull_plane_nx; // [widescreen.cull] plane_nx_sites std::set ws_cull_xclip_load; // [widescreen.cull] xclip_load_sites std::set ws_cull_nclip_keep; // [widescreen.cull] nclip_keep_sites + std::set ws_cull_nclip_exact; // [widescreen.cull] nclip_exact_sites std::set ws_cull_branch_keep; // [widescreen.cull] branch_keep_sites std::vector ws_cull_keep; std::vector ws_cull_widen; @@ -274,6 +275,7 @@ int main(int argc, char** argv) { ws_cull_plane_nx.insert(cfg.ws_cull_plane_nx_sites.begin(), cfg.ws_cull_plane_nx_sites.end()); ws_cull_xclip_load.insert(cfg.ws_cull_xclip_load_sites.begin(), cfg.ws_cull_xclip_load_sites.end()); ws_cull_nclip_keep.insert(cfg.ws_cull_nclip_keep_sites.begin(), cfg.ws_cull_nclip_keep_sites.end()); + ws_cull_nclip_exact.insert(cfg.ws_cull_nclip_exact_sites.begin(), cfg.ws_cull_nclip_exact_sites.end()); ws_cull_branch_keep.insert(cfg.ws_cull_branch_keep_sites.begin(), cfg.ws_cull_branch_keep_sites.end()); ws_cull_keep = cfg.ws_cull_keep_sites; ws_cull_widen = cfg.ws_cull_widen_sites; @@ -370,6 +372,7 @@ int main(int argc, char** argv) { ws_cull_plane_nx.insert(wscfg.ws_cull_plane_nx_sites.begin(), wscfg.ws_cull_plane_nx_sites.end()); ws_cull_xclip_load.insert(wscfg.ws_cull_xclip_load_sites.begin(), wscfg.ws_cull_xclip_load_sites.end()); ws_cull_nclip_keep.insert(wscfg.ws_cull_nclip_keep_sites.begin(), wscfg.ws_cull_nclip_keep_sites.end()); + ws_cull_nclip_exact.insert(wscfg.ws_cull_nclip_exact_sites.begin(), wscfg.ws_cull_nclip_exact_sites.end()); ws_cull_branch_keep.insert(wscfg.ws_cull_branch_keep_sites.begin(), wscfg.ws_cull_branch_keep_sites.end()); if (ws_cull_keep.empty()) ws_cull_keep = wscfg.ws_cull_keep_sites; if (ws_cull_widen.empty()) ws_cull_widen = wscfg.ws_cull_widen_sites; @@ -1251,6 +1254,7 @@ int main(int argc, char** argv) { codegen_config.ws_cull_plane_nx_sites = ws_cull_plane_nx; codegen_config.ws_cull_xclip_load_sites = ws_cull_xclip_load; codegen_config.ws_cull_nclip_keep_sites = ws_cull_nclip_keep; + codegen_config.ws_cull_nclip_exact_sites = ws_cull_nclip_exact; codegen_config.ws_cull_branch_keep_sites = ws_cull_branch_keep; codegen_config.ws_cull_keep_sites = ws_cull_keep; codegen_config.ws_cull_widen_sites = ws_cull_widen; diff --git a/recompiler/tests/recompiler_patch_test.cpp b/recompiler/tests/recompiler_patch_test.cpp index 23510122..bb367943 100644 --- a/recompiler/tests/recompiler_patch_test.cpp +++ b/recompiler/tests/recompiler_patch_test.cpp @@ -245,6 +245,15 @@ branch_keep_sites = ["0x80012340"] std::vector{0x80012340u}, "parser preserves branch keep sites"); + const auto nclip_exact = write_config(root, "nclip-exact", R"toml( +[widescreen.cull] +nclip_exact_sites = ["0x80012340"] +)toml"); + const auto nclip_exact_config = PSXRecompV4::load_game_config(nclip_exact); + check(nclip_exact_config.ws_cull_nclip_exact_sites == + std::vector{0x80012340u}, + "parser preserves exact NCLIP branch sites"); + const auto vxrange = write_config(root, "vxrange", R"toml( [widescreen.cull] vxrange_sites = ["0x80012340"] @@ -729,6 +738,16 @@ void codegen_tests() { branch_keep.find("ws branch keep") != std::string::npos, "codegen emits guarded branch keep predicate"); + PSXRecomp::CodeGenConfig nclip_exact_config; + nclip_exact_config.ws_cull_nclip_exact_sites.insert(0x80010000u); + const std::string nclip_exact = generate_first_instruction( + 0x04400002u, {}, false, nclip_exact_config); // bltz v0,+2 + check(nclip_exact.find( + "gte_nclip_precise_bltz((int32_t)cpu->gpr[2])") != + std::string::npos && + nclip_exact.find("ws exact nclip") != std::string::npos, + "codegen emits title-scoped exact NCLIP predicate"); + PSXRecomp::CodeGenConfig plane_nx_config; plane_nx_config.ws_cull_plane_nx_sites.insert(0x80010000u); const std::string plane_nx = generate_first_instruction( diff --git a/runtime/include/cpu_state.h b/runtime/include/cpu_state.h index aa827871..b6e9a90e 100644 --- a/runtime/include/cpu_state.h +++ b/runtime/include/cpu_state.h @@ -228,6 +228,10 @@ extern void gte_geometry_correction_stats(uint32_t *lookups, uint32_t *hits, * where sub-pixel and guest-visible integer signs differ. MAC0 stays native. */ extern void gte_nclip_precise_stats(uint64_t *hits, uint64_t *fallbacks, uint64_t *disagreements); +/* Title-scoped widescreen cull consumer. Returns the exact tracked NCLIP sign + * only when it belongs to the supplied native MAC0; otherwise preserves the + * native comparison. This does not change guest-visible GTE state. */ +extern int gte_nclip_precise_bltz(int32_t native_mac0); /* pc=0 escape journal (traps.c). Every runtime site that publishes a null PC * (the "continue the native chain below" convention) or rescues one records diff --git a/runtime/src/gte.cpp b/runtime/src/gte.cpp index a9b9a287..3687b249 100644 --- a/runtime/src/gte.cpp +++ b/runtime/src/gte.cpp @@ -942,12 +942,20 @@ void gte_rtpt(GTEState* gte, uint32_t instr) { static uint64_t s_nclip_precise_hits = 0; static uint64_t s_nclip_fallbacks = 0; static uint64_t s_nclip_disagreements = 0; +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; extern "C" void gte_nclip_precise_stats(uint64_t *hits, uint64_t *fallbacks, uint64_t *disagreements) { if (hits) *hits = s_nclip_precise_hits; if (fallbacks) *fallbacks = s_nclip_fallbacks; if (disagreements) *disagreements = s_nclip_disagreements; } +extern "C" int gte_nclip_precise_bltz(int32_t native_mac0) { + if (!s_nclip_last_precise_valid || native_mac0 != s_nclip_last_native) + return native_mac0 < 0; + return s_nclip_last_precise_sign < 0; +} void gte_nclip(GTEState* gte, uint32_t instr) { gte->FLAG = 0; @@ -962,6 +970,8 @@ void gte_nclip(GTEState* gte, uint32_t instr) { (int64_t)sx2 * (sy0 - sy1); gte->check_mac0_overflow(mac0); int32_t out = static_cast(mac0); + s_nclip_last_native = out; + s_nclip_last_precise_valid = false; /* Precise NCLIP is observational only. MAC0 is guest-visible and games * branch on its sign, so a host precision enhancement must never turn a * hardware-visible face into a culled face (or vice versa). Compute the @@ -981,6 +991,8 @@ void gte_nclip(GTEState* gte, uint32_t instr) { const int64_t dx20 = (int64_t)px2 - px0; const int64_t dy20 = (int64_t)py2 - py0; const int64_t cross = dx10 * dy20 - dy10 * dx20; + s_nclip_last_precise_sign = cross < 0 ? -1 : (cross > 0 ? 1 : 0); + s_nclip_last_precise_valid = true; if ((cross > 0 && out <= 0) || (cross < 0 && out >= 0)) s_nclip_disagreements++; } else if (gpu_ws_precise_nclip_enabled() && diff --git a/runtime/tests/test_gte_register_access.cpp b/runtime/tests/test_gte_register_access.cpp index e826f688..a7ac4366 100644 --- a/runtime/tests/test_gte_register_access.cpp +++ b/runtime/tests/test_gte_register_access.cpp @@ -49,6 +49,7 @@ extern "C" int gpu_ws_precise_nclip_enabled(void) { return g_test_precise_nclip_enabled; } extern "C" void gpu_pgxp_rederive_enable(void) {} +extern "C" void gpu_pgxp_vertex_cache_invalidate(void) {} extern "C" uint32_t memory_get_ram_bytes(void) { return 2u * 1024u * 1024u; } extern "C" void psx_ws_note_gte_project(int) {} extern "C" { @@ -596,6 +597,9 @@ int test_precise_nclip_never_flips_guest_sign() { disagree1 != disagree0 + 1u) return fail_value("precise NCLIP preserves guest sign", 0, 0x06u, 0, 1u, cpu.gte_data[24]); + if (!gte_nclip_precise_bltz(1) || gte_nclip_precise_bltz(2)) + return fail_value("title-scoped precise NCLIP predicate", 0, 0x06u, + 0, 1u, 0u); return 0; }