Skip to content

Commit f63b073

Browse files
committed
SQUASHME: Drops complex PLL for simple vblank interval sleep
1 parent 0298b61 commit f63b073

1 file changed

Lines changed: 42 additions & 112 deletions

File tree

ui/xemu.c

Lines changed: 42 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -796,113 +796,54 @@ static void report_stats(void)
796796
}
797797
#endif
798798

799-
typedef struct VBlankPhaseLockedLoop {
800-
int64_t vblank_period_ns;
801-
int64_t expected_vblank_ns;
802-
int64_t last_vblank_ns;
803-
int64_t integral_gain_divisor;
804-
int64_t proportional_gain_divisor;
805-
bool phase_locked;
806-
bool resync_required;
807-
} VBlankPLL;
808-
static VBlankPLL vblank_pll_state = { 0 };
809-
810-
static inline int64_t pll_wrap_phase_error(int64_t error, int64_t period)
811-
{
812-
const int64_t wrapped = error % period;
813-
const int64_t half_period = period / 2;
814-
815-
if (wrapped > half_period) {
816-
return wrapped - period;
817-
}
818799

819-
if (wrapped < -half_period) {
820-
return wrapped + period;
821-
}
800+
enum VsyncOverrideBehavior {
801+
VOB_FORCE_OFF,
802+
VOB_FORCE_ON,
803+
VOB_NO_OVERRIDE,
804+
};
822805

823-
return wrapped;
824-
}
806+
static enum VsyncOverrideBehavior host_vsync_override = VOB_NO_OVERRIDE;
807+
static int64_t host_vblank_period_ns = 0;
825808

826809
// Time reserved for SDL_GL_SwapWindow to perform a swap. This value is chosen
827810
// empirically as the best balance between minimizing guest stalls while still
828811
// hitting the target host refresh rate.
829-
#define VSYNC_SWAP_GRACE_PERIOD_NS 1400000LL
830-
#define MISSED_FRAME_RESYNC_COUNT 8
812+
#define VSYNC_SWAP_GRACE_PERIOD_NS 1000000LL
813+
#define VSYNC_RESYNC_FRAMES 2
831814

832-
#define PLL_BASE_JITTER_NS 1500000LL
833-
834-
static inline void pll_swap_window(SDL_Window *window)
815+
static inline void vsync_swap_window(SDL_Window *window)
835816
{
836-
if (qatomic_xchg(&vblank_pll_state.resync_required, false)) {
837-
vblank_pll_state.last_vblank_ns = 0;
838-
vblank_pll_state.phase_locked = false;
839-
}
840-
841-
if (vblank_pll_state.phase_locked) {
842-
int64_t now = (int64_t)SDL_GetTicksNS();
843-
int64_t sleep_target_ns =
844-
vblank_pll_state.expected_vblank_ns - VSYNC_SWAP_GRACE_PERIOD_NS;
845-
int64_t delay_ns = sleep_target_ns - now;
817+
static int64_t last_swap_complete = 0;
818+
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
846819

847-
if (delay_ns > 0 && delay_ns < vblank_pll_state.vblank_period_ns) {
848-
SDL_DelayPrecise((uint64_t)delay_ns);
820+
if (last_swap_complete > 0) {
821+
int64_t next_vblank = last_swap_complete + host_vblank_period_ns;
822+
if (now <= next_vblank + host_vblank_period_ns) {
823+
int64_t target_swap_time = next_vblank - VSYNC_SWAP_GRACE_PERIOD_NS;
824+
int64_t sleep_duration_ns = target_swap_time - now;
825+
if (sleep_duration_ns > 0) {
826+
SDL_DelayPrecise(sleep_duration_ns);
827+
}
849828
}
850829
}
851830

852831
SDL_GL_SwapWindow(window);
853-
const int64_t swap_finish = (int64_t)SDL_GetTicksNS();
854-
855-
if (!vblank_pll_state.phase_locked) {
856-
if (vblank_pll_state.last_vblank_ns != 0) {
857-
int64_t delta = pll_wrap_phase_error(
858-
swap_finish - vblank_pll_state.last_vblank_ns,
859-
vblank_pll_state.vblank_period_ns);
860-
861-
const int64_t bootstrap_tol =
862-
MIN(PLL_BASE_JITTER_NS, vblank_pll_state.vblank_period_ns / 5);
863-
if (llabs(delta) < bootstrap_tol) {
864-
vblank_pll_state.expected_vblank_ns = swap_finish;
865-
vblank_pll_state.phase_locked = true;
866-
}
867-
}
868-
vblank_pll_state.last_vblank_ns = swap_finish;
869-
return;
870-
}
871832

872-
int64_t error_ns = swap_finish - vblank_pll_state.expected_vblank_ns;
873-
if (llabs(error_ns) >
874-
vblank_pll_state.vblank_period_ns * MISSED_FRAME_RESYNC_COUNT) {
875-
vblank_pll_state.expected_vblank_ns = swap_finish;
833+
int64_t post_swap = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
834+
835+
if (!last_swap_complete ||
836+
post_swap >
837+
last_swap_complete + VSYNC_RESYNC_FRAMES * host_vblank_period_ns) {
838+
last_swap_complete = post_swap;
876839
} else {
877-
int64_t phase_error_ns =
878-
pll_wrap_phase_error(error_ns, vblank_pll_state.vblank_period_ns);
879-
int64_t nearest_vblank = swap_finish - phase_error_ns;
880-
881-
const int64_t tear_tol =
882-
MIN(PLL_BASE_JITTER_NS * 2, vblank_pll_state.vblank_period_ns / 4);
883-
if (llabs(phase_error_ns) > tear_tol) {
884-
// Handle adaptive vsync tearing swaps. These can occur
885-
// significantly out of phase and should be ignored.
886-
vblank_pll_state.expected_vblank_ns =
887-
nearest_vblank + vblank_pll_state.vblank_period_ns;
888-
} else {
889-
vblank_pll_state.vblank_period_ns +=
890-
phase_error_ns / vblank_pll_state.integral_gain_divisor;
891-
vblank_pll_state.expected_vblank_ns =
892-
nearest_vblank + vblank_pll_state.vblank_period_ns +
893-
(phase_error_ns / vblank_pll_state.proportional_gain_divisor);
894-
}
840+
int64_t elapsed_frames =
841+
(post_swap - last_swap_complete + host_vblank_period_ns / 2) /
842+
host_vblank_period_ns;
843+
last_swap_complete += elapsed_frames * host_vblank_period_ns;
895844
}
896845
}
897846

898-
enum VsyncOverrideBehavior {
899-
VOB_FORCE_OFF,
900-
VOB_FORCE_ON,
901-
VOB_NO_OVERRIDE,
902-
};
903-
904-
static enum VsyncOverrideBehavior vsync_override = VOB_NO_OVERRIDE;
905-
906847
/**
907848
* Renders the main interface. Usually called from the main thread,
908849
* but may sometimes be called from another thread.
@@ -958,7 +899,7 @@ static void gl_render_frame(struct xemu_console *scon)
958899
xemu_main_loop_unlock();
959900

960901
xemu_hud_render();
961-
glFinish();
902+
glFlush();
962903

963904
if (release_surface_texture) {
964905
xemu_main_loop_lock();
@@ -968,9 +909,10 @@ static void gl_render_frame(struct xemu_console *scon)
968909

969910
nv2a_release_framebuffer_surface();
970911

971-
if (vsync_override == VOB_FORCE_ON ||
972-
(vsync_override == VOB_NO_OVERRIDE && g_config.display.window.vsync)) {
973-
pll_swap_window(scon->real_window);
912+
if (host_vsync_override == VOB_FORCE_ON ||
913+
(host_vsync_override == VOB_NO_OVERRIDE &&
914+
g_config.display.window.vsync)) {
915+
vsync_swap_window(scon->real_window);
974916
} else {
975917
SDL_GL_SwapWindow(scon->real_window);
976918
}
@@ -983,31 +925,23 @@ static void gl_render_frame(struct xemu_console *scon)
983925
#endif
984926
}
985927

986-
static inline void apply_vsync_interval(const int64_t interval)
987-
{
988-
vblank_pll_state.vblank_period_ns = interval;
989-
// Gain is calculated with a target of locking within ~1.6 seconds at 60Hz.
990-
vblank_pll_state.integral_gain_divisor = 1666666666LL / interval;
991-
vblank_pll_state.proportional_gain_divisor = 166666666LL / interval;
992-
}
993-
994928
static void calculate_vsync_interval_ns(void)
995929
{
996930
SDL_DisplayID display_idx = SDL_GetDisplayForWindow(m_window);
997931
const SDL_DisplayMode *mode = SDL_GetCurrentDisplayMode(display_idx);
998932
if (mode && mode->refresh_rate_numerator > 0 &&
999933
mode->refresh_rate_denominator > 0) {
1000-
apply_vsync_interval((1000000000LL * mode->refresh_rate_denominator) /
1001-
mode->refresh_rate_numerator);
934+
host_vblank_period_ns = (1000000000LL * mode->refresh_rate_denominator) /
935+
mode->refresh_rate_numerator;
1002936
return;
1003937
}
1004938

1005939
if (mode && mode->refresh_rate > 0) {
1006-
apply_vsync_interval((int64_t)(1000000000.0 / mode->refresh_rate));
940+
host_vblank_period_ns = (int64_t)(1000000000.0 / mode->refresh_rate);
1007941
return;
1008942
}
1009943

1010-
apply_vsync_interval(1000000000LL / 60);
944+
host_vblank_period_ns = 1000000000LL / 60;
1011945
}
1012946

1013947
static bool event_watch_callback(void *userdata, SDL_Event *event)
@@ -1020,10 +954,6 @@ static bool event_watch_callback(void *userdata, SDL_Event *event)
1020954
} else if (event->type == SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED ||
1021955
event->type == SDL_EVENT_WINDOW_DISPLAY_CHANGED) {
1022956
calculate_vsync_interval_ns();
1023-
} else if (event->type == SDL_EVENT_WINDOW_RESTORED ||
1024-
event->type == SDL_EVENT_WINDOW_MAXIMIZED ||
1025-
event->type == SDL_EVENT_WINDOW_FOCUS_GAINED) {
1026-
qatomic_set(&vblank_pll_state.resync_required, true);
1027957
}
1028958

1029959
return true; // Ignored
@@ -1408,11 +1338,11 @@ static void setup_nvidia_profile(void)
14081338
nvapi_finalize();
14091339

14101340
if (current_state.vsync_mode == VSYNC_MODE_FORCE_OFF) {
1411-
vsync_override = VOB_FORCE_OFF;
1341+
host_vsync_override = VOB_FORCE_OFF;
14121342
} else if (current_state.vsync_mode == VSYNC_MODE_FORCE_ON) {
1413-
vsync_override = VOB_FORCE_ON;
1343+
host_vsync_override = VOB_FORCE_ON;
14141344
} else {
1415-
vsync_override = VOB_NO_OVERRIDE;
1345+
host_vsync_override = VOB_NO_OVERRIDE;
14161346
}
14171347
}
14181348
}

0 commit comments

Comments
 (0)