]> Pileus Git - ~andy/gtk/commitdiff
Add gdk_frame_timings_get_predicted_presentation_time()
authorOwen W. Taylor <otaylor@fishsoup.net>
Thu, 15 Nov 2012 19:11:41 +0000 (14:11 -0500)
committerOwen W. Taylor <otaylor@fishsoup.net>
Thu, 14 Feb 2013 22:19:51 +0000 (17:19 -0500)
For an operation like synchronizing audio to video playback, we need to
be able to predict the time that a frame will be presented. The details
of this depend on the windowing system, so make the backend predict
a presentation time for ::begin-frame and set it on the GdkFrameTimings.

The timing algorithm of GdkFrameClockIdle is adjusted to give predictable
presentation times for frames that are not throttled by the windowing
system.

Helper functions:

 gdk_frame_clock_get_current_frame_timings()
 gdk_frame_clock_get_refresh_info()

are added for operations that would otherwise be needed multiple times
in different locations.

https://bugzilla.gnome.org/show_bug.cgi?id=685460

gdk/gdkframeclock.c
gdk/gdkframeclock.h
gdk/gdkframeclockidle.c
gdk/gdkframehistory.c
gdk/gdkframetimings.c
gdk/gdkframetimings.h
gdk/x11/gdkdisplay-x11.c
gdk/x11/gdkwindow-x11.c
gdk/x11/gdkwindow-x11.h

index ecb84bd8aa338ac3c8b1dd0b2d8caff1d4d33986..fd41d945331af052c7d189679684f9727affae55 100644 (file)
@@ -378,3 +378,76 @@ gdk_frame_clock_frame_requested (GdkFrameClock *clock)
   g_signal_emit (G_OBJECT (clock),
                  signals[FRAME_REQUESTED], 0);
 }
+
+GdkFrameTimings *
+gdk_frame_clock_get_current_frame_timings (GdkFrameClock *clock)
+{
+  GdkFrameHistory *history;
+  gint64 frame_counter;
+
+  g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0);
+
+  history = gdk_frame_clock_get_history (clock);
+  frame_counter = gdk_frame_history_get_frame_counter (history);
+  return gdk_frame_history_get_timings (history, frame_counter);
+}
+
+
+#define DEFAULT_REFRESH_INTERVAL 16667 /* 16.7ms (1/60th second) */
+#define MAX_HISTORY_AGE 150000         /* 150ms */
+
+void
+gdk_frame_clock_get_refresh_info (GdkFrameClock *clock,
+                                  gint64         base_time,
+                                  gint64        *refresh_interval_return,
+                                  gint64        *presentation_time_return)
+{
+  GdkFrameHistory *history;
+  gint64 frame_counter;
+
+  g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
+
+  history = gdk_frame_clock_get_history (clock);
+  frame_counter = gdk_frame_history_get_frame_counter (history);
+
+  if (presentation_time_return)
+    *presentation_time_return = 0;
+  if (refresh_interval_return)
+    *refresh_interval_return = DEFAULT_REFRESH_INTERVAL;
+
+  while (TRUE)
+    {
+      GdkFrameTimings *timings = gdk_frame_history_get_timings (history, frame_counter);
+      gint64 presentation_time;
+      gint64 refresh_interval;
+
+      if (timings == NULL)
+        return;
+
+      refresh_interval = gdk_frame_timings_get_refresh_interval (timings);
+      presentation_time = gdk_frame_timings_get_presentation_time (timings);
+
+      if (presentation_time != 0)
+        {
+          if (presentation_time > base_time - MAX_HISTORY_AGE &&
+              presentation_time_return)
+            {
+              if (refresh_interval == 0)
+                refresh_interval = DEFAULT_REFRESH_INTERVAL;
+
+              if (refresh_interval_return)
+                *refresh_interval_return = refresh_interval;
+
+              while (presentation_time < base_time)
+                presentation_time += refresh_interval;
+
+              if (presentation_time_return)
+                *presentation_time_return = presentation_time;
+            }
+
+          return;
+        }
+
+      frame_counter--;
+    }
+}
index 03b04304cd0fb6efc9630e978bf9152186105bc8..8d79f90a96b0b87f2ca5e2d84db613756aa1f82a 100644 (file)
@@ -78,7 +78,7 @@ struct _GdkFrameClockInterface
 {
   GTypeInterface                  base_iface;
 
-  guint64  (* get_frame_time)      (GdkFrameClock *clock);
+  guint64  (* get_frame_time)            (GdkFrameClock *clock);
 
   void               (* request_phase) (GdkFrameClock      *clock,
                                         GdkFrameClockPhase  phase);
@@ -102,7 +102,7 @@ struct _GdkFrameClockInterface
 
 GType    gdk_frame_clock_get_type             (void) G_GNUC_CONST;
 
-guint64  gdk_frame_clock_get_frame_time      (GdkFrameClock *clock);
+guint64  gdk_frame_clock_get_frame_time            (GdkFrameClock *clock);
 
 void               gdk_frame_clock_request_phase (GdkFrameClock      *clock,
                                                   GdkFrameClockPhase  phase);
@@ -117,6 +117,13 @@ GdkFrameHistory *gdk_frame_clock_get_history (GdkFrameClock *clock);
 void  gdk_frame_clock_get_frame_time_val (GdkFrameClock  *clock,
                                           GTimeVal       *timeval);
 
+void gdk_frame_clock_get_refresh_info (GdkFrameClock *clock,
+                                       gint64         base_time,
+                                       gint64        *refresh_interval_return,
+                                       gint64        *presentation_time_return);
+
+GdkFrameTimings *gdk_frame_clock_get_current_frame_timings (GdkFrameClock *clock);
+
 /* Signal emitters (used in frame clock implementations) */
 void     gdk_frame_clock_frame_requested     (GdkFrameClock *clock);
 
index 09624c45b0bfaeace0dd9c29abee27997a7adb21..47103703cd127c6cde460f85bccf1f786b590587 100644 (file)
@@ -204,7 +204,7 @@ maybe_start_idle (GdkFrameClockIdle *clock_idle)
 {
   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
 
-  if (priv->freeze_count == 0)
+  if (priv->freeze_count == 0 && priv->requested != 0)
     {
       guint min_interval = 0;
 
@@ -240,6 +240,23 @@ maybe_start_idle (GdkFrameClockIdle *clock_idle)
     }
 }
 
+static gint64
+compute_min_next_frame_time (GdkFrameClockIdle *clock_idle,
+                             gint64             last_frame_time)
+{
+  gint64 presentation_time;
+  gint64 refresh_interval;
+
+  gdk_frame_clock_get_refresh_info (GDK_FRAME_CLOCK (clock_idle),
+                                    last_frame_time,
+                                    &refresh_interval, &presentation_time);
+
+  if (presentation_time == 0)
+    return last_frame_time + refresh_interval;
+  else
+    return presentation_time + refresh_interval / 2;
+}
+
 static gboolean
 gdk_frame_clock_flush_idle (void *data)
 {
@@ -277,6 +294,7 @@ gdk_frame_clock_paint_idle (void *data)
 
   priv->paint_idle_id = 0;
   priv->in_paint_idle = TRUE;
+  priv->min_next_frame_time = 0;
 
   skip_to_resume_events =
     (priv->requested & ~(GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS | GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS)) == 0;
@@ -403,19 +421,16 @@ gdk_frame_clock_paint_idle (void *data)
 
   priv->in_paint_idle = FALSE;
 
-  if (priv->freeze_count == 0 && priv->requested != 0)
+  /* If there is throttling in the backend layer, then we'll do another
+   * update as soon as the backend unthrottles (if there is work to do),
+   * otherwise we need to figure when the next frame should be.
+   */
+  if (priv->freeze_count == 0)
     {
-      /* We need to start over again immediately - this implies that there is no
-       * throttling at the backend layer, so we need to back-off ourselves.
-       */
-      gdk_flush ();
-      priv->min_next_frame_time = priv->frame_time + FRAME_INTERVAL;
+      priv->min_next_frame_time = compute_min_next_frame_time (clock_idle,
+                                                               priv->frame_time);
       maybe_start_idle (clock_idle);
     }
-  else
-    {
-      priv->min_next_frame_time = 0;
-    }
 
   if (priv->freeze_count == 0)
     priv->sleep_serial = get_sleep_serial ();
index 322467bc86328d9e0570a132fc96c137179fc4ea..2f7147ef73253aa61be3773853bdd9b7b5c45e5b 100644 (file)
@@ -154,6 +154,7 @@ _gdk_frame_history_debug_print (GdkFrameHistory *history,
   gint64 frame_end_time = _gdk_frame_timings_get_frame_end_time (timings);
   gint64 frame_time = gdk_frame_timings_get_frame_time (timings);
   gint64 presentation_time = gdk_frame_timings_get_presentation_time (timings);
+  gint64 predicted_presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings);
   gint64 refresh_interval = gdk_frame_timings_get_refresh_interval (timings);
   gint64 previous_frame_time = 0;
   gboolean slept_before = gdk_frame_timings_get_slept_before (timings);
@@ -177,6 +178,8 @@ _gdk_frame_history_debug_print (GdkFrameHistory *history,
     g_print (" frame_end=%-4.1f", (frame_end_time - frame_time) / 1000.);
   if (presentation_time != 0)
     g_print (" present=%-4.1f", (presentation_time - frame_time) / 1000.);
+  if (predicted_presentation_time != 0)
+    g_print (" predicted=%-4.1f", (predicted_presentation_time - frame_time) / 1000.);
   if (refresh_interval != 0)
     g_print (" refresh_interval=%-4.1f", refresh_interval / 1000.);
   g_print ("\n");
index 08130015fd4a2aa1a7cc41b17b47c9e25c410215..ad9ec2e5273d4785bc733fb2eb776897aeaf3bef 100644 (file)
@@ -29,6 +29,7 @@ struct _GdkFrameTimings
   gint64 drawn_time;
   gint64 presentation_time;
   gint64 refresh_interval;
+  gint64 predicted_presentation_time;
 
 #ifdef G_ENABLE_DEBUG
   gint64 layout_start_time;
@@ -187,6 +188,23 @@ gdk_frame_timings_set_presentation_time (GdkFrameTimings *timings,
   timings->presentation_time = presentation_time;
 }
 
+gint64
+gdk_frame_timings_get_predicted_presentation_time (GdkFrameTimings *timings)
+{
+  g_return_val_if_fail (timings != NULL, 0);
+
+  return timings->predicted_presentation_time;
+}
+
+void
+gdk_frame_timings_set_predicted_presentation_time (GdkFrameTimings *timings,
+                                                   gint64           predicted_presentation_time)
+{
+  g_return_if_fail (timings != NULL);
+
+  timings->predicted_presentation_time = predicted_presentation_time;
+}
+
 gint64
 gdk_frame_timings_get_refresh_interval (GdkFrameTimings *timings)
 {
index 53dbffbd068c78ac418f35b435984f5a607c79af..8e86c6e69ff533703dd538af2249bf3aeacd7683 100644 (file)
@@ -62,6 +62,10 @@ gint64           gdk_frame_timings_get_refresh_interval  (GdkFrameTimings *timin
 void             gdk_frame_timings_set_refresh_interval  (GdkFrameTimings *timings,
                                                           gint64           refresh_interval);
 
+gint64           gdk_frame_timings_get_predicted_presentation_time (GdkFrameTimings *timings);
+void             gdk_frame_timings_set_predicted_presentation_time (GdkFrameTimings *timings,
+                                                                    gint64           predicted_presentation_time);
+
 G_END_DECLS
 
 #endif /* __GDK_FRAME_TIMINGS_H__ */
index 888d87dd700ada2970a2ab7f3fc0c0ea975340c5..cf26b3016df467b412c31398dd7a73472223b40d 100644 (file)
@@ -1108,18 +1108,27 @@ _gdk_wm_protocols_filter (GdkXEvent *xev,
           guint32 d3 = xevent->xclient.data.l[3];
 
           guint64 serial = ((guint64)d0 << 32) | d1;
+          gint64 frame_drawn_time = ((guint64)d2 << 32) | d3;
+          gint64 refresh_interval, presentation_time;
 
           GdkFrameClock *clock = gdk_window_get_frame_clock (event->any.window);
           GdkFrameTimings *timings = find_frame_timings (clock, serial);
 
           if (timings)
-            gdk_frame_timings_set_drawn_time (timings, ((guint64)d2 << 32) | d3);
+            gdk_frame_timings_set_drawn_time (timings, frame_drawn_time);
 
           if (window_impl->toplevel->frame_pending)
             {
               window_impl->toplevel->frame_pending = FALSE;
               gdk_frame_clock_thaw (clock);
             }
+
+          gdk_frame_clock_get_refresh_info (clock,
+                                            frame_drawn_time,
+                                            &refresh_interval,
+                                            &presentation_time);
+          if (presentation_time != 0)
+            window_impl->toplevel->throttled_presentation_time = presentation_time + refresh_interval;
         }
 
       return GDK_FILTER_REMOVE;
index ea74baa3f44c0f35272451bd332f9097e639d6d8..d3fac4df5f3a58c9506aa14516a71c8921a910d2 100644 (file)
@@ -277,6 +277,58 @@ unhook_surface_changed (GdkWindow *window)
                                  NULL, NULL);
 }
 
+static void
+gdk_x11_window_predict_presentation_time (GdkWindow *window)
+{
+  GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl);
+  GdkFrameClock *clock;
+  GdkFrameTimings *timings;
+  gint64 frame_time;
+  gint64 presentation_time;
+  gint64 refresh_interval;
+  gboolean slept_before;
+
+  if (!WINDOW_IS_TOPLEVEL (window))
+    return;
+
+  clock = gdk_window_get_frame_clock (window);
+
+  timings = gdk_frame_clock_get_current_frame_timings (clock);
+  frame_time = gdk_frame_timings_get_frame_time (timings);
+  slept_before = gdk_frame_timings_get_slept_before (timings);
+
+  gdk_frame_clock_get_refresh_info (clock,
+                                    frame_time,
+                                    &refresh_interval, &presentation_time);
+
+  if (presentation_time != 0)
+    {
+      if (slept_before)
+        {
+          presentation_time += refresh_interval;
+        }
+      else
+        {
+          if (presentation_time < frame_time + refresh_interval / 2)
+            presentation_time += refresh_interval;
+        }
+    }
+  else
+    {
+      if (slept_before)
+        presentation_time = frame_time + refresh_interval + refresh_interval / 2;
+      else
+        presentation_time = frame_time + refresh_interval;
+    }
+
+  if (presentation_time < impl->toplevel->throttled_presentation_time)
+    presentation_time = impl->toplevel->throttled_presentation_time;
+
+  gdk_frame_timings_set_predicted_presentation_time (timings,
+                                                     presentation_time);
+
+}
+
 static void
 gdk_x11_window_begin_frame (GdkWindow *window)
 {
@@ -299,8 +351,6 @@ static void
 gdk_x11_window_end_frame (GdkWindow *window)
 {
   GdkFrameClock *clock;
-  GdkFrameHistory *history;
-  gint64 frame_counter;
   GdkFrameTimings *timings;
   GdkWindowImplX11 *impl;
 
@@ -314,9 +364,7 @@ gdk_x11_window_end_frame (GdkWindow *window)
     return;
 
   clock = gdk_window_get_frame_clock (window);
-  history = gdk_frame_clock_get_history (clock);
-  frame_counter = gdk_frame_history_get_frame_counter (history);
-  timings = gdk_frame_history_get_timings (history, frame_counter);
+  timings = gdk_frame_clock_get_current_frame_timings (clock);
 
   impl->toplevel->in_frame = FALSE;
 
@@ -880,6 +928,7 @@ static void
 on_frame_clock_before_paint (GdkFrameClock *clock,
                              GdkWindow     *window)
 {
+  gdk_x11_window_predict_presentation_time (window);
   gdk_x11_window_begin_frame (window);
 }
 
index 8dde33501197d990bbd39b43bf4b219037bfe51b..7eb95a07f223d3eb05adb9a0d73efede9b159152 100644 (file)
@@ -156,6 +156,10 @@ struct _GdkToplevelX11
                                 * ConfigureNotify
                                 */
   gint64 current_counter_value;
+
+  /* After a _NET_WM_FRAME_DRAWN message, this is the soonest that we think
+   * frame after will be presented */
+  gint64 throttled_presentation_time;
 #endif
 };