From 339007ecf1022a22a6041e8f6aedd8f8bf419d5b Mon Sep 17 00:00:00 2001 From: Zap0xfce2 <902946+zap0xfce2@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:27:30 +0200 Subject: [PATCH] feat(widescreen): screen_x_sites band form for sltu with the width in a register Renderers that test a 16-bit screen X with an unsigned compare against a width kept in a register (often a loaded clip bound) reject every negative X, so widening the bound only reveals the right band. A screen_x site on sltu now routes through psx_ws_cull_sltu_band: -4m <= (int16)SX < W while widened, identity at 4:3. Four margins keep large near polygons whose corners all lie past one margin; an unbounded left side overflowed a renderer's fixed primitive lists. Covered by ws_cull_screen_x_hi_codegen_test; documented in WIDESCREEN.md. --- docs/WIDESCREEN.md | 17 ++++++++++++++++- recompiler/src/code_generator.cpp | 10 +++++++++- .../tests/ws_cull_screen_x_hi_codegen_test.cpp | 18 ++++++++++++++++++ runtime/include/gpu.h | 2 ++ .../include/overlay_dispatch_preamble.c.inc | 7 +++++++ runtime/src/gpu.c | 17 +++++++++++++++++ 6 files changed, 69 insertions(+), 2 deletions(-) diff --git a/docs/WIDESCREEN.md b/docs/WIDESCREEN.md index d7884a1e..fb36c53d 100644 --- a/docs/WIDESCREEN.md +++ b/docs/WIDESCREEN.md @@ -299,13 +299,28 @@ are non-empty, so titles that do not use them keep their caches. `screen_x_sites` is the explicit form of the `auto_screen_x` compare, for a title whose width is not in `screen_w_imms` or whose function has no paired -height compare. It takes three instructions: +height compare. It takes four instructions: | Instruction at the site | While widened | |---|---| | `sltiu rt, SX, W` | `rt = -m <= SX < W + m` | | `lui rt, W` (a screen edge kept as `W << 16`) | `rt = (W + m) << 16`; a negative `W` moves left instead | | `bltz x, outside` where `x` is `SX << 16` | taken while `SX < -m` | +| `sltu rd, SX, W` with `W` in a register | `rd = -4m <= (int16)SX < W` | + +The `sltu` form covers a renderer that compares a 16-bit screen X +(`andi SX, sxy, 0xffff`) against a width it keeps in a register, typically a +clip bound loaded from RAM. The unsigned compare rejects every negative X, so +widening the bound (`clip_edge_x_load_sites`) only reveals the right band; the +band helper `psx_ws_cull_sltu_band` also accepts negative X. Its left reach +is four margins: these tests check single vertices, so a large near polygon +whose corners all lie past one margin would still drop, while an unbounded +left side lets every off-screen polygon through and overflows a renderer's +fixed primitive lists (Kula World's world-10 intro stalled for 5 s). Pair it with the +load site so `W` already carries the right margin. A bound derived from the +loaded one (`x + r < W + 2r` bounding-sphere tests) works the same way. +Kula World (SCES-01000) is the first user: its object, level-quad and sky +tests use this shape, and forcing them true instead overflowed its renderer. The last two cover a renderer that keeps a screen X in the high half of a register (`sll x, sx, 16`), tests the sign of differences against a `lui`-held diff --git a/recompiler/src/code_generator.cpp b/recompiler/src/code_generator.cpp index 9e280940..8776a854 100644 --- a/recompiler/src/code_generator.cpp +++ b/recompiler/src/code_generator.cpp @@ -1445,6 +1445,13 @@ std::string CodeGenerator::translate_instruction_core(uint32_t addr, uint32_t in return fmt::format("{} = psx_ws_cull_sltiu({}, {});" " /* ws explicit screen-x cull */{}", reg_name(rt), reg_name(rs), imm, comment); + } else if (opcode == 0x00 && (instr & 0x3Fu) == 0x2Bu && get_rd(instr) != 0) { + // sltu rd,SX,W: the width held in a register (often a loaded + // clip bound); SX is a 16-bit screen X. Accept the left band too. + return fmt::format("{} = psx_ws_cull_sltu_band({}, {});" + " /* ws explicit screen-x band */{}", + reg_name(get_rd(instr)), reg_name(get_rs(instr)), + reg_name(get_rt(instr)), comment); } else if (opcode == 0x0F && get_rt(instr) != 0) { // lui rt,W: a screen edge kept in the high half of a register // (compared with `SX << 16`). Move it out by the live margin. @@ -1455,7 +1462,7 @@ std::string CodeGenerator::translate_instruction_core(uint32_t addr, uint32_t in reg_name(rt), imm, comment); } else if (!config_.overlay_mode) { fmt::print(stderr, "ERROR: [widescreen.cull] screen_x site 0x{:08X} " - "is not sltiu, lui or bltz (0x{:08X})\n", addr, instr); + "is not sltiu, sltu, lui or bltz (0x{:08X})\n", addr, instr); std::exit(1); } // Overlay variant: the configured main-EXE PC holds different code. @@ -3626,6 +3633,7 @@ void CodeGenerator::emit_runtime_externs(std::ostream& ss) const { if (!config_.ws_cull_screen_x_sites.empty()) { ss << "extern uint32_t psx_ws_cull_lui_hi(uint32_t imm); /* ws screen-x edge in the high half (gpu.c) */\n"; ss << "extern int psx_ws_cull_bltz_hi(uint32_t v); /* ws cull left edge, X in the high half (gpu.c) */\n"; + ss << "extern int psx_ws_cull_sltu_band(uint32_t sx, uint32_t bound); /* ws screen-x band, W in a register (gpu.c) */\n"; } ss << "extern int psx_ws_cull_vxrange(uint32_t x, uint32_t imm); /* ws masked-u16 X window */\n"; ss << "extern int32_t psx_ws_depth_bound(int32_t imm); /* ws aspect-scaled far bound */\n"; 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 494360df..bae857d2 100644 --- a/recompiler/tests/ws_cull_screen_x_hi_codegen_test.cpp +++ b/recompiler/tests/ws_cull_screen_x_hi_codegen_test.cpp @@ -110,6 +110,7 @@ bool exits_with_failure(F&& body) { } constexpr uint32_t kSltiuV0 = 0x2C620200u; // sltiu v0, v1, 0x200 +constexpr uint32_t kSltuV0 = 0x0064102Bu; // sltu v0, v1, a0 (width in a register) constexpr uint32_t kLuiS6 = 0x3C160200u; // lui s6, 0x200 constexpr uint32_t kLuiZero = 0x3C000200u; // lui zero, 0x200 constexpr uint32_t kBltzT0 = 0x05000002u; // bltz t0, +2 @@ -165,6 +166,22 @@ void listed_sites_emit_the_helpers() { "a screen_x site on sltiu still routes through psx_ws_cull_sltiu"); } +// sltu with the width in a register: the unsigned compare rejects negative X, +// the band helper also accepts the left reveal. +void listed_sltu_emits_the_band_helper() { + PSXRecomp::CodeGenConfig config{}; + config.emit_comments = true; + check(!has(generate_first_instruction(kSltuV0, config), "psx_ws_cull_sltu_band"), + "an unlisted sltu stays vanilla"); + config.ws_cull_screen_x_sites.insert(kBase); + check(has(generate_first_instruction(kSltuV0, config), + "cpu->gpr[2] = psx_ws_cull_sltu_band(cpu->gpr[3], cpu->gpr[4]); " + "/* ws explicit screen-x band */"), + "a screen_x site on sltu routes through psx_ws_cull_sltu_band"); + check(!exits_with_failure([&] { generate_first_instruction(kSltuV0, config); }), + "a screen_x site on sltu generates"); +} + // bltz_sites is the low-half form and keeps its meaning when an address is in // both lists. void bltz_sites_wins_over_screen_x_sites() { @@ -283,6 +300,7 @@ int main(int argc, char** argv) { } unlisted_instructions_stay_vanilla(); listed_sites_emit_the_helpers(); + listed_sltu_emits_the_band_helper(); bltz_sites_wins_over_screen_x_sites(); main_exe_guard_and_overlay_variants(); declarations_only_when_the_key_is_used(); diff --git a/runtime/include/gpu.h b/runtime/include/gpu.h index 4ce9c929..e31f4d76 100644 --- a/runtime/include/gpu.h +++ b/runtime/include/gpu.h @@ -426,6 +426,8 @@ int32_t psx_ws_screen_x_bound(int32_t vanilla); * `sltiu rt, sx, 0x140/0x141` through this one helper so every overlay execution * path widens identically. Returns the sltiu verdict (1 = keep). 0 at 4:3. */ int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm); +/* screen_x_sites on `sltu rd, SX, W` (W in a register): -4m <= SX < W. */ +int psx_ws_cull_sltu_band(uint32_t sx, uint32_t bound); /* Signed-funnel variants (min/max + center±halfwidth idioms — see * ws_cull_detect.h): right-edge widen for `slti v, minSX, W` and left-edge * widen for the paired `bltz maxSX` reject. Identity at 4:3. */ diff --git a/runtime/include/overlay_dispatch_preamble.c.inc b/runtime/include/overlay_dispatch_preamble.c.inc index bc7d1c64..4a059370 100644 --- a/runtime/include/overlay_dispatch_preamble.c.inc +++ b/runtime/include/overlay_dispatch_preamble.c.inc @@ -203,6 +203,13 @@ int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm) { return ((uint32_t)((int32_t)(int16_t)(uint16_t)sx + m) < (uint32_t)((int32_t)imm + 2 * m)) ? 1 : 0; } +/* MUST stay byte-for-byte identical to gpu.c psx_ws_cull_sltu_band. */ +int psx_ws_cull_sltu_band(uint32_t sx, uint32_t bound) { + int m = psx_ws_x_margin(); + if (m <= 0) return sx < bound ? 1 : 0; + int32_t x = (int32_t)(int16_t)(uint16_t)sx; + return (x >= -4 * m && x < (int32_t)bound) ? 1 : 0; /* WS_SLTU_BAND_LEFT_REACH */ +} /* Signed-funnel variants (min/max + center±halfwidth idioms). Same contract: * self-contained, identity at margin 0, MUST stay byte-for-byte identical to * the gpu.c implementations. */ diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 17890e93..2fe783e0 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -2003,6 +2003,23 @@ int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm) { < (uint32_t)((int32_t)imm + 2 * m)) ? 1 : 0; } +/* [widescreen.cull] screen_x_sites on `sltu rd, SX, W` with W in a register: + * SX is a 16-bit screen X (typically `andi SX, sxy, 0xffff`), W the width the + * guest loaded (pair with clip_edge_x_load_sites so W already carries the right + * margin). The unsigned compare rejects negative X; while widened the verdict is + * -k*m <= (int16)SX < W, so the left band passes too. Identity at margin 0. + * These tests check single vertices, so a large near polygon whose corners all + * lie past the reveal would still drop: k = 4 margins keeps them (Kula World's + * level quads), while an unbounded left side lets every off-screen polygon + * through and overflows renderers with fixed primitive lists. */ +#define WS_SLTU_BAND_LEFT_REACH 4 +int psx_ws_cull_sltu_band(uint32_t sx, uint32_t bound) { + int m = psx_ws_x_margin(); + if (m <= 0) return sx < bound ? 1 : 0; + int32_t x = (int32_t)(int16_t)(uint16_t)sx; + return (x >= -WS_SLTU_BAND_LEFT_REACH * m && x < (int32_t)bound) ? 1 : 0; +} + /* Signed right-edge widen for the min/max funnel idiom (`slti v, minSX, W`): * the paired LEFT edge is a separate bltz (psx_ws_cull_bltz below), so this * bound moves out by ONE margin only. Operand is an already sign-extended /