mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Fix display cadence refresh on monitor moves
This commit is contained in:
@@ -518,6 +518,9 @@ if(BUILD_TESTING)
|
||||
add_test(NAME pal_cadence_host_refresh_test
|
||||
COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_pal_cadence_host_refresh.py)
|
||||
add_test(NAME mixed_refresh_display_cadence_test
|
||||
COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_mixed_refresh_display_cadence.py)
|
||||
add_test(NAME cdrom_lid_integration_test
|
||||
COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_cdrom_lid_integration.py)
|
||||
|
||||
+70
-26
@@ -1255,7 +1255,10 @@ static_assert((int)PSX_MOD_CONTROLLER_DIGITAL ==
|
||||
(int)PSXRecompV4::PAD_MODE_DIGITAL);
|
||||
static double g_host_refresh_hz = 0.0;
|
||||
static constexpr double PSX_FRAME_PERIOD_MS = 1000.0 / 59.94;
|
||||
static double g_guest_frame_period_ms = PSX_FRAME_PERIOD_MS;
|
||||
static double g_frame_period_ms = PSX_FRAME_PERIOD_MS;
|
||||
static int g_host_refresh_display_idx = -2;
|
||||
static uint64_t g_host_refresh_last_probe_ms = 0;
|
||||
static bool g_mod_native_vblank_rate = false;
|
||||
static uint32_t g_mod_native_vblank_fps = 0;
|
||||
/* Activation-time request. -1 means no enabled mod owns load acceleration. */
|
||||
@@ -1268,6 +1271,7 @@ static int present_vsync_owns_cadence(void);
|
||||
static int present_effective_swap_interval(void);
|
||||
static int present_should_wall_pace(void);
|
||||
static void apply_present_cadence(void);
|
||||
static void refresh_host_display_cadence(int force_log, int force_probe);
|
||||
|
||||
/* 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
|
||||
@@ -1369,9 +1373,10 @@ extern "C" int psx_mod_set_native_vblank_rate(
|
||||
}
|
||||
g_mod_native_vblank_rate = true;
|
||||
g_mod_native_vblank_fps = frames_per_second;
|
||||
g_frame_period_ms = frames_per_second
|
||||
g_guest_frame_period_ms = frames_per_second
|
||||
? 1000.0 / (double)frames_per_second
|
||||
: 0.0;
|
||||
g_frame_period_ms = g_guest_frame_period_ms;
|
||||
/*
|
||||
* Above the physical panel rate (and in uncapped mode), swap-interval
|
||||
* blocking would silently replace the requested guest cadence with the
|
||||
@@ -2809,12 +2814,66 @@ static void apply_netplay_local_viewport_aspect(bool netplay_enabled) {
|
||||
* guest resumes), which shows up as MotK FMV ~30–40 FPS in netplay vs ~50+
|
||||
* offline. Force immediate swaps for the session; restore on soft-exit. */
|
||||
static int host_refresh_matches_guest_cadence(void) {
|
||||
if (g_host_refresh_hz <= 0.0 || g_frame_period_ms <= 0.0)
|
||||
if (g_host_refresh_hz <= 0.0 || g_guest_frame_period_ms <= 0.0)
|
||||
return 0;
|
||||
const double guest_hz = 1000.0 / g_frame_period_ms;
|
||||
const double guest_hz = 1000.0 / g_guest_frame_period_ms;
|
||||
return std::fabs(g_host_refresh_hz - guest_hz) <= guest_hz * 0.02;
|
||||
}
|
||||
|
||||
static void refresh_host_display_cadence(int force_log, int force_probe) {
|
||||
#ifndef PSX_SDL_NO_RENDER
|
||||
if (!sdl_window)
|
||||
return;
|
||||
|
||||
const uint64_t now_ms = SDL_GetTicks64();
|
||||
const int disp_idx = SDL_GetWindowDisplayIndex(sdl_window);
|
||||
if (!force_probe &&
|
||||
disp_idx == g_host_refresh_display_idx &&
|
||||
g_host_refresh_last_probe_ms != 0 &&
|
||||
now_ms >= g_host_refresh_last_probe_ms &&
|
||||
now_ms - g_host_refresh_last_probe_ms < 1000ull) {
|
||||
return;
|
||||
}
|
||||
g_host_refresh_last_probe_ms = now_ms ? now_ms : 1ull;
|
||||
|
||||
double host_hz = 0.0;
|
||||
if (disp_idx >= 0) {
|
||||
SDL_DisplayMode dm;
|
||||
if (SDL_GetCurrentDisplayMode(disp_idx, &dm) == 0 &&
|
||||
dm.refresh_rate > 0) {
|
||||
host_hz = (double)dm.refresh_rate;
|
||||
}
|
||||
}
|
||||
|
||||
const int display_changed = (disp_idx != g_host_refresh_display_idx);
|
||||
const int refresh_changed =
|
||||
std::fabs(host_hz - g_host_refresh_hz) > 0.05;
|
||||
if (!force_log && !display_changed && !refresh_changed)
|
||||
return;
|
||||
|
||||
g_host_refresh_display_idx = disp_idx;
|
||||
g_host_refresh_hz = host_hz;
|
||||
g_frame_period_ms = g_guest_frame_period_ms;
|
||||
if (host_refresh_matches_guest_cadence()) {
|
||||
g_frame_period_ms = 1000.0 / host_hz;
|
||||
std::printf("psxrecomp: sync-to-host-refresh: pacing to %.1f Hz panel "
|
||||
"(%.4f ms/frame)\n", host_hz, g_frame_period_ms);
|
||||
} else if (host_hz > 0.0) {
|
||||
std::printf("psxrecomp: host panel %.1f Hz does not match guest "
|
||||
"cadence; keeping %.2f Hz pacing\n",
|
||||
host_hz,
|
||||
g_frame_period_ms > 0.0 ? 1000.0 / g_frame_period_ms : 0.0);
|
||||
} else {
|
||||
std::printf("psxrecomp: host refresh unknown; keeping %.2f Hz pacing\n",
|
||||
g_frame_period_ms > 0.0 ? 1000.0 / g_frame_period_ms : 0.0);
|
||||
}
|
||||
apply_present_cadence();
|
||||
#else
|
||||
(void)force_log;
|
||||
(void)force_probe;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int host_driver_vsync_unreliable(void) {
|
||||
#ifdef _WIN32
|
||||
return 0;
|
||||
@@ -6746,6 +6805,8 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
|
||||
return ep;
|
||||
}
|
||||
|
||||
refresh_host_display_cadence(0, 0);
|
||||
|
||||
/* TCP turbo is for automated validation and trace capture. It keeps the
|
||||
* simulation advancing and the debug server polling, but removes frontend
|
||||
* presentation and wall-clock pacing. */
|
||||
@@ -13317,7 +13378,8 @@ session_reboot:
|
||||
/* We need to adjust the frame pacing for PAL games to run at the
|
||||
* correct speed */
|
||||
vblank_cycles = 677376u;
|
||||
g_frame_period_ms = 1000.0 / 50.0; /* 50hz refresh rate */
|
||||
g_guest_frame_period_ms = 1000.0 / 50.0; /* 50hz refresh rate */
|
||||
g_frame_period_ms = g_guest_frame_period_ms;
|
||||
}
|
||||
else if (ident.region == "NTSC-J") cdrom_set_disc_scex("SCEI");
|
||||
else if (ident.region == "NTSC-U") cdrom_set_disc_scex("SCEA");
|
||||
@@ -13547,28 +13609,10 @@ session_reboot:
|
||||
if (!g_fullscreen && !g_video_win_w_explicit)
|
||||
SDL_MaximizeWindow(sdl_window);
|
||||
|
||||
/* Host refresh: if the panel matches the current guest cadence, record it
|
||||
* so driver vsync can own cadence (pacer skipped). Mismatched and unknown
|
||||
* refresh rates keep guest pacing and force swap interval 0; vsync as the
|
||||
* clock would otherwise run the sim at the panel rate. */
|
||||
{
|
||||
SDL_DisplayMode dm;
|
||||
int disp_idx = SDL_GetWindowDisplayIndex(sdl_window);
|
||||
if (disp_idx >= 0 && SDL_GetCurrentDisplayMode(disp_idx, &dm) == 0 && dm.refresh_rate > 0) {
|
||||
double host_hz = (double)dm.refresh_rate;
|
||||
g_host_refresh_hz = host_hz;
|
||||
if (host_refresh_matches_guest_cadence()) {
|
||||
g_frame_period_ms = 1000.0 / host_hz;
|
||||
std::printf("psxrecomp: sync-to-host-refresh: pacing to %.1f Hz panel "
|
||||
"(%.4f ms/frame)\n", host_hz, g_frame_period_ms);
|
||||
} else {
|
||||
std::printf("psxrecomp: host panel %.1f Hz does not match guest "
|
||||
"cadence; keeping %.2f Hz pacing\n",
|
||||
host_hz,
|
||||
g_frame_period_ms > 0.0 ? 1000.0 / g_frame_period_ms : 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Host refresh: if the window's current panel matches the guest cadence,
|
||||
* record it so driver vsync can own cadence. Re-probed while running so
|
||||
* mixed-refresh multi-monitor moves cannot leave this latched at startup. */
|
||||
refresh_host_display_cadence(1, 1);
|
||||
|
||||
/* OpenGL backend: create the GL context now. On failure, relabel the
|
||||
* facade back to software (rasterization already runs through software in
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard mixed-refresh monitor moves against startup-latched cadence."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
MAIN = (ROOT / "runtime" / "src" / "main.cpp").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def require(needle: str, message: str) -> None:
|
||||
if needle not in MAIN:
|
||||
raise AssertionError(message)
|
||||
|
||||
|
||||
require(
|
||||
"static double g_guest_frame_period_ms = PSX_FRAME_PERIOD_MS;",
|
||||
"guest cadence must be stored separately from host-synced pacing",
|
||||
)
|
||||
require(
|
||||
"static int g_host_refresh_display_idx = -2;",
|
||||
"host display identity must be tracked across runtime probes",
|
||||
)
|
||||
require(
|
||||
"static void refresh_host_display_cadence(int force_log, int force_probe)",
|
||||
"host display cadence must have a reusable refresh helper",
|
||||
)
|
||||
require(
|
||||
"const double guest_hz = 1000.0 / g_guest_frame_period_ms;",
|
||||
"host-refresh matching must compare against guest cadence, not effective host period",
|
||||
)
|
||||
require(
|
||||
"g_frame_period_ms = g_guest_frame_period_ms;\n if (host_refresh_matches_guest_cadence())",
|
||||
"mismatched display probes must restore guest pacing before deciding vsync ownership",
|
||||
)
|
||||
require(
|
||||
"refresh_host_display_cadence(1, 1);",
|
||||
"startup must use the shared host display cadence helper",
|
||||
)
|
||||
require(
|
||||
"refresh_host_display_cadence(0, 0);",
|
||||
"runtime must re-probe host display cadence after window moves",
|
||||
)
|
||||
require(
|
||||
"disp_idx == g_host_refresh_display_idx",
|
||||
"runtime probe must detect display index changes",
|
||||
)
|
||||
require(
|
||||
"now_ms - g_host_refresh_last_probe_ms < 1000ull",
|
||||
"runtime probe must still refresh same-display mode changes periodically",
|
||||
)
|
||||
|
||||
if MAIN.count("SDL_GetWindowDisplayIndex(sdl_window)") != 1:
|
||||
raise AssertionError("window display probing should have one shared owner")
|
||||
|
||||
startup = MAIN.index("refresh_host_display_cadence(1, 1);")
|
||||
runtime = MAIN.index("refresh_host_display_cadence(0, 0);")
|
||||
if startup == runtime:
|
||||
raise AssertionError("startup and runtime probes must be distinct call sites")
|
||||
|
||||
print("mixed-refresh display cadence guard passed")
|
||||
@@ -18,7 +18,7 @@ require(
|
||||
"host refresh must be compared against the active guest cadence",
|
||||
)
|
||||
require(
|
||||
"const double guest_hz = 1000.0 / g_frame_period_ms;",
|
||||
"const double guest_hz = 1000.0 / g_guest_frame_period_ms;",
|
||||
"guest cadence must derive from the current frame period",
|
||||
)
|
||||
require(
|
||||
|
||||
Reference in New Issue
Block a user