[gtk+/wip/frame-synchronization: 31/33] Add gdk_frame_timings_get_predicted_presentation_time()



commit f3d46d61978e873189101dfeb9fd0e9ec178a4cf
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Thu Nov 15 14:11:41 2012 -0500

    Add gdk_frame_timings_get_predicted_presentation_time()
    
    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 GdkPaintClockIdle is adjusted to give predictable
    presentation times for frames that are not throttled by the windowing
    system.
    
    Helper functions:
    
     gdk_paint_clock_get_current_frame_timings()
     gdk_paint_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/gdkframehistory.c    |    3 ++
 gdk/gdkframetimings.c    |   18 +++++++++++
 gdk/gdkframetimings.h    |    4 ++
 gdk/gdkpaintclock.c      |   73 ++++++++++++++++++++++++++++++++++++++++++++++
 gdk/gdkpaintclock.h      |   13 +++++++-
 gdk/gdkpaintclockidle.c  |   37 ++++++++++++++++-------
 gdk/x11/gdkdisplay-x11.c |   11 ++++++-
 gdk/x11/gdkwindow-x11.c  |   59 ++++++++++++++++++++++++++++++++++---
 gdk/x11/gdkwindow-x11.h  |    4 ++
 9 files changed, 203 insertions(+), 19 deletions(-)
---
diff --git a/gdk/gdkframehistory.c b/gdk/gdkframehistory.c
index 322467b..2f7147e 100644
--- a/gdk/gdkframehistory.c
+++ b/gdk/gdkframehistory.c
@@ -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");
diff --git a/gdk/gdkframetimings.c b/gdk/gdkframetimings.c
index 0813001..ad9ec2e 100644
--- a/gdk/gdkframetimings.c
+++ b/gdk/gdkframetimings.c
@@ -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;
@@ -188,6 +189,23 @@ gdk_frame_timings_set_presentation_time (GdkFrameTimings *timings,
 }
 
 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)
 {
   g_return_val_if_fail (timings != NULL, 0);
diff --git a/gdk/gdkframetimings.h b/gdk/gdkframetimings.h
index 53dbffb..8e86c6e 100644
--- a/gdk/gdkframetimings.h
+++ b/gdk/gdkframetimings.h
@@ -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__ */
diff --git a/gdk/gdkpaintclock.c b/gdk/gdkpaintclock.c
index c104992..89c4b1d 100644
--- a/gdk/gdkpaintclock.c
+++ b/gdk/gdkpaintclock.c
@@ -378,3 +378,76 @@ gdk_paint_clock_frame_requested (GdkPaintClock *clock)
   g_signal_emit (G_OBJECT (clock),
                  signals[FRAME_REQUESTED], 0);
 }
+
+GdkFrameTimings *
+gdk_paint_clock_get_current_frame_timings (GdkPaintClock *clock)
+{
+  GdkFrameHistory *history;
+  gint64 frame_counter;
+
+  g_return_val_if_fail (GDK_IS_PAINT_CLOCK (clock), 0);
+
+  history = gdk_paint_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_paint_clock_get_refresh_info (GdkPaintClock *clock,
+                                  gint64         base_time,
+                                  gint64        *refresh_interval_return,
+                                  gint64        *presentation_time_return)
+{
+  GdkFrameHistory *history;
+  gint64 frame_counter;
+
+  g_return_if_fail (GDK_IS_PAINT_CLOCK (clock));
+
+  history = gdk_paint_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--;
+    }
+}
diff --git a/gdk/gdkpaintclock.h b/gdk/gdkpaintclock.h
index 46ad01d..e84a10d 100644
--- a/gdk/gdkpaintclock.h
+++ b/gdk/gdkpaintclock.h
@@ -78,7 +78,8 @@ struct _GdkPaintClockInterface
 {
   GTypeInterface		   base_iface;
 
-  guint64  (* get_frame_time)      (GdkPaintClock *clock);
+  guint64  (* get_frame_time)            (GdkPaintClock *clock);
+  guint64  (* predict_presentation_time) (GdkPaintClock *clock);
 
   void               (* request_phase) (GdkPaintClock      *clock,
                                         GdkPaintClockPhase  phase);
@@ -102,7 +103,8 @@ struct _GdkPaintClockInterface
 
 GType    gdk_paint_clock_get_type             (void) G_GNUC_CONST;
 
-guint64  gdk_paint_clock_get_frame_time      (GdkPaintClock *clock);
+guint64  gdk_paint_clock_get_frame_time            (GdkPaintClock *clock);
+guint64  gdk_paint_clock_predict_presentation_time (GdkPaintClock *clock);
 
 void               gdk_paint_clock_request_phase (GdkPaintClock      *clock,
                                                   GdkPaintClockPhase  phase);
@@ -117,6 +119,13 @@ GdkFrameHistory *gdk_paint_clock_get_history (GdkPaintClock *clock);
 void  gdk_paint_clock_get_frame_time_val (GdkPaintClock  *clock,
                                           GTimeVal       *timeval);
 
+void gdk_paint_clock_get_refresh_info (GdkPaintClock *clock,
+                                       gint64         base_time,
+                                       gint64        *refresh_interval_return,
+                                       gint64        *presentation_time_return);
+
+GdkFrameTimings *gdk_paint_clock_get_current_frame_timings (GdkPaintClock *clock);
+
 /* Signal emitters (used in paint clock implementations) */
 void     gdk_paint_clock_frame_requested     (GdkPaintClock *clock);
 
diff --git a/gdk/gdkpaintclockidle.c b/gdk/gdkpaintclockidle.c
index 09be1ed..7666e70 100644
--- a/gdk/gdkpaintclockidle.c
+++ b/gdk/gdkpaintclockidle.c
@@ -204,7 +204,7 @@ maybe_start_idle (GdkPaintClockIdle *clock_idle)
 {
   GdkPaintClockIdlePrivate *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 (GdkPaintClockIdle *clock_idle)
     }
 }
 
+static gint64
+compute_min_next_frame_time (GdkPaintClockIdle *clock_idle,
+                             gint64             last_frame_time)
+{
+  gint64 presentation_time;
+  gint64 refresh_interval;
+
+  gdk_paint_clock_get_refresh_info (GDK_PAINT_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_paint_clock_flush_idle (void *data)
 {
@@ -277,6 +294,7 @@ gdk_paint_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_PAINT_CLOCK_PHASE_FLUSH_EVENTS | GDK_PAINT_CLOCK_PHASE_RESUME_EVENTS)) == 0;
@@ -403,19 +421,16 @@ gdk_paint_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 ();
diff --git a/gdk/x11/gdkdisplay-x11.c b/gdk/x11/gdkdisplay-x11.c
index 347e02e..fc5c72d 100644
--- a/gdk/x11/gdkdisplay-x11.c
+++ b/gdk/x11/gdkdisplay-x11.c
@@ -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;
 
           GdkPaintClock *clock = gdk_window_get_paint_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_paint_clock_thaw (clock);
             }
+
+          gdk_paint_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;
diff --git a/gdk/x11/gdkwindow-x11.c b/gdk/x11/gdkwindow-x11.c
index 29deba1..2900c87 100644
--- a/gdk/x11/gdkwindow-x11.c
+++ b/gdk/x11/gdkwindow-x11.c
@@ -277,6 +277,58 @@ unhook_surface_changed (GdkWindow *window)
 }
 
 static void
+gdk_x11_window_predict_presentation_time (GdkWindow *window)
+{
+  GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl);
+  GdkPaintClock *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_paint_clock (window);
+
+  timings = gdk_paint_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_paint_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)
 {
   GdkWindowImplX11 *impl;
@@ -298,8 +350,6 @@ static void
 gdk_x11_window_end_frame (GdkWindow *window)
 {
   GdkPaintClock *clock;
-  GdkFrameHistory *history;
-  gint64 frame_counter;
   GdkFrameTimings *timings;
   GdkWindowImplX11 *impl;
 
@@ -313,9 +363,7 @@ gdk_x11_window_end_frame (GdkWindow *window)
     return;
 
   clock = gdk_window_get_paint_clock (window);
-  history = gdk_paint_clock_get_history (clock);
-  frame_counter = gdk_frame_history_get_frame_counter (history);
-  timings = gdk_frame_history_get_timings (history, frame_counter);
+  timings = gdk_paint_clock_get_current_frame_timings (clock);
 
   impl->toplevel->in_frame = FALSE;
 
@@ -879,6 +927,7 @@ static void
 on_paint_clock_before_paint (GdkPaintClock *clock,
                              GdkWindow     *window)
 {
+  gdk_x11_window_predict_presentation_time (window);
   gdk_x11_window_begin_frame (window);
 }
 
diff --git a/gdk/x11/gdkwindow-x11.h b/gdk/x11/gdkwindow-x11.h
index 8dde335..7eb95a0 100644
--- a/gdk/x11/gdkwindow-x11.h
+++ b/gdk/x11/gdkwindow-x11.h
@@ -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
 };
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]