mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Preserve stock-frame VSync on faster displays
This commit is contained in:
@@ -1194,6 +1194,9 @@ if(BUILD_TESTING)
|
||||
# Bug B: the pacing decision must take ONE counter sample, so the unsigned
|
||||
# subtraction cannot underflow into a ~24.7-day SDL_Delay.
|
||||
add_executable(frame_pacing_test tests/test_frame_pacing.c)
|
||||
add_executable(present_sync_test tests/test_present_sync.c)
|
||||
target_include_directories(present_sync_test PRIVATE include)
|
||||
add_test(NAME present_sync_test COMMAND present_sync_test)
|
||||
target_include_directories(frame_pacing_test PRIVATE include)
|
||||
add_test(NAME frame_pacing_test COMMAND frame_pacing_test)
|
||||
# Render thread core ([video] render_thread, docs/RENDER_THREAD.md):
|
||||
|
||||
@@ -45,14 +45,14 @@ uint32_t frame_pacing_sleep_ms(uint64_t now, uint64_t deadline,
|
||||
* period in milliseconds (e.g. 1000.0 / 59.94). */
|
||||
void frame_pacer_wait(FramePacer *p, double period_ms);
|
||||
|
||||
/* 1 while the display driver's swap block — not the pacer above — is what holds
|
||||
* the guest to its vblank cadence (present_vsync_owns_cadence() in main.cpp).
|
||||
* The two are XOR: when this returns 1 the frontend does not call
|
||||
* frame_pacer_wait(), so the ONLY thing throttling the guest is the swap.
|
||||
/* 1 when the panel matches guest cadence and synchronized swaps can supply
|
||||
* the guest's VBlank clock (present_vsync_owns_cadence() in main.cpp). The
|
||||
* deadline pacer still caps speed if the driver does not actually block.
|
||||
* Faster panels can also synchronize swaps while the pacer owns guest timing;
|
||||
* this query then returns 0.
|
||||
*
|
||||
* A renderer that elides a redundant swap (unchanged front buffer) must
|
||||
* therefore not do so while this is 1: the elided frame blocks on nothing, and
|
||||
* a game that presents every other vblank runs at 2x. */
|
||||
* therefore retain it while this is 1 to preserve display-clock pacing. */
|
||||
int psx_present_vsync_owns_cadence(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef PSX_PRESENT_SYNC_H
|
||||
#define PSX_PRESENT_SYNC_H
|
||||
|
||||
/* Swap synchronization prevents tearing; the wall-clock pacer limits guest
|
||||
* speed. They need not have the same rate. A faster panel can synchronize
|
||||
* stock frames without becoming the guest's clock. A slower/unknown panel
|
||||
* cannot block every guest frame without slowing emulation.
|
||||
*
|
||||
* blocked covers interpolation's own scheduler, netplay, driver recovery,
|
||||
* and hosts whose swap blocking is unreliable. Mode 2 is the existing VRR
|
||||
* frame-generation mode, which uses immediate swaps. */
|
||||
static inline int psx_present_sync_interval(int requested, int blocked,
|
||||
double guest_hz, double host_hz) {
|
||||
if (blocked || (requested != 1 && requested != -1))
|
||||
return 0;
|
||||
if (!(guest_hz > 0.0) || !(host_hz > 0.0))
|
||||
return 0;
|
||||
/* Same 2% tolerance as the frontend's host/guest cadence match. */
|
||||
return host_hz >= guest_hz * 0.98 ? requested : 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
+15
-13
@@ -62,6 +62,7 @@ extern "C" void psx_event_step_conservative_env_init(void);
|
||||
#include "../third_party/stb_image.h"
|
||||
#include "gpu_vk_renderer.h"
|
||||
#include "frame_pacing.h"
|
||||
#include "present_sync.h"
|
||||
#include "latency_ring.h"
|
||||
#include "sio.h"
|
||||
#ifndef PSX_MAX_PLAYERS
|
||||
@@ -1459,10 +1460,10 @@ static int g_fmv_skip_no_xa_hold = 4;
|
||||
* (1=vsync/tear-free, 0=immediate/lowest display latency+tearing, -1=adaptive);
|
||||
* it trims the display-side scanout latency the CPU-side ring can't see.
|
||||
*
|
||||
* Driver vsync and the wall-clock pacer are XOR. Waiting on both (pacer then
|
||||
* FIFO SwapBuffers) double-blocks on Linux compositors: ~16.7 ms + ~16.7 ms
|
||||
* = 30 Hz / 0.50x with the CPU idle. ~60 Hz panels may use vsync as the clock;
|
||||
* otherwise the pacer holds 59.94 Hz and present must not wait on the swap. */
|
||||
* A deadline pacer caps guest speed even if the driver ignores swap blocking.
|
||||
* A matching panel can supply the guest clock; a faster panel synchronizes
|
||||
* scanout while the pacer retains the guest cadence. Do not equate clock
|
||||
* ownership with the swap interval or high-refresh fullscreen can tear. */
|
||||
static int g_low_latency_input = 1;
|
||||
/* [video] render_thread (docs/RENDER_THREAD.md): the GL backend runs on its
|
||||
* own thread. Started at the first vblank once the context and every startup
|
||||
@@ -3611,15 +3612,12 @@ extern "C" int psx_present_vsync_owns_cadence(void) {
|
||||
}
|
||||
|
||||
static int present_effective_swap_interval(void) {
|
||||
if (g_netplay_vsync_forced_off || psx_netplay_active())
|
||||
return 0;
|
||||
if (g_frame_interpolation)
|
||||
return 0;
|
||||
if (g_frame_period_ms <= 0.0)
|
||||
return 0;
|
||||
if (present_vsync_owns_cadence())
|
||||
return g_video_vsync;
|
||||
return 0;
|
||||
const int blocked = g_netplay_vsync_forced_off || psx_netplay_active() ||
|
||||
g_frame_interpolation || g_present_vsync_disabled ||
|
||||
host_driver_vsync_unreliable();
|
||||
return psx_present_sync_interval(g_video_vsync, blocked,
|
||||
g_frame_period_ms > 0.0 ? 1000.0 / g_frame_period_ms : 0.0,
|
||||
g_host_refresh_hz);
|
||||
}
|
||||
|
||||
static int present_should_wall_pace(void) {
|
||||
@@ -3678,6 +3676,10 @@ static void log_present_cadence(void) {
|
||||
std::printf("psxrecomp: present cadence: wall-clock pacer "
|
||||
"(%.4f ms/frame); driver vsync off under WSL\n",
|
||||
g_frame_period_ms);
|
||||
} else if (present_effective_swap_interval() != 0) {
|
||||
std::printf("psxrecomp: present cadence: wall-clock pacer "
|
||||
"(%.4f ms/frame); synchronized swaps on %.0f Hz panel\n",
|
||||
g_frame_period_ms, g_host_refresh_hz);
|
||||
} else if (g_host_refresh_hz > 0.0) {
|
||||
std::printf("psxrecomp: present cadence: wall-clock pacer "
|
||||
"(%.4f ms/frame); driver vsync off on %.0f Hz panel\n",
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "present_sync.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static int failures;
|
||||
static void check(int requested, int blocked, double guest, double host,
|
||||
int expected) {
|
||||
const int actual = psx_present_sync_interval(requested, blocked, guest, host);
|
||||
if (actual != expected) {
|
||||
fprintf(stderr, "sync(%d,%d,%.3f,%.3f): got %d expected %d\n",
|
||||
requested, blocked, guest, host, actual, expected);
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
const double panels[] = {60, 75, 120, 144, 165, 240};
|
||||
for (unsigned i = 0; i < sizeof panels / sizeof panels[0]; ++i) {
|
||||
check(1, 0, 59.94, panels[i], 1);
|
||||
check(1, 0, 50, panels[i], 1); /* PAL on a faster panel */
|
||||
check(-1, 0, 59.94, panels[i], -1); /* explicit adaptive */
|
||||
check(0, 0, 59.94, panels[i], 0); /* explicit VSync off */
|
||||
check(2, 0, 59.94, panels[i], 0); /* VRR frame generation */
|
||||
check(1, 1, 59.94, panels[i], 0); /* scheduler/netplay/recovery */
|
||||
}
|
||||
check(1, 0, 50, 50, 1);
|
||||
check(1, 0, 60, 59, 1);
|
||||
check(1, 0, 59.94, 50, 0); /* do not slow the guest to the panel */
|
||||
check(1, 0, 120, 60, 0);
|
||||
check(1, 0, 59.94, 0, 0); /* unknown display */
|
||||
check(1, 0, 0, 144, 0); /* uncapped guest */
|
||||
puts(failures ? "present sync FAILED" : "present sync passed (42 cases)");
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user