From 244474a1477bdfdba74817db7e4e7236ef14989d Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Fri, 10 Jul 2026 15:51:07 -0700 Subject: [PATCH] widescreen: scope curved backdrop projection sites --- recompiler/src/config_loader.cpp | 19 +++++++++++++++++++ recompiler/src/config_loader.h | 9 +++++++++ runtime/include/cpu_state.h | 1 + runtime/src/gte.cpp | 26 +++++++++++++++++++++++--- runtime/src/main.cpp | 5 +++++ 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index b22021e4..a2f47032 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -696,6 +696,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { bool ws_hud_sprt_squash = false; bool ws_full_2d = false; bool ws_gte_game_mode = false; + bool ws_native_wide = true; bool ws_nw_hud_corners = false; uint32_t ws_nw_left_hud_packet_lo = 0; uint32_t ws_nw_left_hud_packet_hi = 0; @@ -726,6 +727,8 @@ GameConfig load_game_config(const fs::path& config_path_in) { ws_full_2d = toml::find(ws, "full_2d"); if (ws.contains("gte_game_mode")) ws_gte_game_mode = toml::find(ws, "gte_game_mode"); + if (ws.contains("native_wide")) + ws_native_wide = toml::find(ws, "native_wide"); if (ws.contains("nw_hud_corners")) ws_nw_hud_corners = toml::find(ws, "nw_hud_corners"); const bool has_nw_left_hud_lo = ws.contains("nw_left_hud_packet_lo"); @@ -864,6 +867,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { // (x_sites) + far-backdrop GTE un-squash (unsquash_funcs). std::vector ws_backdrop_x_sites; std::vector ws_backdrop_unsquash_funcs; + std::vector ws_dome_call_sites; if (cfg.contains("widescreen")) { const toml::value& ws = toml::find(cfg, "widescreen"); if (ws.contains("backdrop")) { @@ -879,6 +883,19 @@ GameConfig load_game_config(const fs::path& config_path_in) { } } + // Optional [widescreen.dome] block: exact projection calls belonging to + // finite curved background meshes that retain authored 4:3 coverage. + if (cfg.contains("widescreen")) { + const toml::value& ws = toml::find(cfg, "widescreen"); + if (ws.contains("dome")) { + const toml::value& dome = toml::find(ws, "dome"); + if (dome.contains("call_sites")) + for (const auto& a : toml::find>(dome, "call_sites")) + ws_dome_call_sites.push_back( + parse_hex(a, "widescreen.dome.call_sites")); + } + } + return GameConfig{ /*config_path*/ config_path, /*project_root*/ root, @@ -912,10 +929,12 @@ GameConfig load_game_config(const fs::path& config_path_in) { /*ws_cull_h_imms*/ ws_cull_h_imms, /*ws_backdrop_x_sites*/ ws_backdrop_x_sites, /*ws_backdrop_unsquash_funcs*/ ws_backdrop_unsquash_funcs, + /*ws_dome_call_sites*/ ws_dome_call_sites, /*ws_auto_screen_x_cull*/ ws_auto_screen_x_cull, /*ws_auto_backdrop_preload*/ ws_auto_backdrop_preload, /*ws_full_2d*/ ws_full_2d, /*ws_gte_game_mode*/ ws_gte_game_mode, + /*ws_native_wide*/ ws_native_wide, /*ws_nw_hud_corners*/ ws_nw_hud_corners, /*ws_nw_left_hud_packet_lo*/ ws_nw_left_hud_packet_lo, /*ws_nw_left_hud_packet_hi*/ ws_nw_left_hud_packet_hi, diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 658183b1..182255bd 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -456,6 +456,10 @@ struct GameConfig { // frame instead of leaving edge void (8C). Main-EXE addresses; regen-class. std::vector ws_backdrop_unsquash_funcs; + // [widescreen.dome] call_sites: exact guest JAL addresses whose GTE + // projections belong to a finite curved backdrop mesh authored for 4:3. + std::vector ws_dome_call_sites; + // [widescreen.cull] auto_screen_x — automatic horizontal-FOV cull widening. // GTE-projected render funnels reject a primitive when ALL its vertices fall // off the 4:3 frame: a per-vertex `sltiu vN, SX, 0x140` (right edge) paired @@ -498,6 +502,11 @@ struct GameConfig { // pillarbox 4:3. Runtime-only — no regen required. Off by default. bool ws_gte_game_mode = false; + // [widescreen] native_wide — select the newer wide render-target path. + // Defaults on for compatibility. Titles can keep the original GTE-squash + // + stretched-present path when native-wide is not regression-free. + bool ws_native_wide = true; + // [widescreen] nw_hud_corners — in native-wide, push outer-third screen- // space HUD sprites out to the true wide-frame corners (they otherwise sit // inset by the reveal offset). Runtime-only — no regen. Off by default. diff --git a/runtime/include/cpu_state.h b/runtime/include/cpu_state.h index 844e9dc5..095ba8a5 100644 --- a/runtime/include/cpu_state.h +++ b/runtime/include/cpu_state.h @@ -184,6 +184,7 @@ extern void gte_execute(CPUState* cpu, uint32_t cmd); * wide aspect shows a wider field of view. */ extern void gte_set_display_aspect(int num, int den); extern void gte_ws_set_suppress(int on); /* 8C: un-squash far backdrop draws */ +extern void gte_ws_configure_dome_sites(const uint32_t* sites, int count); extern uint32_t gte_read_data(CPUState* cpu, uint8_t reg); extern uint32_t gte_read_ctrl(CPUState* cpu, uint8_t reg); extern void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val); diff --git a/runtime/src/gte.cpp b/runtime/src/gte.cpp index 9f9782f7..c2fd2253 100644 --- a/runtime/src/gte.cpp +++ b/runtime/src/gte.cpp @@ -243,6 +243,23 @@ extern "C" void gte_ws_set_dome_expand(int on, int aspect_num, int aspect_den) { // dome-expand bracket (the clean alternative to the scene-dependent depth gate). extern "C" { extern uint32_t g_debug_current_func_addr; } static uint32_t s_gte_caller_ra = 0; /* guest ra at gte_execute = return into the GAME fn that issued the projection (not the libgte leaf) */ +#define WS_DOME_CALL_SITES_MAX 32 +static uint32_t s_ws_dome_call_sites[WS_DOME_CALL_SITES_MAX]; +static int s_ws_dome_call_sites_n = 0; +extern "C" void gte_ws_configure_dome_sites(const uint32_t* sites, int count) { + if (count < 0) count = 0; + if (count > WS_DOME_CALL_SITES_MAX) count = WS_DOME_CALL_SITES_MAX; + s_ws_dome_call_sites_n = count; + for (int i = 0; i < count; i++) + s_ws_dome_call_sites[i] = sites[i] & 0x1FFFFFFFu; +} +static inline bool ws_dome_call_matches(void) { + // JAL writes pc+8 to RA; config records the call instruction itself. + const uint32_t call_pc = (s_gte_caller_ra - 8u) & 0x1FFFFFFFu; + for (int i = 0; i < s_ws_dome_call_sites_n; i++) + if (s_ws_dome_call_sites[i] == call_pc) return true; + return false; +} #define DOME_PROBE_SLOTS 48 static struct { uint32_t func; uint32_t count; int32_t max_sz; } s_dome_probe[DOME_PROBE_SLOTS]; static int s_dome_probe_on = 0; @@ -605,6 +622,11 @@ void gte_rtps_internal(GTEState* gte, int16_t* V, bool setMac0) { // full-2D screen), so content and present stay locked. int64_t xterm = (int64_t)gte->IR1 * h_div_sz; bool do_squash = (s_ws_xnum != s_ws_xden) && !gpu_ws_present_native_43(); + const bool dome_call = ws_dome_call_matches(); + // Curved backdrops are authored to cover the original 4:3 projection. + // In classic widescreen, leave that projection intact and let the normal + // final-frame stretch cover the wide output instead of shrinking it first. + if (dome_call) do_squash = false; if (do_squash && s_ws_suppress) { /* Depth-gated: un-squash only FAR geometry (the backdrop); keep near * props squashed so they stay aligned. Record SZ stats for tuning. */ @@ -616,9 +638,7 @@ void gte_rtps_internal(GTEState* gte, int16_t* V, bool setMac0) { } if (do_squash) xterm = xterm * s_ws_xnum / s_ws_xden; - // Native-wide sky-dome expand: no squash active (identity), dome mode on, - // this frame is stretched, and the vertex is far (sky). Scale X outward from - // the projection centre so the finite dome mesh reaches the wider frame. + // Native-wide dome expansion remains a diagnostic-only depth probe. else if (s_ws_dome_on && s_ws_dome_num != s_ws_dome_den && !gpu_ws_present_native_43()) { int32_t sz = gte->SZ[3]; diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 89749270..d2df88aa 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -2394,6 +2394,9 @@ int main(int argc, char** argv) { gc.ws_bg2d_packet_cap); /* [widescreen] gte_game_mode — 3D-title gameplay detector (Ape). */ gpu_ws_set_gte_game_mode(gc.ws_gte_game_mode ? 1 : 0); + /* Keep titles with known native-wide regressions on the original + * projection-squash + stretched-present widescreen path. */ + g_ws_native_wide = gc.ws_native_wide ? 1 : 0; /* [widescreen] nw_hud_corners — push HUD to the true wide corners. */ gpu_ws_set_nw_hud_corners(gc.ws_nw_hud_corners ? 1 : 0); /* Targeted left-HUD packet range — avoids shifting 2D scenery. */ @@ -2408,6 +2411,8 @@ int main(int argc, char** argv) { gpu_ws_set_explicit_cull_sites( gc.ws_cull_bias_sites.data(), (int)gc.ws_cull_bias_sites.size(), gc.ws_cull_slti_sites.data(), (int)gc.ws_cull_slti_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 * pattern-scanned interp/sljit widen hooks. A title that never * opted in must never have its live code scanned and rewritten. */