From b248941af5f206c4b99872a759fbdaea3673e7b7 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Thu, 8 Nov 2012 12:42:24 -0500 Subject: [PATCH] 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 | 156 ++++++++++++++++++++++-------- clutter/clutter-stage-private.h | 4 +- clutter/clutter-stage-window.c | 43 +++++++- clutter/clutter-stage-window.h | 10 +- clutter/clutter-stage.c | 105 +++++++++++++++++++- clutter/clutter-stage.h | 8 ++ clutter/cogl/clutter-stage-cogl.c | 135 +++++++++++++++++++++----- clutter/cogl/clutter-stage-cogl.h | 6 +- 8 files changed, 388 insertions(+), 79 deletions(-) diff --git a/clutter/clutter-master-clock.c b/clutter/clutter-master-clock.c index 32655ee75..104ea0c81 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; @@ -498,10 +567,10 @@ clutter_clock_dispatch (GSource *source, #endif /* We need to protect ourselves against stages being destroyed during - * event handling + * event handling - master_clock_list_ready_stages() returns a + * list of referenced that we'll unref afterwards. */ - 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 +593,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 +688,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 89d1dbd59..9ccba3f5b 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 2790a32e0..c6c9f1ad8 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 8ff756c61..9b38994cc 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 e880076b4..70bfd5106 100644 --- a/clutter/clutter-stage.c +++ b/clutter/clutter-stage.c @@ -50,6 +50,7 @@ #include #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,58 @@ _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, restores the + * default behavior where 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. + * + * Since: 1.14 + * Stability: unstable + */ +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. + * + * Since: 1.14 + * Stability: unstable + */ +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 e3c1d7e9c..af6b091df 100644 --- a/clutter/clutter-stage.h +++ b/clutter/clutter-stage.h @@ -202,6 +202,14 @@ 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 +CLUTTER_AVAILABLE_IN_1_14 +void clutter_stage_set_sync_delay (ClutterStage *stage, + gint sync_delay); +CLUTTER_AVAILABLE_IN_1_14 +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 151d9da2b..c7fb5be2b 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->swap_callback_id = - cogl_onscreen_add_swap_buffers_callback (stage_cogl->onscreen, - handle_swap_complete_cb, - stage_cogl); - } - + stage_cogl->frame_closure = + cogl_onscreen_add_frame_callback (stage_cogl->onscreen, + frame_cb, + stage_cogl, + NULL); 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); + 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->update_time = now; + return; + } + + /* 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); - return stage_cogl->pending_swaps; + 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); + + 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 28b0ad1f5..dd34e387c 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