[clutter/wip/frame-synchronization: 1/2] Add clutter_stage_set_sync_delay()
- From: Owen Taylor <otaylor src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [clutter/wip/frame-synchronization: 1/2] Add clutter_stage_set_sync_delay()
- Date: Mon, 7 Jan 2013 17:31:49 +0000 (UTC)
commit 9eb083cdc295c66cb1cdcdff912ff3a25710c7ab
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.
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 | 81 +++++++++++++++++++-
clutter/cogl/clutter-stage-cogl.h | 1 +
8 files changed, 340 insertions(+), 56 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 3ba3c99..ffa30a1 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 041a255..27ecee0 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);
@@ -103,7 +106,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 7a82543..10ee07e 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);
@@ -2247,6 +2255,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
@@ -3558,6 +3567,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;
@@ -3825,9 +3838,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;
@@ -3838,7 +3867,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);
}
/**
@@ -3972,6 +4011,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 ();
@@ -4461,3 +4501,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 68b98b1..b8f091f 100644
--- a/clutter/cogl/clutter-stage-cogl.c
+++ b/clutter/cogl/clutter-stage-cogl.c
@@ -145,12 +145,82 @@ clutter_stage_cogl_realize (ClutterStageWindow *stage_window)
return TRUE;
}
-static int
-clutter_stage_cogl_get_pending_swaps (ClutterStageWindow *stage_window)
+static void
+clutter_stage_cogl_schedule_update (ClutterStageWindow *stage_window,
+ gint sync_delay)
+{
+ ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
+ int64_t frame_counter, frame_history_start, i;
+ int64_t presentation_time = 0;
+ int64_t refresh_interval;
+ int64_t now;
+
+ if (stage_cogl->update_time != -1)
+ return;
+
+ now = g_get_monotonic_time ();
+
+ if (sync_delay < 0)
+ {
+ stage_cogl->update_time = now;
+ return;
+ }
+
+ frame_counter = cogl_onscreen_get_frame_counter (stage_cogl->onscreen);
+ frame_history_start = cogl_onscreen_get_frame_history_start (stage_cogl->onscreen);
+
+ for (i = frame_counter; i >= frame_history_start; i--)
+ {
+ CoglFrameTimings *timings;
+
+ timings = cogl_onscreen_get_frame_timings (stage_cogl->onscreen, i);
+ if (timings)
+ {
+ presentation_time = cogl_frame_timings_get_presentation_time (timings);
+ refresh_interval = cogl_frame_timings_get_refresh_interval (timings);
+ }
+
+ if (presentation_time != 0)
+ break;
+ }
+
+ /* 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 (presentation_time == 0 || presentation_time < now - 150000)
+ {
+ stage_cogl->update_time = now;
+ return;
+ }
+
+ if (refresh_interval == 0)
+ refresh_interval = 16667; /* 1/60th second */
+
+ stage_cogl->update_time = 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 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 *
@@ -523,7 +593,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;
@@ -570,4 +642,5 @@ _clutter_stage_cogl_class_init (ClutterStageCoglClass *klass)
static void
_clutter_stage_cogl_init (ClutterStageCogl *stage)
{
+ stage->update_time = -1;
}
diff --git a/clutter/cogl/clutter-stage-cogl.h b/clutter/cogl/clutter-stage-cogl.h
index e140824..7fc171b 100644
--- a/clutter/cogl/clutter-stage-cogl.h
+++ b/clutter/cogl/clutter-stage-cogl.h
@@ -35,6 +35,7 @@ struct _ClutterStageCogl
CoglOnscreen *onscreen;
+ gint64 update_time;
gint pending_swaps;
unsigned int swap_callback_id;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]