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 <novelasevilliousesp@gmail.com>
This commit is contained in:
Matthew Stanley
2026-07-19 23:25:15 -07:00
parent 8ba7f4efe6
commit 288926111f
12 changed files with 85 additions and 3 deletions
+5
View File
@@ -100,6 +100,11 @@ struct CodeGenConfig {
// Identity at 4:3; empty by default.
std::set<uint32_t> 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<uint32_t> 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).
+15
View File
@@ -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";
+3
View File
@@ -1048,6 +1048,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
std::vector<uint32_t> ws_cull_screen_x_sites;
std::vector<uint32_t> ws_cull_slti_sites;
std::vector<uint32_t> ws_cull_negsub_sites;
std::vector<uint32_t> 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<int>(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,
+3
View File
@@ -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<uint32_t> 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<uint32_t> ws_cull_vxrange_sites;
// Extra per-side actor overdraw beyond the visible widescreen edge.
int ws_cull_guard_pixels = 0;
+4
View File
@@ -155,6 +155,7 @@ int main(int argc, char** argv) {
std::set<uint32_t> ws_cull_screen_x; // [widescreen.cull] screen_x_sites
std::set<uint32_t> ws_cull_slti; // [widescreen.cull] slti_sites
std::set<uint32_t> ws_cull_negsub; // [widescreen.cull] negsub_sites
std::set<uint32_t> ws_cull_vxrange; // [widescreen.cull] vxrange_sites
std::vector<uint32_t> ws_cull_w_imms = { 0x140, 0x141 }; // [widescreen.cull] screen_w_imms
std::vector<uint32_t> ws_cull_h_imms = { 0xE0, 0xF1 }; // [widescreen.cull] screen_h_imms
std::set<uint32_t> 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;
@@ -182,6 +182,15 @@ negsub_sites = ["0x80012340"]
check(negsub_config.ws_cull_negsub_sites ==
std::vector<uint32_t>{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<uint32_t>{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() {
+3
View File
@@ -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. */
+1 -1
View File
@@ -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
@@ -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);
}
/* ----------------------------------------------------------------------- */
+3 -1
View File
@@ -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;
+18
View File
@@ -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
+2
View File
@@ -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