diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index aea76097..764ea77c 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -1373,7 +1373,16 @@ UserSettings load_user_settings(const fs::path& path) { s.bios_hle = toml::find(v, "bios_hle"); s.has_bios_hle = true; }); if (v.contains("fullscreen")) try_get([&]{ - s.fullscreen = toml::find(v, "fullscreen"); s.has_fullscreen = true; + // Tri-state (0 off / 1 borderless / 2 exclusive). Back-compat: a + // settings.toml written before the tri-state migration stores this + // as a bool (true meant borderless desktop fullscreen). + try { + s.fullscreen = toml::find(v, "fullscreen"); + } catch (const std::exception&) { + s.fullscreen = toml::find(v, "fullscreen") ? 1 : 0; + } + if (s.fullscreen < 0 || s.fullscreen > 2) s.fullscreen = 0; + s.has_fullscreen = true; }); if (v.contains("low_latency_input")) try_get([&]{ s.low_latency_input = toml::find(v, "low_latency_input"); @@ -1541,7 +1550,7 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) { if (s.has_bios_hle) f << "bios_hle = " << (s.bios_hle ? "true" : "false") << "\n"; if (s.has_fullscreen) - f << "fullscreen = " << (s.fullscreen ? "true" : "false") << "\n"; + f << "fullscreen = " << s.fullscreen << "\n"; if (s.has_low_latency_input) f << "low_latency_input = " << (s.low_latency_input ? "true" : "false") << "\n"; if (s.has_vsync) diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 87e7274c..06f19f25 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -761,10 +761,12 @@ struct UserSettings { bool has_fast_boot = false; bool fast_boot = false; // HLE BIOS tier toggle (see RuntimeConfig::bios_hle). Overrides game.toml. bool has_bios_hle = false; bool bios_hle = false; - // [video] fullscreen: launch the game window in desktop fullscreen (the - // launcher's "Fullscreen on launch" toggle; the in-game F11 / Alt+Enter - // hotkey still toggles it live). false => windowed (default). - bool has_fullscreen = false; bool fullscreen = false; + // [video] fullscreen: universal tri-state (matches every recomp-ui console's + // launcher control). 0 = windowed (default), 1 = borderless desktop + // fullscreen, 2 = exclusive fullscreen (real display-mode change). The + // in-game Alt+Enter / Cmd+Ctrl+F hotkey toggles live between windowed and + // whichever of these is configured. + bool has_fullscreen = false; int fullscreen = 0; // Low-latency present knobs. low_latency_input re-samples the pad after the // wall-clock pacer (just before present) so the next CPU frame reads fresh // input instead of input ~one frame stale (the dominant input->photon cost diff --git a/runtime/launcher/assets/launcher.rml b/runtime/launcher/assets/launcher.rml index b3f50245..6d818afd 100644 --- a/runtime/launcher/assets/launcher.rml +++ b/runtime/launcher/assets/launcher.rml @@ -389,8 +389,8 @@ button:hover { background-color: #232d3c; }
Turbo loads
-
Fullscreen on launch -
+
Fullscreen +
Widescreen EXPERIMENTAL 16:9 native field-of-view
diff --git a/runtime/launcher/launcher.cpp b/runtime/launcher/launcher.cpp index 35021f2a..09643c4a 100644 --- a/runtime/launcher/launcher.cpp +++ b/runtime/launcher/launcher.cpp @@ -112,7 +112,7 @@ struct LauncherModel { bool ws_eligible = true; // toggle shown only when renderer==software (native-wide is SW-only) bool ultrawide = false; // separate EXPERIMENTAL 21:9 choice bool uw_eligible = false; // per-game offer_ultrawide gate - bool fullscreen = false; // launch the game window in desktop fullscreen + int fullscreen = 0; // tri-state: 0 windowed, 1 borderless (desktop), 2 exclusive bool frame_interpolation = false; int frame_interpolation_fps = 0; // 0 = current display refresh bool opengl_renderer = false; @@ -133,6 +133,7 @@ struct LauncherModel { Rml::String aspect_label; Rml::String winsize_label; Rml::String interpolation_fps_label; + Rml::String fullscreen_label; // Disc verification (recomputed whenever disc_path changes). Rml::String disc_file; // file name only, e.g. "tomba.cue" @@ -392,6 +393,17 @@ const char* crt_name(int v) { } } +// Tri-state fullscreen: 0 off / 1 borderless (desktop) fullscreen / 2 exclusive +// fullscreen (real display-mode change) — same semantics as recomp-ui's +// universal Fullscreen control on every other console. +const char* fullscreen_name(int v) { + switch (v) { + case 1: return "Borderless"; + case 2: return "Exclusive"; + default: return "Off"; + } +} + // Offered display aspects. 4:3 is the native presentation every game ships // with; wider aspects enable the runtime widescreen hack (GTE X-squash + // stretched present — see [video] aspect_ratio in config_loader.h). @@ -452,6 +464,7 @@ void refresh_labels(LauncherModel& m) { m.aspect_label = aspect_name(m.aspect_index); m.winsize_label = winsize_label_for(m.window_width, m.aspect_index); m.interpolation_fps_label = interp_fps_label(m.frame_interpolation_fps); + m.fullscreen_label = fullscreen_name(m.fullscreen); m.opengl_renderer = (m.renderer == 1); m.interpolation_target_visible = m.opengl_renderer && m.frame_interpolation; m.widescreen = (m.aspect_index == 1); // 16:9 == experimental native-wide @@ -806,6 +819,7 @@ Result run(SDL_Window* window, void* gl_context, c.Bind("auto_skip_fmv", &m.auto_skip_fmv); c.Bind("turbo_loads", &m.turbo_loads); c.Bind("fullscreen", &m.fullscreen); + c.Bind("fullscreen_label", &m.fullscreen_label); c.Bind("frame_interpolation", &m.frame_interpolation); c.Bind("interpolation_fps_label", &m.interpolation_fps_label); c.Bind("opengl_renderer", &m.opengl_renderer); @@ -1029,10 +1043,11 @@ Result run(SDL_Window* window, void* gl_context, m.turbo_loads = !m.turbo_loads; handle.DirtyVariable("turbo_loads"); }); - c.BindEventCallback("toggle_fullscreen", + c.BindEventCallback("cycle_fullscreen", [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { - m.fullscreen = !m.fullscreen; + m.fullscreen = (m.fullscreen + 1) % 3; refresh_labels(m); handle.DirtyVariable("fullscreen"); + handle.DirtyVariable("fullscreen_label"); }); c.BindEventCallback("toggle_frame_interpolation", [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index c204a5cd..d5394c07 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -404,7 +404,8 @@ static int g_video_scale = 1; /* internal-resolution SSAA factor * static bool g_video_aa = true; /* linear present filtering */ static int g_video_texfilter = 0; /* 0=nearest, 1=bilinear */ static int g_video_renderer = PSXRecompV4::DEFAULT_VIDEO_RENDERER; -static int g_fullscreen = 0; /* launch the game window in desktop fullscreen */ +static int g_fullscreen = 0; /* tri-state: 0 windowed, 1 borderless (desktop) + * fullscreen, 2 exclusive fullscreen */ static int g_video_screen = 0; /* 0=raw,1=crt,2=composite,3=trinitron */ static int g_video_win_w = 1280; /* window width (height follows aspect) */ static bool g_audio_spu_hq = false; /* SPU float-shadow (env overrides) */ @@ -439,6 +440,15 @@ static int g_frame_interpolation = 0; static int g_frame_interpolation_fps = 0; static double g_host_refresh_hz = 0.0; +/* Map the configured tri-state fullscreen mode (g_fullscreen) to the SDL + * window-fullscreen flag: used both to open the window in that mode and to + * pick the hotkey's fullscreen target. */ +static Uint32 psx_fullscreen_flag_for_mode(int mode) { + if (mode == 2) return SDL_WINDOW_FULLSCREEN; /* exclusive */ + if (mode == 1) return SDL_WINDOW_FULLSCREEN_DESKTOP; /* borderless */ + return 0; /* windowed */ +} + /* FMV auto-skip detection hooks (cdrom.c / mdec.c). */ extern "C" int cdrom_xa_stream_active(void); extern "C" uint32_t mdec_get_decode_count(void); @@ -2677,15 +2687,28 @@ static void sdl_vblank_present(void) { std::fprintf(stdout, "[DEBUG] Forzando reinserción de CD...\n"); debug_force_cd_reinsert(); } - /* Fullscreen toggle: Alt+Enter or Cmd/Ctrl+F. FULLSCREEN_DESKTOP - * keeps the desktop resolution; the renderer's logical size - * letterboxes the 640x480 image. */ + /* Fullscreen toggle: Alt+Enter or Cmd/Ctrl+F. Toggles between + * windowed and the CONFIGURED tri-state mode (g_fullscreen: 1 + * borderless desktop fullscreen keeping the desktop resolution + * and letterboxing the image, or 2 exclusive fullscreen — a + * real display-mode change). SDL_WINDOW_FULLSCREEN's bit is + * set in both SDL_WINDOW_FULLSCREEN and + * SDL_WINDOW_FULLSCREEN_DESKTOP, so testing just that bit + * detects "currently fullscreen, either mode". */ else if ((ev.key.keysym.sym == SDLK_RETURN && (mod & KMOD_ALT)) || (ev.key.keysym.sym == SDLK_f && (mod & (KMOD_GUI | KMOD_CTRL)))) { Uint32 is_fs = SDL_GetWindowFlags(sdl_window) & - SDL_WINDOW_FULLSCREEN_DESKTOP; - SDL_SetWindowFullscreen(sdl_window, - is_fs ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP); + SDL_WINDOW_FULLSCREEN; + if (is_fs) { + SDL_SetWindowFullscreen(sdl_window, 0); + } else { + /* If the configured mode is "off", the hotkey still + * needs a mode to switch INTO — default to borderless, + * matching the historical (pre-tri-state) behaviour. */ + Uint32 target = psx_fullscreen_flag_for_mode(g_fullscreen); + if (target == 0) target = SDL_WINDOW_FULLSCREEN_DESKTOP; + SDL_SetWindowFullscreen(sdl_window, target); + } } } } @@ -3796,7 +3819,7 @@ int main(int argc, char** argv) { if (us.has_turbo_loads) g_turbo_loads_enabled = us.turbo_loads ? 1 : 0; if (us.has_fast_boot) fast_boot = us.fast_boot; if (us.has_bios_hle) bios_hle = us.bios_hle; - if (us.has_fullscreen) g_fullscreen = us.fullscreen ? 1 : 0; + if (us.has_fullscreen) g_fullscreen = us.fullscreen; if (us.has_aspect_ratio) { g_video_aspect_num = us.aspect_num; g_video_aspect_den = us.aspect_den; @@ -3965,7 +3988,7 @@ int main(int argc, char** argv) { seed.turbo_loads = (g_turbo_loads_enabled != 0); seed.has_turbo_loads = true; seed.fast_boot = fast_boot; seed.has_fast_boot = true; seed.bios_hle = bios_hle; seed.has_bios_hle = true; - seed.fullscreen = (g_fullscreen != 0); seed.has_fullscreen = true; + seed.fullscreen = g_fullscreen; seed.has_fullscreen = true; seed.frame_interpolation = (g_frame_interpolation != 0); seed.has_frame_interpolation = true; seed.frame_interpolation_fps = g_frame_interpolation_fps; @@ -4008,7 +4031,7 @@ int main(int argc, char** argv) { RecompLauncherCSettings ls{}; ls.output_method = 2; /* OpenGL */ ls.window_scale = std::max(1, std::min(4, g_video_win_w / 320)); - ls.fullscreen = seed.fullscreen ? 1 : 0; + ls.fullscreen = seed.fullscreen; ls.ignore_aspect = 0; ls.linear_filter = (seed.texture_filter != 0) ? 1 : 0; ls.widescreen = (seed.aspect_num == 16 && seed.aspect_den == 9) ? 1 : 0; @@ -4152,7 +4175,7 @@ int main(int argc, char** argv) { seed.disc_path = rui_out_disc; seed.has_disc_path = true; } - seed.fullscreen = ls.fullscreen != 0; seed.has_fullscreen = true; + 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). */ @@ -4265,7 +4288,7 @@ int main(int argc, char** argv) { g_turbo_loads_enabled = seed.turbo_loads ? 1 : 0; fast_boot = seed.fast_boot; bios_hle = seed.bios_hle; - g_fullscreen = seed.fullscreen ? 1 : 0; + g_fullscreen = seed.fullscreen; g_frame_interpolation = seed.frame_interpolation ? 1 : 0; g_frame_interpolation_fps = seed.frame_interpolation_fps; g_video_aspect_num = seed.aspect_num; @@ -4573,10 +4596,11 @@ int main(int argc, char** argv) { win_flags |= SDL_WINDOW_OPENGL; } if (g_video_renderer == 2) win_flags |= SDL_WINDOW_VULKAN; - /* Fullscreen on launch (launcher "Fullscreen on launch" toggle). DESKTOP - * fullscreen keeps the desktop resolution and letterboxes the image, matching - * the in-game F11 / Alt+Enter hotkey behaviour. */ - if (g_fullscreen) win_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + /* Fullscreen on launch (launcher's tri-state Fullscreen control): 1 = + * borderless desktop fullscreen (keeps the desktop resolution, letterboxes + * the image), 2 = exclusive fullscreen (real display-mode change), 0 = + * windowed. Matches the in-game Alt+Enter / Cmd+Ctrl+F hotkey behaviour. */ + win_flags |= psx_fullscreen_flag_for_mode(g_fullscreen); /* Open at the user-chosen window size (default 1280 wide) instead of the * old hardcoded 640x480, so the game doesn't boot into a tiny window. The * height follows the configured display aspect (4:3 native, wider for the