From 288926111f67f722da40401778d64812bc5f736d Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Sun, 19 Jul 2026 23:25:15 -0700 Subject: [PATCH] feat: add masked-u16 widescreen cull sites Add a shared native/interpreter helper for sltiu classifiers operating on ANDI-masked 16-bit screen X values, preserving MIPS immediate sign extension and overlay linkage. Co-authored-by: Opok --- recompiler/include/code_generator.h | 5 +++++ recompiler/src/code_generator.cpp | 15 +++++++++++++ recompiler/src/config_loader.cpp | 3 +++ recompiler/src/config_loader.h | 3 +++ recompiler/src/main_psx.cpp | 4 ++++ recompiler/tests/recompiler_patch_test.cpp | 22 +++++++++++++++++++ runtime/include/gpu.h | 3 +++ runtime/include/overlay_api.h | 2 +- .../include/overlay_dispatch_preamble.c.inc | 7 +++++- runtime/src/dirty_ram_interp.c | 4 +++- runtime/src/gpu.c | 18 +++++++++++++++ runtime/src/main.cpp | 2 ++ 12 files changed, 85 insertions(+), 3 deletions(-) diff --git a/recompiler/include/code_generator.h b/recompiler/include/code_generator.h index a5822f2e..760e8164 100644 --- a/recompiler/include/code_generator.h +++ b/recompiler/include/code_generator.h @@ -100,6 +100,11 @@ struct CodeGenConfig { // Identity at 4:3; empty by default. std::set ws_cull_negsub_sites; + // Explicit masked-16-bit screen-X window sites ([widescreen.cull] + // vxrange_sites). The configured sltiu is routed through the shared + // runtime helper so native and interpreted overlays use identical math. + std::set ws_cull_vxrange_sites; + // Screen-extent signature immediates ([widescreen.cull] screen_w_imms / // screen_h_imms) — per-game display-width-derived bounds. Defaults are the // Tomba signature; Ape Escape uses 0x181 (+ 0xF1 height). diff --git a/recompiler/src/code_generator.cpp b/recompiler/src/code_generator.cpp index d0b94675..aca376b6 100644 --- a/recompiler/src/code_generator.cpp +++ b/recompiler/src/code_generator.cpp @@ -834,6 +834,20 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr) } // overlay variant: addr is different code here — fall through to vanilla. } + if (config_.ws_cull_vxrange_sites.count(addr)) { + if (opcode == 0x0B) { // sltiu + uint32_t rs = get_rs(instr), rt = get_rt(instr); + uint16_t imm = get_imm16_u(instr); + return fmt::format("{} = psx_ws_cull_vxrange({}, {});" + " /* ws cull masked-u16 X window */{}", + reg_name(rt), reg_name(rs), (unsigned)imm, comment); + } else if (!config_.overlay_mode) { + fmt::print(stderr, "ERROR: [widescreen.cull] vxrange site 0x{:08X} is not " + "sltiu (opcode 0x{:02X})\n", addr, opcode); + std::exit(1); + } + // Overlay variant at the same address: leave nonmatching code unchanged. + } if (config_.ws_cull_range_sites.count(addr)) { if (opcode == 0x0B) { // sltiu uint32_t rs = get_rs(instr), rt = get_rt(instr); @@ -2603,6 +2617,7 @@ void CodeGenerator::emit_runtime_externs(std::ostream& ss) const { ss << "extern int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm); /* ws auto screen-x cull (gpu.c) */\n"; ss << "extern int psx_ws_cull_slti(uint32_t sx, uint32_t imm); /* ws cull signed right edge (gpu.c) */\n"; ss << "extern int psx_ws_cull_bltz(uint32_t v); /* ws cull signed left edge (gpu.c) */\n"; + ss << "extern int psx_ws_cull_vxrange(uint32_t x, uint32_t imm); /* ws masked-u16 X window */\n"; ss << "extern int psx_ws_backdrop_x(int x); /* widescreen backdrop screenX squash (gpu.c) */\n"; ss << "extern int psx_ws_bg2d_cols(int base); /* ws 2D bg tile-loop widen: col count (gpu.c) */\n"; ss << "extern int psx_ws_bg2d_startcol(int col, unsigned mask); /* ws 2D bg tile-loop widen: start tile col (gpu.c) */\n"; diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index 09143fbe..d1e33c5e 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -1048,6 +1048,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { std::vector ws_cull_screen_x_sites; std::vector ws_cull_slti_sites; std::vector ws_cull_negsub_sites; + std::vector ws_cull_vxrange_sites; int ws_cull_guard_pixels = 0; // Cull-signature immediates (screen_w_imms / screen_h_imms). Defaults are // the original Tomba signature (320-display: 0x140/0x141 + 0xE0/0xF1); a @@ -1071,6 +1072,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { load_sites("screen_x_sites", ws_cull_screen_x_sites); load_sites("slti_sites", ws_cull_slti_sites); load_sites("negsub_sites", ws_cull_negsub_sites); + load_sites("vxrange_sites", ws_cull_vxrange_sites); if (cull.contains("guard_pixels")) { ws_cull_guard_pixels = toml::find(cull, "guard_pixels"); if (ws_cull_guard_pixels < 0 || ws_cull_guard_pixels > 256) @@ -1222,6 +1224,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { /*ws_cull_screen_x_sites*/ ws_cull_screen_x_sites, /*ws_cull_slti_sites*/ ws_cull_slti_sites, /*ws_cull_negsub_sites*/ ws_cull_negsub_sites, + /*ws_cull_vxrange_sites*/ ws_cull_vxrange_sites, /*ws_cull_guard_pixels*/ ws_cull_guard_pixels, /*ws_cull_w_imms*/ ws_cull_w_imms, /*ws_cull_h_imms*/ ws_cull_h_imms, diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 3ad22be0..79ddda5b 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -516,6 +516,9 @@ struct GameConfig { // Horizontal low-edge form `subu rd,zero,rs` -> `-rs-x_margin`. // Empty by default; configured sites require regenerated native code. std::vector ws_cull_negsub_sites; + // `sltiu rt,rs,imm` where rs is an ANDI-masked 16-bit screen X. + // Widens both edges in 16-bit space; empty by default; regen required. + std::vector ws_cull_vxrange_sites; // Extra per-side actor overdraw beyond the visible widescreen edge. int ws_cull_guard_pixels = 0; diff --git a/recompiler/src/main_psx.cpp b/recompiler/src/main_psx.cpp index 780dc25b..2c060635 100644 --- a/recompiler/src/main_psx.cpp +++ b/recompiler/src/main_psx.cpp @@ -155,6 +155,7 @@ int main(int argc, char** argv) { std::set ws_cull_screen_x; // [widescreen.cull] screen_x_sites std::set ws_cull_slti; // [widescreen.cull] slti_sites std::set ws_cull_negsub; // [widescreen.cull] negsub_sites + std::set ws_cull_vxrange; // [widescreen.cull] vxrange_sites std::vector ws_cull_w_imms = { 0x140, 0x141 }; // [widescreen.cull] screen_w_imms std::vector ws_cull_h_imms = { 0xE0, 0xF1 }; // [widescreen.cull] screen_h_imms std::set ws_backdrop_x; // [widescreen.backdrop] x_sites @@ -196,6 +197,7 @@ int main(int argc, char** argv) { ws_cull_screen_x.insert(cfg.ws_cull_screen_x_sites.begin(), cfg.ws_cull_screen_x_sites.end()); ws_cull_slti.insert(cfg.ws_cull_slti_sites.begin(), cfg.ws_cull_slti_sites.end()); ws_cull_negsub.insert(cfg.ws_cull_negsub_sites.begin(), cfg.ws_cull_negsub_sites.end()); + ws_cull_vxrange.insert(cfg.ws_cull_vxrange_sites.begin(), cfg.ws_cull_vxrange_sites.end()); ws_cull_w_imms = cfg.ws_cull_w_imms; ws_cull_h_imms = cfg.ws_cull_h_imms; ws_backdrop_x.insert(cfg.ws_backdrop_x_sites.begin(), cfg.ws_backdrop_x_sites.end()); @@ -270,6 +272,7 @@ int main(int argc, char** argv) { ws_cull_screen_x.insert(wscfg.ws_cull_screen_x_sites.begin(), wscfg.ws_cull_screen_x_sites.end()); ws_cull_slti.insert(wscfg.ws_cull_slti_sites.begin(), wscfg.ws_cull_slti_sites.end()); ws_cull_negsub.insert(wscfg.ws_cull_negsub_sites.begin(), wscfg.ws_cull_negsub_sites.end()); + ws_cull_vxrange.insert(wscfg.ws_cull_vxrange_sites.begin(), wscfg.ws_cull_vxrange_sites.end()); ws_cull_w_imms = wscfg.ws_cull_w_imms; ws_cull_h_imms = wscfg.ws_cull_h_imms; ws_backdrop_x.insert(wscfg.ws_backdrop_x_sites.begin(), wscfg.ws_backdrop_x_sites.end()); @@ -927,6 +930,7 @@ int main(int argc, char** argv) { codegen_config.ws_cull_screen_x_sites = ws_cull_screen_x; codegen_config.ws_cull_slti_sites = ws_cull_slti; codegen_config.ws_cull_negsub_sites = ws_cull_negsub; + codegen_config.ws_cull_vxrange_sites = ws_cull_vxrange; codegen_config.ws_cull_w_imms = ws_cull_w_imms; codegen_config.ws_cull_h_imms = ws_cull_h_imms; codegen_config.ws_backdrop_x_sites = ws_backdrop_x; diff --git a/recompiler/tests/recompiler_patch_test.cpp b/recompiler/tests/recompiler_patch_test.cpp index c083f68e..93deaa97 100644 --- a/recompiler/tests/recompiler_patch_test.cpp +++ b/recompiler/tests/recompiler_patch_test.cpp @@ -182,6 +182,15 @@ negsub_sites = ["0x80012340"] check(negsub_config.ws_cull_negsub_sites == std::vector{0x80012340u}, "parser preserves negsub cull sites"); + + const auto vxrange = write_config(root, "vxrange", R"toml( +[widescreen.cull] +vxrange_sites = ["0x80012340"] +)toml"); + const auto vxrange_config = PSXRecompV4::load_game_config(vxrange); + check(vxrange_config.ws_cull_vxrange_sites == + std::vector{0x80012340u}, + "parser preserves masked-u16 X-window sites"); } void capture_history_config_tests(const fs::path& root) { @@ -274,6 +283,19 @@ void codegen_tests() { 0x00041021u, {}, true, negsub_config); // addu v0,zero,a0 check(overlay_mismatch.find("ws cull negsub") == std::string::npos, "overlay nonmatching negsub variant remains unchanged"); + + PSXRecomp::CodeGenConfig vxrange_config; + vxrange_config.ws_cull_vxrange_sites.insert(0x80010000u); + const std::string vxrange = generate_first_instruction( + 0x2C820140u, {}, false, vxrange_config); // sltiu v0,a0,0x140 + check(vxrange.find("cpu->gpr[2] = psx_ws_cull_vxrange(cpu->gpr[4], 320)") != + std::string::npos, + "codegen routes masked-u16 X-window sites through shared helper"); + + const std::string vxrange_overlay_mismatch = generate_first_instruction( + 0x24820140u, {}, true, vxrange_config); // addiu v0,a0,0x140 + check(vxrange_overlay_mismatch.find("ws cull masked-u16") == std::string::npos, + "overlay nonmatching masked-u16 variant remains unchanged"); } void jump_table_producer_codegen_test() { diff --git a/runtime/include/gpu.h b/runtime/include/gpu.h index 658d06c4..fd059757 100644 --- a/runtime/include/gpu.h +++ b/runtime/include/gpu.h @@ -144,9 +144,11 @@ void gpu_ws_set_cull_guard_pixels(int pixels); void gpu_ws_set_explicit_cull_sites(const uint32_t *bias, int nbias, const uint32_t *slti, int nslti); void gpu_ws_set_negsub_cull_sites(const uint32_t *sites, int nsites); +void gpu_ws_set_vxrange_cull_sites(const uint32_t *sites, int nsites); int psx_ws_is_cull_bias_site(uint32_t pc); int psx_ws_is_cull_slti_site(uint32_t pc); int psx_ws_is_cull_negsub_site(uint32_t pc); +int psx_ws_is_cull_vxrange_site(uint32_t pc); /* Scale a signed Q16 horizontal gameplay limit into the active native-wide * game field. Identity at 4:3 / menus / FMV. */ int32_t psx_ws_player_x_bound(int32_t vanilla); @@ -164,6 +166,7 @@ int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm); * widen for the paired `bltz maxSX` reject. Identity at 4:3. */ int psx_ws_cull_slti(uint32_t sx, uint32_t imm); int psx_ws_cull_bltz(uint32_t v); +int psx_ws_cull_vxrange(uint32_t x, uint32_t imm); /* True if a run of instruction words carries the screen-extent reject signature * (a width compare AND a height compare from the configured immediate sets). * Used by the interp to gate the widening to real render funnels. */ diff --git a/runtime/include/overlay_api.h b/runtime/include/overlay_api.h index 99f30ce8..63b5bb4b 100644 --- a/runtime/include/overlay_api.h +++ b/runtime/include/overlay_api.h @@ -108,7 +108,7 @@ * 4: continuation-passing (PSX_CPS, RECURSION_BUG.md §25) — overlay funcs * tail-transfer + carry an entry-switch; the loader routes continuations. * Fresh namespace so CPS DLLs never reuse a legacy (unit-model) cg3 DLL. - * 6: + configured negsub horizontal low-edge cull emit. */ + * 6: + configured negsub and masked-16-bit screen-X cull emits. */ #define PSX_OVERLAY_CODEGEN_VER 6 /* Auto codegen hash (generated by hash_codegen.cmake from the recompiler codegen diff --git a/runtime/include/overlay_dispatch_preamble.c.inc b/runtime/include/overlay_dispatch_preamble.c.inc index 8e299972..7c9ffc3f 100644 --- a/runtime/include/overlay_dispatch_preamble.c.inc +++ b/runtime/include/overlay_dispatch_preamble.c.inc @@ -205,6 +205,12 @@ int psx_ws_cull_slti(uint32_t sx, uint32_t imm) { int psx_ws_cull_bltz(uint32_t v) { return ((int32_t)v < -psx_ws_x_margin()) ? 1 : 0; } +int psx_ws_cull_vxrange(uint32_t x, uint32_t imm) { + int32_t margin = psx_ws_x_margin(); + uint32_t bound = (uint32_t)(int32_t)(int16_t)(uint16_t)imm; + return (((x + (uint32_t)margin) & 0xFFFFu) < + (bound + 2u * (uint32_t)margin)) ? 1 : 0; +} void psx_ws_sprite_tag(CPUState *cpu) { if (g_cbs.ws_sprite_tag) g_cbs.ws_sprite_tag(cpu); } @@ -234,4 +240,3 @@ void gte_precision_store_word(uint32_t addr, uint8_t reg) { if (g_cbs.gte_precision_store_word) g_cbs.gte_precision_store_word(addr, reg); } /* ----------------------------------------------------------------------- */ - diff --git a/runtime/src/dirty_ram_interp.c b/runtime/src/dirty_ram_interp.c index 50397c2c..8de5a4a6 100644 --- a/runtime/src/dirty_ram_interp.c +++ b/runtime/src/dirty_ram_interp.c @@ -1559,7 +1559,9 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) { * shared helper for a flagged render-cull site — it is byte-identical * to the vanilla compare at 4:3 (margin 0) and widens at 16:9, so the one * code path serves both aspects (no widescreen-specific caching). */ - if (psx_ws_auto_cull_on() && psx_ws_is_cull_w_imm(imm) && ws_cull_site(pc)) + if (psx_ws_is_cull_vxrange_site(pc)) + cpu->gpr[rt] = (uint32_t)psx_ws_cull_vxrange(cpu->gpr[rs], imm); + else if (psx_ws_auto_cull_on() && psx_ws_is_cull_w_imm(imm) && ws_cull_site(pc)) cpu->gpr[rt] = (uint32_t)psx_ws_cull_sltiu(cpu->gpr[rs], imm); else cpu->gpr[rt] = (cpu->gpr[rs] < (uint32_t)simm) ? 1u : 0u; diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 0b778478..725414ae 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -346,6 +346,18 @@ void gpu_ws_set_negsub_cull_sites(const uint32_t *sites, int nsites) { int psx_ws_is_cull_negsub_site(uint32_t pc) { return ws_explicit_site(ws_explicit_negsub_sites, ws_explicit_negsub_n, pc); } +static uint32_t ws_explicit_vxrange_sites[WS_EXPLICIT_CULL_SITES_MAX]; +static int ws_explicit_vxrange_n = 0; +void gpu_ws_set_vxrange_cull_sites(const uint32_t *sites, int nsites) { + if (nsites < 0) nsites = 0; + if (nsites > WS_EXPLICIT_CULL_SITES_MAX) nsites = WS_EXPLICIT_CULL_SITES_MAX; + ws_explicit_vxrange_n = nsites; + for (int i = 0; i < nsites; i++) + ws_explicit_vxrange_sites[i] = sites[i] & 0x1FFFFFFFu; +} +int psx_ws_is_cull_vxrange_site(uint32_t pc) { + return ws_explicit_site(ws_explicit_vxrange_sites, ws_explicit_vxrange_n, pc); +} int psx_ws_x_margin(void) { if (ws_margin_override >= 0) return ws_margin_override; @@ -921,6 +933,12 @@ int psx_ws_cull_slti(uint32_t sx, uint32_t imm) { int psx_ws_cull_bltz(uint32_t v) { return ((int32_t)v < -psx_ws_x_margin()) ? 1 : 0; } +int psx_ws_cull_vxrange(uint32_t x, uint32_t imm) { + int32_t margin = psx_ws_x_margin(); + uint32_t bound = (uint32_t)(int32_t)(int16_t)(uint16_t)imm; + return (((x + (uint32_t)margin) & 0xFFFFu) < + (bound + 2u * (uint32_t)margin)) ? 1 : 0; +} /* ---- Cull signature configuration ([widescreen.cull] screen_w_imms / * screen_h_imms). The width/height immediates are per-game (Tomba: 0x140/0x141 diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index c04dd0a7..4a976a8f 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -3163,6 +3163,8 @@ int main(int argc, char** argv) { gc.ws_cull_slti_sites.data(), (int)gc.ws_cull_slti_sites.size()); gpu_ws_set_negsub_cull_sites( gc.ws_cull_negsub_sites.data(), (int)gc.ws_cull_negsub_sites.size()); + gpu_ws_set_vxrange_cull_sites( + gc.ws_cull_vxrange_sites.data(), (int)gc.ws_cull_vxrange_sites.size()); gte_ws_configure_dome_sites( gc.ws_dome_call_sites.data(), (int)gc.ws_dome_call_sites.size()); /* [widescreen.cull] per-game gates + signature immediates for the