@@ -796,17 +796,111 @@ 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+ }
818+
819+ if (wrapped < - half_period ) {
820+ return wrapped + period ;
821+ }
822+
823+ return wrapped ;
824+ }
825+
799826// Time reserved for SDL_GL_SwapWindow to perform a swap. This value is chosen
800827// empirically as the best balance between minimizing guest stalls while still
801828// hitting the target host refresh rate.
802- #define VSYNC_SWAP_GRACE_PERIOD_NS 300000LL
803- static int64_t display_vsync_interval = 1000000000LL / 60 ;
829+ #define VSYNC_SWAP_GRACE_PERIOD_NS 1400000LL
830+ #define MISSED_FRAME_RESYNC_COUNT 8
831+
832+ #define PLL_BASE_JITTER_NS 1500000LL
833+
834+ static inline void pll_swap_window (SDL_Window * window )
835+ {
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 ;
846+
847+ if (delay_ns > 0 && delay_ns < vblank_pll_state .vblank_period_ns ) {
848+ SDL_DelayPrecise ((uint64_t )delay_ns );
849+ }
850+ }
851+
852+ 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+ }
871+
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 ;
876+ } 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+ }
895+ }
896+ }
804897
805898enum VsyncOverrideBehavior {
806899 VOB_FORCE_OFF ,
807900 VOB_FORCE_ON ,
808901 VOB_NO_OVERRIDE ,
809902};
903+
810904static enum VsyncOverrideBehavior vsync_override = VOB_NO_OVERRIDE ;
811905
812906/**
@@ -874,21 +968,12 @@ static void gl_render_frame(struct xemu_console *scon)
874968
875969 nv2a_release_framebuffer_surface ();
876970
877- static int64_t last_ui_frame_time_ns = 0 ;
878- if (last_ui_frame_time_ns &&
879- (vsync_override == VOB_FORCE_ON || (vsync_override == VOB_NO_OVERRIDE &&
880- g_config .display .window .vsync ))) {
881- int64_t next_frame = last_ui_frame_time_ns + display_vsync_interval -
882- VSYNC_SWAP_GRACE_PERIOD_NS ;
883-
884- int64_t now = qemu_clock_get_ns (QEMU_CLOCK_REALTIME );
885- if (now < next_frame ) {
886- SDL_DelayPrecise (next_frame - now );
887- }
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 );
974+ } else {
975+ SDL_GL_SwapWindow (scon -> real_window );
888976 }
889-
890- SDL_GL_SwapWindow (scon -> real_window );
891- last_ui_frame_time_ns = qemu_clock_get_ns (QEMU_CLOCK_REALTIME );
892977 assert (glGetError () == GL_NO_ERROR );
893978
894979 qatomic_set (& rendering , false);
@@ -898,24 +983,31 @@ static void gl_render_frame(struct xemu_console *scon)
898983#endif
899984}
900985
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+
901994static void calculate_vsync_interval_ns (void )
902995{
903996 SDL_DisplayID display_idx = SDL_GetDisplayForWindow (m_window );
904997 const SDL_DisplayMode * mode = SDL_GetCurrentDisplayMode (display_idx );
905998 if (mode && mode -> refresh_rate_numerator > 0 &&
906999 mode -> refresh_rate_denominator > 0 ) {
907- display_vsync_interval =
908- (1000000000LL * mode -> refresh_rate_denominator ) /
909- mode -> refresh_rate_numerator ;
1000+ apply_vsync_interval ((1000000000LL * mode -> refresh_rate_denominator ) /
1001+ mode -> refresh_rate_numerator );
9101002 return ;
9111003 }
9121004
9131005 if (mode && mode -> refresh_rate > 0 ) {
914- display_vsync_interval = ( int64_t )(1000000000.0 / mode -> refresh_rate );
1006+ apply_vsync_interval (( int64_t )(1000000000.0 / mode -> refresh_rate ) );
9151007 return ;
9161008 }
9171009
918- display_vsync_interval = 1000000000LL / 60 ;
1010+ apply_vsync_interval ( 1000000000LL / 60 ) ;
9191011}
9201012
9211013static bool event_watch_callback (void * userdata , SDL_Event * event )
@@ -928,6 +1020,10 @@ static bool event_watch_callback(void *userdata, SDL_Event *event)
9281020 } else if (event -> type == SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED ||
9291021 event -> type == SDL_EVENT_WINDOW_DISPLAY_CHANGED ) {
9301022 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);
9311027 }
9321028
9331029 return true; // Ignored
0 commit comments