From cece00296aed6aa749b8a4e3a8f1491964283f37 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Wed, 22 Jul 2026 21:58:41 -0700 Subject: [PATCH] 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 --- WIDESCREEN.md | 18 +++++ recompiler/src/config_loader.cpp | 10 +++ recompiler/src/config_loader.h | 7 ++ runtime/src/gpu.c | 80 +++++++++++++++++--- runtime/src/main.cpp | 121 +++++++++++++++++++++++++++---- 5 files changed, 212 insertions(+), 24 deletions(-) diff --git a/WIDESCREEN.md b/WIDESCREEN.md index 6594947c..3f4efcc8 100644 --- a/WIDESCREEN.md +++ b/WIDESCREEN.md @@ -70,8 +70,26 @@ clear_reveal = true # clear synthetic native-wide side margin nw_left_hud_packet_lo = "0x000E3400" # optional targeted left-HUD packet range nw_left_hud_packet_hi = "0x000E4100" # (half-open); avoids shifting 2D scenery. offer_ultrawide = true # separate experimental 21:9 launcher row. +adaptive_view = true # expose live resize-driven aspect mode. ``` +When `adaptive_view` is enabled by the game, the launcher's **Aspect ratio** +selector gains an **Adaptive** entry. The previously selected fixed aspect +still determines the initial window shape. Once the game window exists, +resizing it continuously +updates the GTE/native-wide projection, cull margins, wide render target, and +present aspect. The live ratio is clamped to 4:3 on the narrow side and to the +widest mode the game offers (`16:9`, or `21:9` with `offer_ultrawide`). BIOS, +FMV, menus, and other title-classified 2D frames retain their existing 4:3 +pillarbox policy. The user choice persists as `[video] adaptive_view` in +`settings.toml`. + +The native-wide draw-area early-out correction was independently identified +and contributed by **OpokXeno** in +[PR #73](https://github.com/mstan/psxrecomp/pull/73). The landed variant keeps +that mirror-space correction while restricting it to active framebuffer +targets, so offscreen texture targets retain the original host-side pruning. + Tomba's values are Ghidra-evidenced: `0x8005E08C` is the shared per-prim helper all ~23 character render functions (RTPS cluster `0x800459E0`–`0x8004FB54`) call; the RTPS preamble stores the anchor SXY to scratchpad `0x1F800070`. diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index 94ed4bcf..1b8d93c4 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -889,6 +889,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { std::vector ws_signed_x_bound_sites; bool ws_offered = true; bool vulkan_offered = false; + bool ws_adaptive_view = false; bool ws_ultrawide_offered = false; if (cfg.contains("video")) { const toml::value& video = toml::find(cfg, "video"); @@ -1060,6 +1061,8 @@ GameConfig load_game_config(const fs::path& config_path_in) { ws_offered = toml::find(ws, "offer"); if (ws.contains("offer_ultrawide")) ws_ultrawide_offered = toml::find(ws, "offer_ultrawide"); + if (ws.contains("adaptive_view")) + ws_adaptive_view = toml::find(ws, "adaptive_view"); } // Optional [widescreen.cull] block — world-space draw-cull widening. @@ -1279,6 +1282,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { /*ws_signed_x_bound_sites*/ ws_signed_x_bound_sites, /*ws_offered*/ ws_offered, /*vulkan_offered*/ vulkan_offered, + /*ws_adaptive_view*/ ws_adaptive_view, /*ws_ultrawide_offered*/ ws_ultrawide_offered, /*ws_bg2d_count_site*/ ws_bg2d_count_site, /*ws_bg2d_startcol_site*/ ws_bg2d_startcol_site, @@ -1438,6 +1442,10 @@ UserSettings load_user_settings(const fs::path& path) { s.aspect_num = n; s.aspect_den = d; s.has_aspect_ratio = true; } }); + if (v.contains("adaptive_view")) try_get([&]{ + s.adaptive_view = toml::find(v, "adaptive_view"); + s.has_adaptive_view = true; + }); } if (doc.contains("audio")) { const toml::value& a = toml::find(doc, "audio"); @@ -1600,6 +1608,8 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) { f << "frame_interpolation_fps = " << s.frame_interpolation_fps << "\n"; if (s.has_aspect_ratio) f << "aspect_ratio = \"" << s.aspect_num << ":" << s.aspect_den << "\"\n"; + if (s.has_adaptive_view) + f << "adaptive_view = " << (s.adaptive_view ? "true" : "false") << "\n"; f << "\n[audio]\n"; if (s.has_spu_hq) f << "spu_hq = " << (s.spu_hq ? "true" : "false") << "\n"; diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index c1922e32..3a9eee8b 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -700,6 +700,12 @@ struct GameConfig { bool ws_offered = true; bool vulkan_offered = false; + // [widescreen] adaptive_view — let the user opt into a live, resize-driven + // aspect instead of selecting only fixed 4:3/16:9/21:9 modes. The fixed + // aspect remains the initial window shape; the live view is clamped to the + // widest aspect this title offers. + bool ws_adaptive_view = false; + // [widescreen] offer_ultrawide — expose a separate experimental 21:9 // launcher choice for titles that have explicitly tested it. Default off; // ordinary widescreen offer remains the independent 16:9 choice. @@ -810,6 +816,7 @@ struct UserSettings { bool has_netplay_lobby_url = false; std::string netplay_lobby_url; bool has_aspect_ratio = false; int aspect_num = 4; // display aspect W:H int aspect_den = 3; // (4:3 = native) + bool has_adaptive_view = false; bool adaptive_view = false; // [audio] bool has_spu_hq = false; bool spu_hq = false; // [bios] / [disc] / [memcard] diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 4398753d..a9bf2e53 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -82,6 +82,7 @@ static int ws_hud_sprt = 0; /* edge-anchor untagged HUD SPRTs * * in native-wide mode. */ static int ws_mode = 0; static int ws_cfg_num = 4, ws_cfg_den = 3; +static void ws_nw_sync_target(void); #define WS_TAG_BUCKETS 4096 /* power of two */ #define WS_TAG_PROBES 8 @@ -279,13 +280,22 @@ static int ws_active(void) { return ws_configured() && !gpu_ws_present_native_43 int ws_native_wide_active(void) { return ws_mode == 2 && !gpu_ws_present_native_43(); } +/* Cull/spawn setup often runs while a scene is loading, before the first GTE + * frame can classify it as gameplay. Keep that pre-render setup aware of the + * configured native-wide viewport; presentation itself remains gated by + * ws_native_wide_active(). */ +static int ws_native_wide_configured(void) { + return ws_mode == 2 && ws_cfg_num * 3 > ws_cfg_den * 4; +} +static int ws_nw_configured_offset(void) { + if (!ws_native_wide_configured()) return 0; + int numr = 3 * ws_cfg_num - 4 * ws_cfg_den; + int w = (int)ws_disp_w(); + return (w * numr + 4 * ws_cfg_den) / (8 * ws_cfg_den); +} static int ws_nw_offset(void) { if (!ws_native_wide_active()) return 0; - int numr = 3 * ws_cfg_num - 4 * ws_cfg_den; /* > 0 for aspects wider than 4:3 */ - if (numr <= 0) return 0; - int w = (int)ws_disp_w(); - int v = (w * numr + 4 * ws_cfg_den) / (8 * ws_cfg_den); /* round-to-nearest */ - return v; + return ws_nw_configured_offset(); } int ws_nw_extra(void) { return 2 * ws_nw_offset(); } @@ -446,7 +456,12 @@ int psx_ws_x_margin(void) { * that previously fell outside the 4:3 cull window; the wide compositor then * rasterizes it into the revealed margins. Same recompiler emit sites as the * squash path ([widescreen.cull]); 0 at 4:3 so the cull stays byte-identical. */ - if (ws_native_wide_active()) return ws_nw_offset() + ws_cull_guard_pixels; + /* Unlike rendering/presentation, do not wait for game-mode detection here. + * Tomba 2 builds its terrain-cell and actor spawn lists during scene load; + * returning zero until the first 3D frame permanently bakes a 4:3 frustum + * into those lists. */ + if (ws_native_wide_configured()) + return ws_nw_configured_offset() + ws_cull_guard_pixels; if (!ws_active()) return 0; return (160 * (ws_xden - ws_xnum) + ws_xnum / 2) / ws_xnum; } @@ -1261,6 +1276,10 @@ void gpu_ws_configure(int aspect_num, int aspect_den, } ws_anchor_addr = sprite_anchor_addr; ws_hud_sprt = hud_sprt_squash; + /* Adaptive view can change mode/extent without the guest reissuing E3/E4 + * immediately. Keep the active mirror target and its scissor in lockstep + * with the new live aspect instead of waiting for another draw-env packet. */ + ws_nw_sync_target(); } /* Called from generated code at the entry of each [widescreen] @@ -2349,9 +2368,44 @@ static void raster_pixel(int32_t x, int32_t y, uint16_t color) { * clips those for free; our GL path was building two triangles per clipped * prim (gpu_share ~0.9, host FPS ~5–10). Skip the host rasterizer when the * post-offset bbox cannot touch the draw area. Side effects that must still - * run (texpage latch, oversize reject) happen before these checks. */ + * run (texpage latch, oversize reject) happen before these checks. + * + * Native-wide is intentionally different in X: the mirror renderer translates + * canonical framebuffer coordinates by the live reveal offset and scissors to + * the FULL wide surface, not GP0(E3/E4)'s 4:3 X range. Commit 31015ce originally + * compared every primitive only with the guest draw area here, rejecting the + * margin geometry before the mirror renderer could see it. Match the mirror's + * exact X extent while its framebuffer target is active; Y remains the guest + * draw area because native-wide does not extend vertically. At 4:3, in FMV/ + * menus, in squash mode, and for offscreen texture targets, margin is zero and + * this remains the original fast reject byte-for-byte. + * + * OpokXeno independently identified the same host-side regression on Xenogears + * and contributed the original generalized fix in psxrecomp PR #73: + * https://github.com/mstan/psxrecomp/pull/73 + * Keep that credit with this guarded framebuffer-target variant. */ +static inline int32_t draw_area_wide_x_margin(void) { + if (!ws_native_wide_active() || !ws_is_fb_base(draw_area_left)) return 0; + return (int32_t)ws_nw_offset(); +} + +static inline void draw_area_host_x_bounds(int32_t *left, int32_t *right) { + int32_t margin = draw_area_wide_x_margin(); + if (margin > 0) { + /* ws_nw_sync_target uses draw_area_left as the framebuffer base and + * configures a surface ws_disp_w()+2*margin pixels wide. */ + *left = (int32_t)draw_area_left - margin; + *right = (int32_t)draw_area_left + (int32_t)ws_disp_w() + margin - 1; + } else { + *left = (int32_t)draw_area_left; + *right = (int32_t)draw_area_right; + } +} + static inline int draw_area_out_point(int32_t x, int32_t y) { - return x < (int32_t)draw_area_left || x > (int32_t)draw_area_right + int32_t left, right; + draw_area_host_x_bounds(&left, &right); + return x < left || x > right || y < (int32_t)draw_area_top || y > (int32_t)draw_area_bottom; } @@ -2363,14 +2417,18 @@ static inline int draw_area_out_bbox(const int32_t *vx, const int32_t *vy, int n if (vy[i] < miny) miny = vy[i]; if (vy[i] > maxy) maxy = vy[i]; } - return maxx < (int32_t)draw_area_left || minx > (int32_t)draw_area_right + int32_t left, right; + draw_area_host_x_bounds(&left, &right); + return maxx < left || minx > right || maxy < (int32_t)draw_area_top || miny > (int32_t)draw_area_bottom; } static inline int draw_area_out_rect(int32_t x, int32_t y, int w, int h) { if (w <= 0 || h <= 0) return 1; - return (x + w - 1) < (int32_t)draw_area_left - || x > (int32_t)draw_area_right + int32_t left, right; + draw_area_host_x_bounds(&left, &right); + return (x + w - 1) < left + || x > right || (y + h - 1) < (int32_t)draw_area_top || y > (int32_t)draw_area_bottom; } diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 6778f30d..f1f74c81 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -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. */