feat: add adaptive native widescreen

Add a resize-driven aspect option, keep native-wide culling and spawning hooks engaged across live aspect changes, and widen host GP0 draw-area pruning only for the active wide framebuffer.

Credit OpokXeno's independently discovered draw-area pruning fix documented in PR #73.

Co-authored-by: Opok <novelasevilliousesp@gmail.com>
This commit is contained in:
Matthew Stanley
2026-07-22 21:58:41 -07:00
parent 1a73e9826c
commit cece00296a
5 changed files with 212 additions and 24 deletions
+108 -13
View File
@@ -556,6 +556,12 @@ extern "C" void debug_get_fmv_config(int *auto_skip, uint32_t *total_table,
* in config_loader.h). */
static int g_video_aspect_num = 4;
static int g_video_aspect_den = 3;
/* Resize-driven widescreen. The user's fixed aspect is still used to shape the
* initial window; after the game window exists these values follow its live
* aspect, clamped to 4:3..the widest mode offered by the title. */
static bool g_ws_adaptive_view = false;
static int g_ws_adaptive_max_num = 16;
static int g_ws_adaptive_max_den = 9;
/* [widescreen] per-game hooks (see config_loader.h): anchor scratch addr for
* tagged sprite prims + HUD SPRT center-squash. Inert at 0/false. */
static uint32_t g_ws_anchor_addr = 0;
@@ -593,6 +599,53 @@ static void clamp_window_aspect(int* w, int* h, int num, int den) {
*h = width * den / num;
}
static int aspect_gcd(int a, int b) {
while (b) { int t = a % b; a = b; b = t; }
return a > 0 ? a : 1;
}
/* Follow the host window without feeding its absolute pixel size into guest
* rendering. Only the ratio matters: gpu_ws_configure derives the PSX-native
* sidecar width from it, just as the fixed 16:9/21:9 modes do. */
static void update_adaptive_widescreen() {
if (!g_ws_adaptive_view || !sdl_window) return;
int width = 0, height = 0;
SDL_GetWindowSize(sdl_window, &width, &height);
if (width <= 0 || height <= 0) return;
int num = width, den = height;
if ((int64_t)width * 3 <= (int64_t)height * 4) {
num = 4; den = 3;
} else if ((int64_t)width * g_ws_adaptive_max_den >=
(int64_t)height * g_ws_adaptive_max_num) {
num = g_ws_adaptive_max_num;
den = g_ws_adaptive_max_den;
} else {
int divisor = aspect_gcd(num, den);
num /= divisor;
den /= divisor;
}
if (num == g_video_aspect_num && den == g_video_aspect_den) return;
g_video_aspect_num = num;
g_video_aspect_den = den;
gl_renderer_set_display_aspect(num, den);
if (sdl_renderer) {
g_logical_w = 480 * num * g_video_scale / den;
SDL_RenderSetLogicalSize(sdl_renderer, g_logical_w, 480 * g_video_scale);
}
if (g_ws_engaged) {
const bool wide = num * 3 != den * 4;
const int mode = wide ? (g_ws_native_wide ? 2 : 1) : 0;
gte_set_display_aspect(mode == 1 ? num : 4,
mode == 1 ? den : 3);
gpu_ws_configure(num, den, g_ws_anchor_addr,
g_ws_hud_sprt ? 1 : 0, mode);
}
}
/* SDL GL attributes are global inputs to the next context creation. Set the
* runtime requirements immediately before every GL window, because launcher
* teardown resets them and macOS otherwise supplies a legacy 2.1 context. */
@@ -3437,12 +3490,17 @@ static void sdl_vblank_present(void) {
/* Mod hooks. Run after all normal input sampling. */
mod_call_frame_hooks();
/* Resize-driven mode updates the pending aspect during BIOS boot too, but
* the actual wide compositor remains disengaged until game entry. */
update_adaptive_widescreen();
/* Engage widescreen at game entry: BIOS boot stays authentic 4:3. */
if (!g_ws_engaged) {
extern int fntrace_is_game_started(void);
if (fntrace_is_game_started()) {
g_ws_engaged = true;
int mode = g_ws_native_wide ? 2 : 1;
const bool wide = g_video_aspect_num * 3 != g_video_aspect_den * 4;
int mode = wide ? (g_ws_native_wide ? 2 : 1) : 0;
/* Native-wide: GTE drawn un-squashed — feed it the 4:3 ratio
* (identity squash). Squash mode: feed the real wide aspect. */
gte_set_display_aspect(mode == 1 ? g_video_aspect_num : 4,
@@ -4522,6 +4580,7 @@ int main(int argc, char** argv) {
int ctrl_locked_p2_mode = PSXRecompV4::PAD_MODE_HYBRID;
bool ws_offered = true; /* game.toml [widescreen] offer; false hides the launcher toggle + clamps 4:3 */
bool ws_ultrawide_offered = false;
bool ws_adaptive_view_supported = false;
bool vulkan_offered = false; /* game.toml [video] offer_vulkan; developer opt-in for launcher visibility */
int resolved_deadzone = -1; /* <0 => keep input.ini/runtime default (12000) */
/* Localization: the effective language (game.toml default -> settings.toml ->
@@ -4695,6 +4754,7 @@ int main(int argc, char** argv) {
gc.ws_cull_h_imms.data(), (int)gc.ws_cull_h_imms.size());
ws_offered = gc.ws_offered;
ws_ultrawide_offered = gc.ws_ultrawide_offered;
ws_adaptive_view_supported = gc.ws_adaptive_view;
vulkan_offered = gc.vulkan_offered;
/* Register the [widescreen.backdrop] store PCs so the dirty-RAM
* interpreter applies the backdrop screenX squash on the interp
@@ -5000,6 +5060,7 @@ int main(int argc, char** argv) {
g_video_aspect_num = us.aspect_num;
g_video_aspect_den = us.aspect_den;
}
if (us.has_adaptive_view) g_ws_adaptive_view = us.adaptive_view;
if (us.has_spu_hq) g_audio_spu_hq = us.spu_hq;
if (us.has_bios_path && !bios_from_cli) {
settings_bios_storage = us.bios_path.string();
@@ -5058,6 +5119,9 @@ int main(int argc, char** argv) {
g_video_aspect_num = ws_offered ? 16 : 4;
g_video_aspect_den = ws_offered ? 9 : 3;
}
if (!ws_adaptive_view_supported) g_ws_adaptive_view = false;
g_ws_adaptive_max_num = ws_ultrawide_offered ? 21 : 16;
g_ws_adaptive_max_den = 9;
/* Latency knobs: env overrides win over config (for A/B measurement).
* PSX_LOW_LATENCY_INPUT=0/1 ; PSX_VSYNC=1(vsync)/0(immediate)/-1(adaptive);
@@ -5173,6 +5237,7 @@ int main(int argc, char** argv) {
seed.has_frame_interpolation_fps = true;
seed.aspect_num = g_video_aspect_num;
seed.aspect_den = g_video_aspect_den; seed.has_aspect_ratio = true;
seed.adaptive_view = g_ws_adaptive_view; seed.has_adaptive_view = true;
seed.spu_hq = g_audio_spu_hq; seed.has_spu_hq = true;
seed.skip_launcher = skip_launcher_setting; seed.has_skip_launcher = true;
if (has_netplay_player_name) {
@@ -5231,9 +5296,13 @@ int main(int argc, char** argv) {
has_netplay_player_name ? netplay_player_name.c_str() : "");
ls.pad_mode[0] = seed.p1_mode;
ls.pad_mode[1] = seed.p2_mode;
/* aspect_index: 0 = 4:3, 1 = 16:9, 2 = 21:9 (see RecompLauncherCSettings). */
ls.aspect_index = (seed.aspect_num * 9 == seed.aspect_den * 21) ? 2 :
(seed.aspect_num == 16 && seed.aspect_den == 9) ? 1 : 0;
/* Adaptive is part of the aspect vocabulary, not a second toggle.
* It follows 21:9 when ultrawide is offered, otherwise 16:9. */
const int adaptive_aspect_index = ws_ultrawide_offered ? 3 : 2;
ls.aspect_index = seed.adaptive_view ? adaptive_aspect_index :
(seed.aspect_num * 9 == seed.aspect_den * 21) ? 2 :
(seed.aspect_num == 16 && seed.aspect_den == 9) ? 1 : 0;
ls.adaptive_view = seed.adaptive_view ? 1 : 0;
/* ---- deeper PSX-style settings (capability-gated via launcher_profile
* below). Sourced 1:1 from PSXRecompV4::UserSettings (config_loader.h). */
@@ -5301,6 +5370,17 @@ int main(int argc, char** argv) {
"OpenGL (Recommended)",
"Vulkan"
};
static const char* const kAdaptiveAspectLabels[] = {
"4:3 (Native)",
"16:9 (Widescreen)",
"21:9 (Ultrawide)",
"Adaptive"
};
static const char* const kAdaptiveAspectLabelsNoUltrawide[] = {
"4:3 (Native)",
"16:9 (Widescreen)",
"Adaptive"
};
/* System identity + full PS1 settings-surface capability set (theme,
* platform label, rom_noun, pad-mode support, aspect mask base, and
* all has_* deep-settings flags) — one profile call keeps them from
@@ -5323,6 +5403,12 @@ int main(int argc, char** argv) {
gi.locked_pad_mode = p1_mode; /* force the game's declared mode (default_mode) */
gi.lock_device = ctrl_lock_device ? 1 : 0;
gi.aspect_mask = 0x1 | (ws_offered ? 0x2 : 0) | (ws_ultrawide_offered ? 0x4 : 0);
if (ws_adaptive_view_supported) {
gi.aspect_labels = ws_ultrawide_offered
? kAdaptiveAspectLabels : kAdaptiveAspectLabelsNoUltrawide;
gi.num_aspect_labels = ws_ultrawide_offered ? 4 : 3;
gi.aspect_experimental = 1;
}
gi.renderer_labels = kPsxRendererLabels;
gi.num_renderers = vulkan_offered ? 3 : 2;
/* Localization menu: shown only when the game declares languages. */
@@ -5364,14 +5450,21 @@ int main(int argc, char** argv) {
}
seed.fullscreen = ls.fullscreen; seed.has_fullscreen = true;
seed.skip_launcher = ls.skip_launcher != 0; seed.has_skip_launcher = true;
/* aspect_index round-trips 0/1/2 -> 4:3 / 16:9 / 21:9, superseding the
* legacy ls.widescreen bool (still set above for older callers). */
switch (ls.aspect_index) {
case 2: seed.aspect_num = 21; seed.aspect_den = 9; break;
case 1: seed.aspect_num = 16; seed.aspect_den = 9; break;
default: seed.aspect_num = 4; seed.aspect_den = 3; break;
/* Adaptive preserves the previous fixed aspect as the initial
* window shape. Selecting a fixed entry turns adaptation off. */
if (ws_adaptive_view_supported &&
ls.aspect_index == adaptive_aspect_index) {
seed.adaptive_view = true;
} else {
seed.adaptive_view = false;
switch (ls.aspect_index) {
case 2: seed.aspect_num = 21; seed.aspect_den = 9; break;
case 1: seed.aspect_num = 16; seed.aspect_den = 9; break;
default: seed.aspect_num = 4; seed.aspect_den = 3; break;
}
}
seed.has_aspect_ratio = true;
seed.has_adaptive_view = true;
/* has_texture_filter is on (PSX profile) so the launcher edits
* ls.texture_filter directly (0=nearest,1=bilinear); ls.linear_filter
* is the legacy fallback field for consoles without the cap and is
@@ -5473,6 +5566,7 @@ int main(int argc, char** argv) {
g_frame_interpolation_fps = seed.frame_interpolation_fps;
g_video_aspect_num = seed.aspect_num;
g_video_aspect_den = seed.aspect_den;
g_ws_adaptive_view = seed.adaptive_view;
g_audio_spu_hq = seed.spu_hq;
skip_launcher_setting = seed.skip_launcher;
if (seed.has_bios_path) {
@@ -5592,17 +5686,18 @@ session_reboot:
* this aspect; native-wide fills it with a genuinely wider frame (no
* stretch), squash mode stretches the 4:3 frame into it. */
gl_renderer_set_display_aspect(g_video_aspect_num, g_video_aspect_den);
if (g_video_aspect_num * 3 != g_video_aspect_den * 4) {
if (g_video_aspect_num * 3 != g_video_aspect_den * 4 || g_ws_adaptive_view) {
/* Hold widescreen off through the BIOS boot (authentic 4:3 logos);
* the per-frame present path engages it at game entry. */
g_ws_engaged = false;
std::fprintf(stdout,
"psxrecomp: widescreen %d:%d (%s%s%s; engages at game entry)\n",
"psxrecomp: widescreen %d:%d (%s%s%s%s; engages at game entry)\n",
g_video_aspect_num, g_video_aspect_den,
g_ws_native_wide ? "native-wide, present 1:1"
: "GTE X-squash + stretched present",
g_ws_anchor_addr ? " + sprite tags" : "",
g_ws_hud_sprt ? " + HUD squash" : "");
g_ws_hud_sprt ? " + HUD squash" : "",
g_ws_adaptive_view ? " + adaptive view" : "");
}
/* Present-time screen-colour model (verified-enhancement LUT). Default raw
* is byte-identical; PSX_SCREEN env overrides this at scanout. */