[clutter/wip/frame-synchronization: 8/8] Add clutter_stage_set_sync_delay()



commit 7aeae4dd9fd17a8d157387821c7c6b52f3f4d2c1
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Thu Nov 8 12:42:24 2012 -0500

    Add clutter_stage_set_sync_delay()
    
    New experimental API is added to allow changing the way that redraws
    are timed for a stage to include a "sync delay" - a period after
    the vertical blanking period where Clutter simply waits for updates.
    
    In detail, the algorithm is that when the master clock is restarted
    after drawing a frame (in the case where there are timelines running)
    or started fresh in response to a queued redraw or relayout, the
    start is scheduled at the next sync point (sync_delay ms after the
    predicted vblank period) rather than done immediately.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=692901

 clutter/clutter-master-clock.c    |  153 +++++++++++++++++++++++++++----------
 clutter/clutter-stage-private.h   |    4 +-
 clutter/clutter-stage-window.c    |   43 ++++++++++-
 clutter/clutter-stage-window.h    |   10 ++-
 clutter/clutter-stage.c           |   98 ++++++++++++++++++++++-
 clutter/clutter-stage.h           |    6 ++
 clutter/cogl/clutter-stage-cogl.c |  131 ++++++++++++++++++++++++++------
 clutter/cogl/clutter-stage-cogl.h |    6 +-
 8 files changed, 375 insertions(+), 76 deletions(-)
---
diff --git a/clutter/clutter-master-clock.c b/clutter/clutter-master-clock.c
index 32655ee..acd5dc8 100644
--- a/clutter/clutter-master-clock.c
+++ b/clutter/clutter-master-clock.c
@@ -139,24 +139,9 @@ master_clock_is_running (ClutterMasterClock *master_clock)
 {
   ClutterStageManager *stage_manager = clutter_stage_manager_get_default ();
   const GSList *stages, *l;
-  gboolean stage_free = FALSE;
 
   stages = clutter_stage_manager_peek_stages (stage_manager);
 
-  /* If all of the stages are busy waiting for a swap-buffers to complete
-   * then we stop the master clock... */
-  for (l = stages; l != NULL; l = l->next)
-    {
-      if (_clutter_stage_get_pending_swaps (l->data) == 0)
-        {
-          stage_free = TRUE;
-          break;
-        }
-    }
-
-  if (!stage_free)
-    return FALSE;
-
   if (master_clock->timelines)
     return TRUE;
 
@@ -176,6 +161,103 @@ master_clock_is_running (ClutterMasterClock *master_clock)
   return FALSE;
 }
 
+static gint
+master_clock_get_swap_wait_time (ClutterMasterClock *master_clock)
+{
+  ClutterStageManager *stage_manager = clutter_stage_manager_get_default ();
+  const GSList *stages, *l;
+  gint64 min_update_time = -1;
+
+  stages = clutter_stage_manager_peek_stages (stage_manager);
+
+  for (l = stages; l != NULL; l = l->next)
+    {
+      gint64 update_time = _clutter_stage_get_update_time (l->data);
+      if (min_update_time == -1 ||
+          (update_time != -1 && update_time < min_update_time))
+        min_update_time = update_time;
+    }
+
+  if (min_update_time == -1)
+    {
+      return -1;
+    }
+  else
+    {
+      gint64 now = g_source_get_time (master_clock->source);
+      if (min_update_time < now)
+        {
+          return 0;
+        }
+      else
+        {
+          gint64 delay_us = min_update_time - now;
+          return (delay_us + 999) / 1000;
+        }
+    }
+}
+
+static void
+master_clock_schedule_stage_updates (ClutterMasterClock *master_clock)
+{
+  ClutterStageManager *stage_manager = clutter_stage_manager_get_default ();
+  const GSList *stages, *l;
+
+  stages = clutter_stage_manager_peek_stages (stage_manager);
+
+  for (l = stages; l != NULL; l = l->next)
+    _clutter_stage_schedule_update (l->data);
+}
+
+static GSList *
+master_clock_list_ready_stages (ClutterMasterClock *master_clock)
+{
+  ClutterStageManager *stage_manager = clutter_stage_manager_get_default ();
+  const GSList *stages, *l;
+  GSList *result;
+
+  stages = clutter_stage_manager_peek_stages (stage_manager);
+
+  result = NULL;
+  for (l = stages; l != NULL; l = l->next)
+    {
+      gint64 update_time = _clutter_stage_get_update_time (l->data);
+
+      /* If a stage has a swap-buffers pending we don't want to draw to it
+       * in case the driver may block the CPU while it waits for the next
+       * backbuffer to become available.
+       *
+       * TODO: We should be able to identify if we are running triple or N
+       * buffered and in these cases we can still draw if there is 1 swap
+       * pending so we can hopefully always be ready to swap for the next
+       * vblank and really match the vsync frequency.
+       */
+      if (update_time != -1 && update_time <= master_clock->cur_tick)
+        result = g_slist_prepend (result, g_object_ref (l->data));
+    }
+
+  return g_slist_reverse (result);
+}
+
+static void
+master_clock_reschedule_stage_updates (ClutterMasterClock *master_clock,
+                                       GSList             *stages)
+{
+  const GSList *l;
+
+  for (l = stages; l != NULL; l = l->next)
+    {
+      /* Clear the old update time */
+      _clutter_stage_clear_update_time (l->data);
+
+      /* And if there is still work to be done, schedule a new one */
+      if (master_clock->timelines ||
+          _clutter_stage_has_queued_events (l->data) ||
+          _clutter_stage_needs_update (l->data))
+        _clutter_stage_schedule_update (l->data);
+    }
+}
+
 /*
  * master_clock_next_frame_delay:
  * @master_clock: a #ClutterMasterClock
@@ -189,10 +271,17 @@ static gint
 master_clock_next_frame_delay (ClutterMasterClock *master_clock)
 {
   gint64 now, next;
+  gint swap_delay;
 
   if (!master_clock_is_running (master_clock))
     return -1;
 
+  /* If all of the stages are busy waiting for a swap-buffers to complete
+   * then we wait for one to be ready.. */
+  swap_delay = master_clock_get_swap_wait_time (master_clock);
+  if (swap_delay != 0)
+    return swap_delay;
+
   /* When we have sync-to-vblank, we count on swap-buffer requests (or
    * swap-buffer-complete events if supported in the backend) to throttle our
    * frame rate so no additional delay is needed to start the next frame.
@@ -274,14 +363,7 @@ master_clock_process_events (ClutterMasterClock *master_clock,
 
   /* Process queued events */
   for (l = stages; l != NULL; l = l->next)
-    {
-      /* NB: If a stage is busy waiting for a swap-buffers completion then
-       * we don't process its events so we can maximize the benefits of
-       * motion compression, and avoid multiple picks per frame.
-       */
-      if (_clutter_stage_get_pending_swaps (l->data) == 0)
-        _clutter_stage_process_queued_events (l->data);
-    }
+    _clutter_stage_process_queued_events (l->data);
 
   CLUTTER_TIMER_STOP (_clutter_uprof_context, master_event_process);
 
@@ -372,19 +454,7 @@ master_clock_update_stages (ClutterMasterClock *master_clock,
    * is advanced.
    */
   for (l = stages; l != NULL; l = l->next)
-    {
-      /* If a stage has a swap-buffers pending we don't want to draw to it
-       * in case the driver may block the CPU while it waits for the next
-       * backbuffer to become available.
-       *
-       * TODO: We should be able to identify if we are running triple or N
-       * buffered and in these cases we can still draw if there is 1 swap
-       * pending so we can hopefully always be ready to swap for the next
-       * vblank and really match the vsync frequency.
-       */
-      if (_clutter_stage_get_pending_swaps (l->data) == 0)
-        stages_updated |= _clutter_stage_do_update (l->data);
-    }
+    stages_updated |= _clutter_stage_do_update (l->data);
 
   _clutter_run_repaint_functions (CLUTTER_REPAINT_FLAGS_POST_PAINT);
 
@@ -474,7 +544,6 @@ clutter_clock_dispatch (GSource     *source,
 {
   ClutterClockSource *clock_source = (ClutterClockSource *) source;
   ClutterMasterClock *master_clock = clock_source->master_clock;
-  ClutterStageManager *stage_manager = clutter_stage_manager_get_default ();
   gboolean stages_updated = FALSE;
   GSList *stages;
 
@@ -500,8 +569,7 @@ clutter_clock_dispatch (GSource     *source,
   /* We need to protect ourselves against stages being destroyed during
    * event handling
    */
-  stages = clutter_stage_manager_list_stages (stage_manager);
-  g_slist_foreach (stages, (GFunc) g_object_ref, NULL);
+  stages = master_clock_list_ready_stages (master_clock);
 
   master_clock->idle = FALSE;
 
@@ -524,6 +592,8 @@ clutter_clock_dispatch (GSource     *source,
   if (!stages_updated)
     master_clock->idle = TRUE;
 
+  master_clock_reschedule_stage_updates (master_clock, stages);
+
   g_slist_foreach (stages, (GFunc) g_object_unref, NULL);
   g_slist_free (stages);
 
@@ -617,7 +687,10 @@ _clutter_master_clock_add_timeline (ClutterMasterClock *master_clock,
                                              timeline);
 
   if (is_first)
-    _clutter_master_clock_start_running (master_clock);
+    {
+      master_clock_schedule_stage_updates (master_clock);
+      _clutter_master_clock_start_running (master_clock);
+    }
 }
 
 /*
diff --git a/clutter/clutter-stage-private.h b/clutter/clutter-stage-private.h
index 89d1dbd..9ccba3f 100644
--- a/clutter/clutter-stage-private.h
+++ b/clutter/clutter-stage-private.h
@@ -66,7 +66,9 @@ void     _clutter_stage_queue_event                       (ClutterStage *stage,
 gboolean _clutter_stage_has_queued_events                 (ClutterStage *stage);
 void     _clutter_stage_process_queued_events             (ClutterStage *stage);
 void     _clutter_stage_update_input_devices              (ClutterStage *stage);
-int      _clutter_stage_get_pending_swaps                 (ClutterStage *stage);
+void     _clutter_stage_schedule_update                   (ClutterStage *stage);
+gint64    _clutter_stage_get_update_time                  (ClutterStage *stage);
+void     _clutter_stage_clear_update_time                 (ClutterStage *stage);
 gboolean _clutter_stage_has_full_redraw_queued            (ClutterStage *stage);
 
 ClutterActor *_clutter_stage_do_pick (ClutterStage    *stage,
diff --git a/clutter/clutter-stage-window.c b/clutter/clutter-stage-window.c
index 2790a32..c6c9f1a 100644
--- a/clutter/clutter-stage-window.c
+++ b/clutter/clutter-stage-window.c
@@ -122,21 +122,56 @@ _clutter_stage_window_get_geometry (ClutterStageWindow    *window,
   CLUTTER_STAGE_WINDOW_GET_IFACE (window)->get_geometry (window, geometry);
 }
 
-int
-_clutter_stage_window_get_pending_swaps (ClutterStageWindow *window)
+void
+_clutter_stage_window_schedule_update  (ClutterStageWindow *window,
+                                        int                 sync_delay)
+{
+  ClutterStageWindowIface *iface;
+
+  g_return_if_fail (CLUTTER_IS_STAGE_WINDOW (window));
+
+  iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window);
+  if (iface->schedule_update == NULL)
+    {
+      g_assert (!clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS));
+      return;
+    }
+
+  iface->schedule_update (window, sync_delay);
+}
+
+gint64
+_clutter_stage_window_get_update_time (ClutterStageWindow *window)
 {
   ClutterStageWindowIface *iface;
 
   g_return_val_if_fail (CLUTTER_IS_STAGE_WINDOW (window), 0);
 
   iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window);
-  if (iface->get_pending_swaps == NULL)
+  if (iface->get_update_time == NULL)
     {
       g_assert (!clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS));
       return 0;
     }
 
-  return iface->get_pending_swaps (window);
+  return iface->get_update_time (window);
+}
+
+void
+_clutter_stage_window_clear_update_time (ClutterStageWindow *window)
+{
+  ClutterStageWindowIface *iface;
+
+  g_return_if_fail (CLUTTER_IS_STAGE_WINDOW (window));
+
+  iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window);
+  if (iface->clear_update_time == NULL)
+    {
+      g_assert (!clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS));
+      return;
+    }
+
+  iface->clear_update_time (window);
 }
 
 void
diff --git a/clutter/clutter-stage-window.h b/clutter/clutter-stage-window.h
index 8ff756c..9b38994 100644
--- a/clutter/clutter-stage-window.h
+++ b/clutter/clutter-stage-window.h
@@ -58,7 +58,10 @@ struct _ClutterStageWindowIface
   void              (* get_geometry)            (ClutterStageWindow *stage_window,
                                                  cairo_rectangle_int_t *geometry);
 
-  int               (* get_pending_swaps)       (ClutterStageWindow *stage_window);
+  void              (* schedule_update)         (ClutterStageWindow *stage_window,
+                                                 int                 sync_delay);
+  gint64            (* get_update_time)         (ClutterStageWindow *stage_window);
+  void              (* clear_update_time)       (ClutterStageWindow *stage_window);
 
   void              (* add_redraw_clip)         (ClutterStageWindow    *stage_window,
                                                  cairo_rectangle_int_t *stage_rectangle);
@@ -108,7 +111,10 @@ void              _clutter_stage_window_resize                  (ClutterStageWin
                                                                  gint                height);
 void              _clutter_stage_window_get_geometry            (ClutterStageWindow *window,
                                                                  cairo_rectangle_int_t *geometry);
-int               _clutter_stage_window_get_pending_swaps       (ClutterStageWindow *window);
+void              _clutter_stage_window_schedule_update         (ClutterStageWindow *window,
+                                                                 int                 sync_delay);
+gint64            _clutter_stage_window_get_update_time         (ClutterStageWindow *window);
+void              _clutter_stage_window_clear_update_time       (ClutterStageWindow *window);
 
 void              _clutter_stage_window_add_redraw_clip         (ClutterStageWindow    *window,
                                                                  cairo_rectangle_int_t *stage_clip);
diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c
index e880076..3d4aaac 100644
--- a/clutter/clutter-stage.c
+++ b/clutter/clutter-stage.c
@@ -50,6 +50,7 @@
 #include <cairo.h>
 
 #define CLUTTER_DISABLE_DEPRECATION_WARNINGS
+#define CLUTTER_ENABLE_EXPERIMENTAL_API
 
 #include "clutter-stage.h"
 #include "deprecated/clutter-stage.h"
@@ -145,6 +146,8 @@ struct _ClutterStagePrivate
 
   CoglFramebuffer *active_framebuffer;
 
+  gint sync_delay;
+
   GTimer *fps_timer;
   gint32 timer_n_frames;
 
@@ -930,6 +933,7 @@ _clutter_stage_queue_event (ClutterStage *stage,
     {
       ClutterMasterClock *master_clock = _clutter_master_clock_get_default ();
       _clutter_master_clock_start_running (master_clock);
+      _clutter_stage_schedule_update (stage);
     }
 
   /* if needed, update the state of the input device of the event.
@@ -1250,7 +1254,11 @@ clutter_stage_real_queue_relayout (ClutterActor *self)
   ClutterStagePrivate *priv = stage->priv;
   ClutterActorClass *parent_class;
 
-  priv->relayout_pending = TRUE;
+  if (!priv->relayout_pending)
+    {
+      _clutter_stage_schedule_update (stage);
+      priv->relayout_pending = TRUE;
+    }
 
   /* chain up */
   parent_class = CLUTTER_ACTOR_CLASS (clutter_stage_parent_class);
@@ -2273,6 +2281,7 @@ clutter_stage_init (ClutterStage *self)
   priv->use_fog = FALSE;
   priv->throttle_motion_events = TRUE;
   priv->min_size_changed = FALSE;
+  priv->sync_delay = -1;
 
   /* XXX - we need to keep the invariant that calling
    * clutter_set_motion_event_enabled() before the stage creation
@@ -3584,6 +3593,10 @@ clutter_stage_ensure_redraw (ClutterStage *stage)
   g_return_if_fail (CLUTTER_IS_STAGE (stage));
 
   priv = stage->priv;
+
+  if (!priv->relayout_pending && !priv->redraw_pending)
+    _clutter_stage_schedule_update (stage);
+
   priv->relayout_pending = TRUE;
   priv->redraw_pending = TRUE;
 
@@ -3851,9 +3864,25 @@ clutter_stage_get_minimum_size (ClutterStage *stage,
     *height_p = (guint) height;
 }
 
-/* Returns the number of swap buffers pending completion for the stage */
-int
-_clutter_stage_get_pending_swaps (ClutterStage *stage)
+void
+_clutter_stage_schedule_update (ClutterStage *stage)
+{
+  ClutterStageWindow *stage_window;
+
+  if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
+    return;
+
+  stage_window = _clutter_stage_get_window (stage);
+  if (stage_window == NULL)
+    return;
+
+  return _clutter_stage_window_schedule_update (stage_window,
+                                                stage->priv->sync_delay);
+}
+
+/* Returns the earliest time the stage is ready to update */
+gint64
+_clutter_stage_get_update_time (ClutterStage *stage)
 {
   ClutterStageWindow *stage_window;
 
@@ -3864,7 +3893,17 @@ _clutter_stage_get_pending_swaps (ClutterStage *stage)
   if (stage_window == NULL)
     return 0;
 
-  return _clutter_stage_window_get_pending_swaps (stage_window);
+  return _clutter_stage_window_get_update_time (stage_window);
+}
+
+void
+_clutter_stage_clear_update_time (ClutterStage *stage)
+{
+  ClutterStageWindow *stage_window;
+
+  stage_window = _clutter_stage_get_window (stage);
+  if (stage_window)
+    _clutter_stage_window_clear_update_time (stage_window);
 }
 
 /**
@@ -3998,6 +4037,7 @@ _clutter_stage_queue_actor_redraw (ClutterStage *stage,
 
       CLUTTER_NOTE (PAINT, "First redraw request");
 
+      _clutter_stage_schedule_update (stage);
       priv->redraw_pending = TRUE;
 
       master_clock = _clutter_master_clock_get_default ();
@@ -4487,3 +4527,51 @@ _clutter_stage_update_state (ClutterStage      *stage,
 
   return TRUE;
 }
+
+/**
+ * clutter_stage_set_sync_delay:
+ * @stage: a #ClutterStage
+ * @sync_delay: number of milliseconds after frame presentation to wait
+ *   before painting the next frame. If less than zero, redraw is throttled
+ *   to the refresh rate but not synchronized to it.
+ *
+ * This function enables an alternate behavior where Clutter draws at
+ * a fixed point in time after the frame presentation time (also known
+ * as the VBlank time). This is most useful when the application
+ * wants to show incoming data with predictable latency. (The primary
+ * example of this would be a window system compositor.) By synchronizing
+ * to provide new data before Clutter redraws, an external source of
+ * updates (in the compositor, an application) can get a reliable latency.
+ *
+ * The appropriate value of @sync_delay depends on the complexity of
+ * drawing the stage's scene graph - in general a value of between 0
+ * and 8 ms (up to one-half of a typical 60hz frame rate) is appropriate.
+ * using a larger value will reduce latency but risks skipping a frame if
+ * drawing the stage takes too long.
+ */
+void
+clutter_stage_set_sync_delay (ClutterStage *stage,
+                              gint          sync_delay)
+{
+  g_return_if_fail (CLUTTER_IS_STAGE (stage));
+
+  stage->priv->sync_delay = sync_delay;
+}
+
+/**
+ * clutter_stage_skip_sync_delay:
+ * @stage: a #ClutterStage
+ *
+ * Causes the next frame for the stage to be drawn as quickly as
+ * possible, ignoring any delay that clutter_stage_set_sync_delay()
+ * would normally cause.
+ */
+void
+clutter_stage_skip_sync_delay (ClutterStage *stage)
+{
+  ClutterStageWindow *stage_window;
+
+  stage_window = _clutter_stage_get_window (stage);
+  if (stage_window)
+    _clutter_stage_window_schedule_update (stage_window, -1);
+}
diff --git a/clutter/clutter-stage.h b/clutter/clutter-stage.h
index e3c1d7e..db3dfb1 100644
--- a/clutter/clutter-stage.h
+++ b/clutter/clutter-stage.h
@@ -202,6 +202,12 @@ void            clutter_stage_ensure_current                    (ClutterStage
 void            clutter_stage_ensure_viewport                   (ClutterStage          *stage);
 void            clutter_stage_ensure_redraw                     (ClutterStage          *stage);
 
+#ifdef CLUTTER_ENABLE_EXPERIMENTAL_API
+void            clutter_stage_set_sync_delay                    (ClutterStage          *stage,
+                                                                 gint                   sync_delay);
+void            clutter_stage_skip_sync_delay                   (ClutterStage          *stage);
+#endif
+
 G_END_DECLS
 
 #endif /* __CLUTTER_STAGE_H__ */
diff --git a/clutter/cogl/clutter-stage-cogl.c b/clutter/cogl/clutter-stage-cogl.c
index 151d9da..c7fb5be 100644
--- a/clutter/cogl/clutter-stage-cogl.c
+++ b/clutter/cogl/clutter-stage-cogl.c
@@ -71,28 +71,53 @@ clutter_stage_cogl_unrealize (ClutterStageWindow *stage_window)
 
   if (stage_cogl->onscreen != NULL)
     {
+      cogl_onscreen_remove_frame_callback (stage_cogl->onscreen,
+                                           stage_cogl->frame_closure);
+      stage_cogl->frame_closure = NULL;
+
       cogl_object_unref (stage_cogl->onscreen);
       stage_cogl->onscreen = NULL;
     }
 }
 
 static void
-handle_swap_complete_cb (CoglFramebuffer *framebuffer,
-                         void *user_data)
+frame_cb (CoglOnscreen  *onscreen,
+          CoglFrameEvent event,
+          CoglFrameInfo *info,
+          void          *user_data)
 {
   ClutterStageCogl *stage_cogl = user_data;
 
-  /* Early versions of the swap_event implementation in Mesa
-   * deliver BufferSwapComplete event when not selected for,
-   * so if we get a swap event we aren't expecting, just ignore it.
-   *
-   * https://bugs.freedesktop.org/show_bug.cgi?id=27962
-   *
-   * FIXME: This issue can be hidden inside Cogl so we shouldn't
-   * need to care about this bug here.
-   */
-  if (stage_cogl->pending_swaps > 0)
-    stage_cogl->pending_swaps--;
+  if (event == COGL_FRAME_EVENT_SYNC)
+    {
+      /* Early versions of the swap_event implementation in Mesa
+       * deliver BufferSwapComplete event when not selected for,
+       * so if we get a swap event we aren't expecting, just ignore it.
+       *
+       * https://bugs.freedesktop.org/show_bug.cgi?id=27962
+       *
+       * FIXME: This issue can be hidden inside Cogl so we shouldn't
+       * need to care about this bug here.
+       */
+      if (stage_cogl->pending_swaps > 0)
+        stage_cogl->pending_swaps--;
+    }
+  else if (event == COGL_FRAME_EVENT_COMPLETE)
+    {
+      gint64 presentation_time_cogl = cogl_frame_info_get_presentation_time (info);
+
+      if (presentation_time_cogl != 0)
+        {
+          CoglContext *context = cogl_framebuffer_get_context (COGL_FRAMEBUFFER (onscreen));
+          gint64 current_time_cogl = cogl_get_clock_time (context);
+          gint64 now = g_get_monotonic_time ();
+
+          stage_cogl->last_presentation_time =
+            now + (presentation_time_cogl - current_time_cogl) / 1000;
+        }
+
+      stage_cogl->refresh_rate = cogl_frame_info_get_refresh_rate (info);
+    }
 }
 
 static gboolean
@@ -134,23 +159,77 @@ clutter_stage_cogl_realize (ClutterStageWindow *stage_window)
    * will be ignored, so we need to make sure the stage size is
    * updated to this size. */
 
-  if (cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_BUFFERS_EVENT))
+  stage_cogl->frame_closure =
+          cogl_onscreen_add_frame_callback (stage_cogl->onscreen,
+                                            frame_cb,
+                                            stage_cogl,
+                                            NULL);
+  return TRUE;
+}
+
+static void
+clutter_stage_cogl_schedule_update (ClutterStageWindow *stage_window,
+                                    gint                sync_delay)
+{
+  ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
+  gint64 now;
+  float refresh_rate;
+  gint64 refresh_interval;
+
+  if (stage_cogl->update_time != -1)
+    return;
+
+  now = g_get_monotonic_time ();
+
+  if (sync_delay < 0)
     {
-      stage_cogl->swap_callback_id =
-        cogl_onscreen_add_swap_buffers_callback (stage_cogl->onscreen,
-                                                 handle_swap_complete_cb,
-                                                 stage_cogl);
+      stage_cogl->update_time = now;
+      return;
     }
 
-  return TRUE;
+  /* We only extrapolate presentation times for 150ms  - this is somewhat
+   * arbitrary. The reasons it might not be accurate for larger times are
+   * that the refresh interval might be wrong or the vertical refresh
+   * might be downclocked if nothing is going on onscreen.
+   */
+  if (stage_cogl->last_presentation_time == 0||
+      stage_cogl->last_presentation_time < now - 150000)
+    {
+      stage_cogl->update_time = now;
+      return;
+    }
+
+  refresh_rate = stage_cogl->refresh_rate;
+  if (refresh_rate == 0.0)
+    refresh_rate = 60.0;
+
+  refresh_interval = (gint64) (0.5 + 1000000 / refresh_rate);
+  if (refresh_interval == 0)
+    refresh_interval = 16667; /* 1/60th second */
+
+  stage_cogl->update_time = stage_cogl->last_presentation_time + 1000 * sync_delay;
+
+  while (stage_cogl->update_time < now)
+    stage_cogl->update_time += refresh_interval;
+}
+
+static gint64
+clutter_stage_cogl_get_update_time (ClutterStageWindow *stage_window)
+{
+  ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
+
+  if (stage_cogl->pending_swaps)
+    return -1; /* in the future, indefinite */
+
+  return stage_cogl->update_time;
 }
 
-static int
-clutter_stage_cogl_get_pending_swaps (ClutterStageWindow *stage_window)
+static void
+clutter_stage_cogl_clear_update_time (ClutterStageWindow *stage_window)
 {
   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
 
-  return stage_cogl->pending_swaps;
+  stage_cogl->update_time = -1;
 }
 
 static ClutterActor *
@@ -620,7 +699,9 @@ clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
   iface->resize = clutter_stage_cogl_resize;
   iface->show = clutter_stage_cogl_show;
   iface->hide = clutter_stage_cogl_hide;
-  iface->get_pending_swaps = clutter_stage_cogl_get_pending_swaps;
+  iface->schedule_update = clutter_stage_cogl_schedule_update;
+  iface->get_update_time = clutter_stage_cogl_get_update_time;
+  iface->clear_update_time = clutter_stage_cogl_clear_update_time;
   iface->add_redraw_clip = clutter_stage_cogl_add_redraw_clip;
   iface->has_redraw_clips = clutter_stage_cogl_has_redraw_clips;
   iface->ignoring_redraw_clips = clutter_stage_cogl_ignoring_redraw_clips;
@@ -669,4 +750,8 @@ _clutter_stage_cogl_class_init (ClutterStageCoglClass *klass)
 static void
 _clutter_stage_cogl_init (ClutterStageCogl *stage)
 {
+  stage->last_presentation_time = 0;
+  stage->refresh_rate = 0.0;
+
+  stage->update_time = -1;
 }
diff --git a/clutter/cogl/clutter-stage-cogl.h b/clutter/cogl/clutter-stage-cogl.h
index 28b0ad1..dd34e38 100644
--- a/clutter/cogl/clutter-stage-cogl.h
+++ b/clutter/cogl/clutter-stage-cogl.h
@@ -35,8 +35,12 @@ struct _ClutterStageCogl
 
   CoglOnscreen *onscreen;
 
+  gint64 last_presentation_time;
+  float refresh_rate;
+
+  gint64 update_time;
   gint pending_swaps;
-  unsigned int swap_callback_id;
+  CoglFrameClosure *frame_closure;
 
   /* We only enable clipped redraws after 2 frames, since we've seen
    * a lot of drivers can struggle to get going and may output some


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