Merge pull request #1 from mstan/master

runtime: tri-state fullscreen (windowed/borderless/exclusive)
This commit is contained in:
Alex Vanderveen
2026-07-21 11:46:54 -04:00
committed by GitHub
5 changed files with 77 additions and 27 deletions
+11 -2
View File
@@ -1373,7 +1373,16 @@ UserSettings load_user_settings(const fs::path& path) {
s.bios_hle = toml::find<bool>(v, "bios_hle"); s.has_bios_hle = true;
});
if (v.contains("fullscreen")) try_get([&]{
s.fullscreen = toml::find<bool>(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<int>(v, "fullscreen");
} catch (const std::exception&) {
s.fullscreen = toml::find<bool>(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<bool>(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)
+6 -4
View File
@@ -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
+2 -2
View File
@@ -389,8 +389,8 @@ button:hover { background-color: #232d3c; }
<div class="set-row"><span class="set-lbl">Turbo loads</span><span class="set-ctl">
<button class="toggle" data-event-click="toggle_turbo_loads"><span class="value" data-if="turbo_loads">On</span><span class="value" data-if="turbo_loads == false">Off</span></button>
</span></div>
<div class="set-row"><span class="set-lbl">Fullscreen on launch</span><span class="set-ctl">
<button class="toggle" data-event-click="toggle_fullscreen"><span class="value" data-if="fullscreen">On</span><span class="value" data-if="fullscreen == false">Off</span></button></span></div>
<div class="set-row"><span class="set-lbl">Fullscreen</span><span class="set-ctl">
<button class="toggle" data-event-click="cycle_fullscreen"><span class="value">{{fullscreen_label}}</span></button></span></div>
<div class="set-row" data-if="ws_eligible"><span class="set-lbl">Widescreen <span class="exp-tag">EXPERIMENTAL</span></span><span class="set-ctl">
<button class="toggle" data-event-click="toggle_widescreen"><span class="value" data-if="widescreen">On</span><span class="value" data-if="widescreen == false">Off</span></button>
<span class="ws-hint" data-if="widescreen">16:9 native field-of-view</span></span></div>
+18 -3
View File
@@ -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 {
+40 -16
View File
@@ -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