From 6f37a0edee407e07de72c36efc0ba7b69e1ce57e Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Thu, 8 Oct 2026 12:59:36 -0700 Subject: [PATCH] Widen guarded packed screen-X predicates in native and interpreted code (cherry picked from commit b0f7b2be8e12d40824a28932d990cfd8e79aa370) --- docs/config_schema.md | 17 +++++++++ recompiler/include/code_generator.h | 1 + recompiler/src/code_generator.cpp | 21 +++++++++++ recompiler/src/config_loader.cpp | 35 +++++++++++++++++++ recompiler/src/config_loader.h | 7 ++++ recompiler/src/main_psx.cpp | 4 +++ .../ws_cull_screen_x_hi_codegen_test.cpp | 22 ++++++++++++ runtime/include/gpu.h | 4 +++ runtime/include/ws_cull_edge.h | 14 ++++++++ runtime/src/dirty_ram_interp.c | 6 ++-- runtime/src/gpu.c | 23 ++++++++++++ runtime/src/main.cpp | 9 +++++ 12 files changed, 161 insertions(+), 2 deletions(-) diff --git a/docs/config_schema.md b/docs/config_schema.md index 1abc6908..d562ebbb 100644 --- a/docs/config_schema.md +++ b/docs/config_schema.md @@ -241,6 +241,23 @@ Changing a site, guard or mask changes the overlay configuration fingerprint. ### Guarded widescreen participation comparisons +`[[widescreen.cull.packed_x]]` handles a proven mesh screen-X predicate +expressed as `SLTU rd, (SX << 16), (width << 16)`: + +```toml +[[widescreen.cull.packed_x]] +address = "0x801466A0" +expected = "0x0079C02B" +``` + +The complete instruction guards native overlays and the interpreter. While +wide, X must lie in `[-margin, width + margin)`; both sides widen together. +At 4:3 the original unsigned comparison is exact. Nonzero low-half operands, +zero width, and widths above 1024 retain the original comparison. It changes +only the predicate, preserving GPR operands, vertical, backface, depth and +ordering-table limits. Sites affect the overlay cache identity; regenerate +main/native overlays after changing them. Empty lists are inert. + Games may disable a proven object/model cull verdict in widened world views without changing true 4:3 behavior: diff --git a/recompiler/include/code_generator.h b/recompiler/include/code_generator.h index 0896727a..36fa8642 100644 --- a/recompiler/include/code_generator.h +++ b/recompiler/include/code_generator.h @@ -180,6 +180,7 @@ struct CodeGenConfig { // [[widescreen.cull.sxy]] packed-SXY AND folds (left edge widened). std::vector ws_cull_sxy_sites; std::vector ws_cull_masked_reject_sites; + std::vector ws_cull_packed_x_sites; std::vector ws_cull_scale_sites; // Exact `addi[u] rt,zero,imm` 12-bit angular half-extents. The runtime diff --git a/recompiler/src/code_generator.cpp b/recompiler/src/code_generator.cpp index 6fa5a161..ae3c1c51 100644 --- a/recompiler/src/code_generator.cpp +++ b/recompiler/src/code_generator.cpp @@ -1235,6 +1235,25 @@ std::string CodeGenerator::translate_instruction_core(uint32_t addr, uint32_t in reg_name(dst), vanilla, site.result, comment); } + for (const auto& site : config_.ws_cull_packed_x_sites) { + if ((site.address & 0x1FFFFFFFu) != (addr & 0x1FFFFFFFu)) continue; + if (instr != site.expected) { + if (config_.overlay_mode) continue; + fmt::print(stderr, "ERROR: packed_x expected 0x{:08X} at 0x{:08X}, found 0x{:08X}\n", + site.expected, addr, instr); + std::exit(1); + } + if (opcode != 0u || funct != 0x2Bu) { + fmt::print(stderr, "ERROR: packed_x site 0x{:08X} must be SLTU\n", addr); + std::exit(1); + } + return fmt::format( + "{} = psx_ws_cull_packed_x_value({}, {}, psx_ws_x_margin());" + " /* guarded packed screen-X range */{}", + reg_name(get_rd(instr)), reg_name(get_rs(instr)), + reg_name(get_rt(instr)), comment); + } + // Register-register frustum-edge compares ([[widescreen.cull.scale]]): // the configured operand is a +/-half_extent*z bound computed at runtime, // so it is scaled by the live reveal instead of rewriting an immediate. @@ -3768,6 +3787,8 @@ void CodeGenerator::emit_runtime_externs(std::ostream& ss) const { ss << "extern uint32_t psx_ws_xclip_bound(uint32_t vanilla); /* ws per-prim X reject bound load (gpu.c) */\n"; ss << "extern uint32_t psx_ws_cull_keep_result(uint32_t vanilla, uint32_t forced); /* ws guarded keep compare (gpu.c) */\n"; ss << "extern int32_t psx_ws_cull_scale(int32_t bound, int32_t half_extent); /* ws frustum-edge bound scale (gpu.c) */\n"; + if (!config_.ws_cull_packed_x_sites.empty()) + ss << "#include \"ws_cull_edge.h\"\n"; ss << "extern uint32_t psx_ws_aspect_cone_result(uint32_t site, uint32_t vanilla, uint32_t object, int32_t x, int32_t z, int32_t y); /* aspect-aware participation cone */\n"; ss << "extern uint32_t psx_ws_angle_widen(uint32_t vanilla); /* aspect-scaled 12-bit terrain-frustum half-angle */\n"; ss << "extern int psx_ws_backdrop_x(int x); /* widescreen backdrop screenX squash (gpu.c) */\n"; diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index dd69e470..63f504cd 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -64,6 +64,18 @@ uint32_t overlay_codegen_config_hash(const GameConfig& c) { h.words("sprite_tag_funcs", c.ws_sprite_tag_funcs); h.words("mod_function_entry_funcs", c.mod_function_entry_funcs); + if (!c.ws_cull_packed_x_sites.empty()) { + h.tag("cull_packed_x"); + auto sites = c.ws_cull_packed_x_sites; + std::sort(sites.begin(), sites.end(), [](const auto& a, const auto& b) { + return (a.address & 0x1FFFFFFFu) < (b.address & 0x1FFFFFFFu); + }); + h.u32((uint32_t)sites.size()); + for (const auto& site : sites) { + h.u32(site.address & 0x1FFFFFFFu); + h.u32(site.expected); + } + } if (!c.ws_cull_masked_reject_sites.empty()) { h.tag("cull_masked_reject"); auto sites = c.ws_cull_masked_reject_sites; @@ -2489,6 +2501,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { std::vector ws_cull_keep_sites; std::vector ws_cull_sxy_sites; std::vector ws_cull_masked_reject_sites; + std::vector ws_cull_packed_x_sites; std::vector ws_cull_scale_sites; std::vector ws_cull_angle_sites; WidescreenAspectConeConfig ws_aspect_cone; @@ -2557,6 +2570,27 @@ GameConfig load_game_config(const fs::path& config_path_in) { throw std::runtime_error("masked_reject supports at most 256 sites"); } } + if (cull.contains("packed_x")) { + std::set seen; + for (const auto& item : toml::find(cull, "packed_x")) { + WidescreenPackedXSite site; + site.address = parse_hex(toml::find(item, "address"), + "widescreen.cull.packed_x.address"); + site.expected = parse_hex(toml::find(item, "expected"), + "widescreen.cull.packed_x.expected"); + if ((site.address & 3u) || (site.expected >> 26) != 0u || + (site.expected & 0x7FFu) != 0x2Bu || + ((site.expected >> 21) & 31u) == 0u || + ((site.expected >> 16) & 31u) == 0u || + ((site.expected >> 11) & 31u) == 0u) + throw std::runtime_error("packed_x needs aligned SLTU rd,rs,rt with nonzero registers"); + if (!seen.insert(site.address & 0x1FFFFFFFu).second) + throw std::runtime_error("duplicate packed_x address"); + ws_cull_packed_x_sites.push_back(site); + if (ws_cull_packed_x_sites.size() > 256) + throw std::runtime_error("packed_x supports at most 256 sites"); + } + } if (cull.contains("keep")) { std::set seen; for (const auto& item : toml::find(cull, "keep")) { @@ -3097,6 +3131,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { std::move(ws_cull_clip_edge_x_load_sites); loaded.ws_cull_clip_edge_width = ws_cull_clip_edge_width; loaded.ws_cull_masked_reject_sites = std::move(ws_cull_masked_reject_sites); + loaded.ws_cull_packed_x_sites = std::move(ws_cull_packed_x_sites); loaded.draw_distance_clamp_sites = parse_draw_distance_clamps(cfg, config_path); loaded.quality_presets = parse_quality_presets(cfg, root, config_path); diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 584a08a4..f40de18e 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -1029,6 +1029,11 @@ struct QualityPreset { std::vector keys; // the [video] keys this preset sets }; +struct WidescreenPackedXSite { + uint32_t address = 0; + uint32_t expected = 0; +}; + struct GameConfig { std::filesystem::path config_path; std::filesystem::path project_root; @@ -1547,6 +1552,8 @@ struct GameConfig { std::vector quality_presets; // [[mod_immediate.sites]]: run-time-selectable immediates (ModImmediateSite). std::vector mod_immediate_sites; + // Guarded SLTU on SX<<16 against screen width<<16; both wide edges. + std::vector ws_cull_packed_x_sites; }; // Effective clip_edge_width: explicit value, else screen_w_imms[0], else 320. diff --git a/recompiler/src/main_psx.cpp b/recompiler/src/main_psx.cpp index e5a4d36c..ab22b119 100644 --- a/recompiler/src/main_psx.cpp +++ b/recompiler/src/main_psx.cpp @@ -246,6 +246,7 @@ static int psxrecomp_game_main(int argc, char** argv) { uint32_t ws_cull_clip_edge_width = 0; // [widescreen.cull] clip_edge_width std::vector ws_cull_keep; std::vector ws_cull_sxy; + std::vector ws_cull_packed_x; std::vector ws_cull_scale; std::vector ws_cull_angle; std::vector draw_distance_clamps; // [[draw_distance.clamp]] @@ -326,6 +327,7 @@ static int psxrecomp_game_main(int argc, char** argv) { ws_cull_clip_edge_width = PSXRecompV4::ws_cull_clip_edge_width(cfg); ws_cull_keep = cfg.ws_cull_keep_sites; ws_cull_sxy = cfg.ws_cull_sxy_sites; + ws_cull_packed_x = cfg.ws_cull_packed_x_sites; ws_cull_masked_reject = cfg.ws_cull_masked_reject_sites; ws_cull_scale = cfg.ws_cull_scale_sites; ws_cull_angle = cfg.ws_cull_angle_sites; @@ -445,6 +447,7 @@ static int psxrecomp_game_main(int argc, char** argv) { ws_cull_clip_edge_width = PSXRecompV4::ws_cull_clip_edge_width(wscfg); if (ws_cull_keep.empty()) ws_cull_keep = wscfg.ws_cull_keep_sites; if (ws_cull_sxy.empty()) ws_cull_sxy = wscfg.ws_cull_sxy_sites; + if (ws_cull_packed_x.empty()) ws_cull_packed_x = wscfg.ws_cull_packed_x_sites; if (ws_cull_masked_reject.empty()) ws_cull_masked_reject = wscfg.ws_cull_masked_reject_sites; if (ws_cull_scale.empty()) ws_cull_scale = wscfg.ws_cull_scale_sites; if (ws_cull_angle.empty()) ws_cull_angle = wscfg.ws_cull_angle_sites; @@ -1578,6 +1581,7 @@ static int psxrecomp_game_main(int argc, char** argv) { codegen_config.ws_cull_clip_edge_width = ws_cull_w_imms.front(); codegen_config.ws_cull_keep_sites = ws_cull_keep; codegen_config.ws_cull_sxy_sites = ws_cull_sxy; + codegen_config.ws_cull_packed_x_sites = ws_cull_packed_x; codegen_config.ws_cull_scale_sites = ws_cull_scale; codegen_config.ws_cull_angle_sites = ws_cull_angle; codegen_config.draw_distance_clamp_sites = draw_distance_clamps; diff --git a/recompiler/tests/ws_cull_screen_x_hi_codegen_test.cpp b/recompiler/tests/ws_cull_screen_x_hi_codegen_test.cpp index 5ea46ef0..90281503 100644 --- a/recompiler/tests/ws_cull_screen_x_hi_codegen_test.cpp +++ b/recompiler/tests/ws_cull_screen_x_hi_codegen_test.cpp @@ -321,6 +321,27 @@ void runtime_math() { } // namespace +static void packed_x_forms() { + PSXRecomp::CodeGenConfig config; + config.ws_cull_packed_x_sites.push_back({kBase, 0x0079C02Bu}); + const auto emitted = generate_first_instruction(0x0079C02Bu, config); + check(has(emitted, "psx_ws_cull_packed_x_value"), "packed X emits the shared predicate"); + config.overlay_mode = true; + const auto alias = generate_first_instruction(0x0079C02Au, config); + check(!has(alias, "psx_ws_cull_packed_x_value"), "different overlay opcode stays vanilla"); + for (int sx = -32768; sx <= 32767; ++sx) { + const uint32_t x = (uint32_t)sx << 16; + const uint32_t w = 320u << 16; + check(psx_ws_cull_packed_x_value(x, w, 0) == (x < w), "packed X 4:3 identity"); + check(psx_ws_cull_packed_x_value(x, w, 120) == (sx >= -120 && sx < 440), + "packed X covers exactly the expanded horizontal range"); + } + check(psx_ws_cull_packed_x_value(0xFFFF0001u, 320u << 16, 120) == 0, + "nonzero packed low half stays vanilla"); + check(psx_ws_cull_packed_x_value(0xFFFF0000u, 2048u << 16, 120) == 0, + "non-screen width stays vanilla"); +} + int main(int argc, char** argv) { g_self = argv[0]; if (argc == 3 && std::string(argv[1]) == "--exit-probe") { @@ -335,6 +356,7 @@ int main(int argc, char** argv) { main_exe_guard_and_overlay_variants(); declarations_only_when_the_key_is_used(); runtime_math(); + packed_x_forms(); if (g_exit_probe >= 0) return 3; // no call had that probe index if (failures != 0) { diff --git a/runtime/include/gpu.h b/runtime/include/gpu.h index 08550135..2ab87ff0 100644 --- a/runtime/include/gpu.h +++ b/runtime/include/gpu.h @@ -418,6 +418,10 @@ uint32_t psx_ws_cull_sxy_and(uint32_t acc, uint32_t sxy); extern int g_psx_ws_sxy_cull_n; uint32_t psx_ws_sxy_cull_vertex_reg(uint32_t pc, uint32_t instr, uint32_t (*read_word)(uint32_t phys)); +void gpu_ws_set_packed_x_sites(const uint32_t *addresses, + const uint32_t *expected, int count); +int psx_ws_cull_packed_x_site(uint32_t pc, uint32_t instr, uint32_t x, + uint32_t width, uint32_t *out); uint32_t psx_ws_angle_widen(uint32_t vanilla); int psx_ws_angle_site(uint32_t pc, uint32_t instr, uint32_t *out); /* bound * (half_extent + margin) / half_extent; identity at 4:3. */ diff --git a/runtime/include/ws_cull_edge.h b/runtime/include/ws_cull_edge.h index 0307f1ab..55b47a5d 100644 --- a/runtime/include/ws_cull_edge.h +++ b/runtime/include/ws_cull_edge.h @@ -75,6 +75,20 @@ extern "C" { #endif /* bgez predicate (1 = branch taken = keep) for a signed screen X. */ +/* SLTU on screen X and width shifted into the high half. Only proven packed + * operands widen; original comparison is exact at 4:3 and for other layouts. */ +static inline uint32_t psx_ws_cull_packed_x_value(uint32_t x, uint32_t width, + int32_t m) +{ + if (m <= 0 || ((x | width) & 0xFFFFu) || width == 0u || + (width >> 16) > 1024u) + return x < width ? 1u : 0u; + int32_t sx = (int16_t)(x >> 16); + int64_t offset = (int64_t)sx + m; + int64_t span = (int64_t)(width >> 16) + 2LL * m; + return offset >= 0 && offset < span ? 1u : 0u; +} + static inline int psx_ws_cull_bgez_value(int32_t v, int32_t m) { return v >= -(m > 0 ? m : 0); diff --git a/runtime/src/dirty_ram_interp.c b/runtime/src/dirty_ram_interp.c index 22179754..7936b7b4 100644 --- a/runtime/src/dirty_ram_interp.c +++ b/runtime/src/dirty_ram_interp.c @@ -2024,8 +2024,10 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn, { uint32_t vanilla = (cpu->gpr[rs] < cpu->gpr[rt]) ? 1u : 0u; uint32_t kept = vanilla; - /* Same precedence as the code generator: scale and keep sites first. */ - if (!psx_ws_cull_scale_site(pc, insn, cpu->gpr[rs], cpu->gpr[rt], &kept) && + /* Same precedence as the code generator: packed-X, scale and keep + * sites first. */ + if (!psx_ws_cull_packed_x_site(pc, insn, cpu->gpr[rs], cpu->gpr[rt], &kept) && + !psx_ws_cull_scale_site(pc, insn, cpu->gpr[rs], cpu->gpr[rt], &kept) && !psx_ws_cull_keep_site(pc, insn, vanilla, &kept) && psx_ws_is_cull_screen_x_site(pc)) kept = (uint32_t)psx_ws_cull_sltu_band(cpu->gpr[rs], cpu->gpr[rt]); diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 2b1deaac..8c4e51f5 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -1057,6 +1057,29 @@ typedef struct { uint32_t expected; uint32_t result; } WsCullKeepSite; +typedef struct { uint32_t address, expected; } WsPackedXSite; +static WsPackedXSite ws_packed_x_sites[WS_EXPLICIT_CULL_SITES_MAX]; +static int ws_packed_x_n; +void gpu_ws_set_packed_x_sites(const uint32_t *addresses, + const uint32_t *expected, int count) { + if (count < 0) count = 0; + if (count > WS_EXPLICIT_CULL_SITES_MAX) count = WS_EXPLICIT_CULL_SITES_MAX; + ws_packed_x_n = count; + for (int i = 0; i < count; ++i) { + ws_packed_x_sites[i].address = addresses[i] & 0x1FFFFFFFu; + ws_packed_x_sites[i].expected = expected[i]; + } +} +int psx_ws_cull_packed_x_site(uint32_t pc, uint32_t instr, uint32_t x, + uint32_t width, uint32_t *out) { + for (int i = 0; i < ws_packed_x_n; ++i) { + if (ws_packed_x_sites[i].address != (pc & 0x1FFFFFFFu) || + ws_packed_x_sites[i].expected != instr) continue; + if (out) *out = psx_ws_cull_packed_x_value(x, width, psx_ws_x_margin()); + return 1; + } + return 0; +} static WsCullKeepSite ws_cull_keep_sites[WS_EXPLICIT_CULL_SITES_MAX]; static int ws_cull_keep_n = 0; void gpu_ws_set_cull_keep_sites(const uint32_t *addresses, diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 8b00f22d..648e927a 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -18383,6 +18383,15 @@ int main(int argc, char** argv) { vertex_regs.data(), consumer_addresses.data(), consumer_expected.data(), (int)vertices.size()); } + { + std::vector addresses, expected; + for (const auto& site : gc.ws_cull_packed_x_sites) { + addresses.push_back(site.address); + expected.push_back(site.expected); + } + gpu_ws_set_packed_x_sites(addresses.data(), expected.data(), + (int)addresses.size()); + } { std::vector addresses, expected, masks; for (const auto& site : gc.ws_cull_masked_reject_sites) {