From 82c314f1af3fca776d4e7796ab7555b7182c8464 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 5 Jun 2020 11:16:57 +0200 Subject: [PATCH 1/3] frame clock: Track if paint is caused by thaw or not When we get to a paint cycle we now know if this was caused by a thaw, which typically means last frame was drawn, or some other event. In the first case the time of the cycle is tied to the vblank in some sense, and in the others it is essentially random. We can use this information to compute better frame times. (Will be done in later commits.) --- gdk/gdkframeclockidle.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/gdk/gdkframeclockidle.c b/gdk/gdkframeclockidle.c index ef32ad518a..cf43f7442f 100644 --- a/gdk/gdkframeclockidle.c +++ b/gdk/gdkframeclockidle.c @@ -56,6 +56,7 @@ struct _GdkFrameClockIdlePrivate GdkFrameClockPhase phase; guint in_paint_idle : 1; + guint paint_is_thaw : 1; #ifdef G_OS_WIN32 guint begin_period : 1; #endif @@ -275,7 +276,8 @@ gdk_frame_clock_idle_get_frame_time (GdkFrameClock *clock) (priv)->updating_count > 0)) static void -maybe_start_idle (GdkFrameClockIdle *clock_idle) +maybe_start_idle (GdkFrameClockIdle *clock_idle, + gboolean caused_by_thaw) { GdkFrameClockIdlePrivate *priv = clock_idle->priv; @@ -303,6 +305,7 @@ maybe_start_idle (GdkFrameClockIdle *clock_idle) if (!priv->in_paint_idle && priv->paint_idle_id == 0 && RUN_PAINT_IDLE (priv)) { + priv->paint_is_thaw = caused_by_thaw; priv->paint_idle_id = g_timeout_add_full (GDK_PRIORITY_REDRAW, min_interval, gdk_frame_clock_paint_idle, @@ -560,7 +563,7 @@ gdk_frame_clock_paint_idle (void *data) { priv->min_next_frame_time = compute_min_next_frame_time (clock_idle, priv->frame_time); - maybe_start_idle (clock_idle); + maybe_start_idle (clock_idle, FALSE); } if (priv->freeze_count == 0) @@ -580,7 +583,7 @@ gdk_frame_clock_idle_request_phase (GdkFrameClock *clock, GdkFrameClockIdlePrivate *priv = clock_idle->priv; priv->requested |= phase; - maybe_start_idle (clock_idle); + maybe_start_idle (clock_idle, FALSE); } static void @@ -599,7 +602,7 @@ gdk_frame_clock_idle_begin_updating (GdkFrameClock *clock) #endif priv->updating_count++; - maybe_start_idle (clock_idle); + maybe_start_idle (clock_idle, FALSE); } static void @@ -649,7 +652,7 @@ gdk_frame_clock_idle_thaw (GdkFrameClock *clock) priv->freeze_count--; if (priv->freeze_count == 0) { - maybe_start_idle (clock_idle); + maybe_start_idle (clock_idle, TRUE); /* If nothing is requested so we didn't start an idle, we need * to skip to the end of the state chain, since the idle won't * run and do it for us. From 91af8a705b74db6ede1a4e5f0793f44e91b1ffa4 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 5 Jun 2020 11:30:47 +0200 Subject: [PATCH 2/3] frame clock: Better handle non-regular clock cycles We try to step the frame clock in whole refresh_interval steps, but to avoid drift and rounding issues we additionally try to converge it to be synced to the physical vblank (actually the time we get the frame-drawn message from the compositor, but these are tied together). However, the convergence to vsync only really makes sense if the new frame_time actually is tied to the vsync. It may very well be that some other kind of event (say a network or mouse event) triggered the redraw, and not a vsync presentation. We used to assume that all frames that are close in time (< 4 frames apart) were regular and thus tied to the vsync, but there is really no guarantee of that. Even non regular times could be rapid. This commit changes the code to only do the convergence-to-real-time if the cause of the clock cycle was a thaw (i.e. last frame drawn and animating). Paint cycles for any other kind of reason are always scheduled an integer number of frames after the last cycle that was caused by a thaw. --- gdk/gdkframeclockidle.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/gdk/gdkframeclockidle.c b/gdk/gdkframeclockidle.c index cf43f7442f..3b54429bf6 100644 --- a/gdk/gdkframeclockidle.c +++ b/gdk/gdkframeclockidle.c @@ -163,7 +163,7 @@ gdk_frame_clock_idle_dispose (GObject *object) static gint64 compute_smooth_frame_time (GdkFrameClock *clock, gint64 new_frame_time, - gboolean new_frame_time_is_regular, + gboolean new_frame_time_is_vsync_related, gint64 smoothed_frame_time_base, gint64 frame_interval) { @@ -205,13 +205,13 @@ compute_smooth_frame_time (GdkFrameClock *clock, * (current_error/frame_interval)*(current_error/frame_interval)*frame_interval * But this can be simplified as below. * - * Note: We only do this correction if we're regularly animating (no - * or low frame skip). If the last frame was a long time ago, or if - * we're not doing this in the frame cycle this call was likely - * triggered by an input event and new_frame_time is essentially - * random and not tied to the presentation time. + * Note: We only do this correction if the new frame is caused by a + * thaw of the frame clock, so that we know the time is actually + * related to the physical vblank. For frameclock cycles triggered + * by other events we always step up in whole frames from the last + * reported time. */ - if (new_frame_time_is_regular) + if (new_frame_time_is_vsync_related) { current_error = new_smoothed_time - new_frame_time; correction_magnitude = current_error * current_error / frame_interval; /* Note, this is always > 0 due to the square */ @@ -412,7 +412,6 @@ gdk_frame_clock_paint_idle (void *data) { gint64 frame_interval = FRAME_INTERVAL; GdkFrameTimings *prev_timings = gdk_frame_clock_get_current_timings (clock); - gint64 old_frame_time = priv->frame_time; if (prev_timings && prev_timings->refresh_interval) frame_interval = prev_timings->refresh_interval; @@ -427,11 +426,9 @@ gdk_frame_clock_paint_idle (void *data) } else { - /* For long delays, cycle was probably caused by input event rather than animation */ - gboolean is_regular = priv->frame_time - old_frame_time < 4 * FRAME_INTERVAL; priv->smoothed_frame_time_base = compute_smooth_frame_time (clock, priv->frame_time, - is_regular, + priv->paint_is_thaw, priv->smoothed_frame_time_base, priv->smoothed_frame_time_period); priv->smoothed_frame_time_period = frame_interval; From f5de46670b4c6bca15a015302e858b72f43498a9 Mon Sep 17 00:00:00 2001 From: Yariv Barkan <21448-yarivb@users.noreply.gitlab.gnome.org> Date: Sun, 31 May 2020 00:23:24 +0300 Subject: [PATCH 3/3] frame clock: schedule in refresh intervals When the application does not receive "frame drawn" signals we schedule the clock to run more or less at intervals equal to the last known refresh interval. In order to minimize clock skew we have to aim for exact intervals. --- gdk/gdkframeclockidle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdk/gdkframeclockidle.c b/gdk/gdkframeclockidle.c index 3b54429bf6..1a345c8ec0 100644 --- a/gdk/gdkframeclockidle.c +++ b/gdk/gdkframeclockidle.c @@ -559,7 +559,7 @@ gdk_frame_clock_paint_idle (void *data) if (priv->freeze_count == 0) { priv->min_next_frame_time = compute_min_next_frame_time (clock_idle, - priv->frame_time); + priv->smoothed_frame_time_base); maybe_start_idle (clock_idle, FALSE); }