From 86bc31bd55fec32ed70abc19f0f2c4514d1c8e29 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 7 May 2009 19:07:21 +0100 Subject: [PATCH] [clock] Rework the master clock The master clock is currently advanced using a frame source driven by the default frame rate. This breaks the sync to vblank because the vblanking rate could be different than 60 Hz -- or it might be completely disabled (e.g. with CLUTTER_VBLANK=none). We should be using the main loop to check if we have timelines playing, and if so queue a redraw on the stages we own. We should also prepare the subsequent frame at the end of the redraw process, so if there are new redraw we will have the scene already in place. This makes Clutter redraw at the maximum frame rate, which is limited by the vblanking frequency. --- clutter/clutter-main.c | 14 +- clutter/clutter-master-clock.c | 247 +++++++++++++++++++++------------ clutter/clutter-private.h | 2 + clutter/clutter-stage.c | 10 +- 4 files changed, 173 insertions(+), 100 deletions(-) diff --git a/clutter/clutter-main.c b/clutter/clutter-main.c index 9c693e59e..59fa41f72 100644 --- a/clutter/clutter-main.c +++ b/clutter/clutter-main.c @@ -182,12 +182,12 @@ void clutter_redraw (ClutterStage *stage) { ClutterMainContext *ctx; + ClutterMasterClock *master_clock; static GTimer *timer = NULL; static guint timer_n_frames = 0; ctx = clutter_context_get_default (); - - CLUTTER_NOTE (PAINT, " Redraw enter for stage:%p", stage); + master_clock = _clutter_master_clock_get_default (); /* Before we can paint, we have to be sure we have the latest layout */ _clutter_stage_maybe_relayout (CLUTTER_ACTOR (stage)); @@ -208,10 +208,15 @@ clutter_redraw (ClutterStage *stage) /* Call through to the actual backend to do the painting down from * the stage. It will likely need to swap buffers, vblank sync etc - * which will be windowing system dependant. + * which will be windowing system dependent */ _clutter_backend_redraw (ctx->backend, stage); + /* prepare for the next frame; if anything queues a redraw as the + * result of a timeline, this will end up redrawing the scene + */ + _clutter_master_clock_advance (master_clock); + /* Complete FPS info */ if (G_UNLIKELY (clutter_get_show_fps ())) { @@ -225,7 +230,6 @@ clutter_redraw (ClutterStage *stage) } } - CLUTTER_NOTE (PAINT, " Redraw leave for stage:%p", stage); CLUTTER_TIMESTAMP (SCHEDULER, "Redraw finish for stage:%p", stage); } @@ -1052,6 +1056,8 @@ clutter_context_get_default (void) ctx->is_initialized = FALSE; ctx->motion_events_per_actor = TRUE; + ctx->master_clock = _clutter_master_clock_get_default (); + #ifdef CLUTTER_ENABLE_DEBUG ctx->timer = g_timer_new (); g_timer_start (ctx->timer); diff --git a/clutter/clutter-master-clock.c b/clutter/clutter-master-clock.c index a417dbbca..8b10238f2 100644 --- a/clutter/clutter-master-clock.c +++ b/clutter/clutter-master-clock.c @@ -43,18 +43,31 @@ #define CLUTTER_IS_MASTER_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_MASTER_CLOCK)) #define CLUTTER_MASTER_CLASS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_MASTER_CLOCK, ClutterMasterClockClass)) +typedef struct _ClutterClockSource ClutterClockSource; typedef struct _ClutterMasterClockClass ClutterMasterClockClass; struct _ClutterMasterClock { GObject parent_instance; + /* the list of timelines handled by the clock */ GSList *timelines; + /* the previous state of the clock, used to compute + * the delta + */ GTimeVal prev_tick; gulong msecs_delta; - gulong tick_id; + /* an idle source, used by the Master Clock to queue + * a redraw on the stage and drive the animations + */ + GSource *source; + + /* a guard, so that we dispatch the last redraw even + * after the last timeline has been completed + */ + guint last_advance : 1; }; struct _ClutterMasterClockClass @@ -62,36 +75,135 @@ struct _ClutterMasterClockClass GObjectClass parent_class; }; -static ClutterMasterClock *default_clock = NULL; +struct _ClutterClockSource +{ + GSource source; -static void on_timeline_start (ClutterTimeline *timeline, + ClutterMasterClock *master_clock; +}; + +static void on_timeline_started (ClutterTimeline *timeline, ClutterMasterClock *master_clock); static void on_timeline_completed (ClutterTimeline *timeline, ClutterMasterClock *master_clock); static void on_timeline_paused (ClutterTimeline *timeline, ClutterMasterClock *master_clock); +static gboolean clutter_clock_prepare (GSource *source, + gint *timeout); +static gboolean clutter_clock_check (GSource *source); +static gboolean clutter_clock_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); + +static ClutterMasterClock *default_clock = NULL; + +static GSourceFuncs clock_funcs = { + clutter_clock_prepare, + clutter_clock_check, + clutter_clock_dispatch, + NULL +}; + G_DEFINE_TYPE (ClutterMasterClock, clutter_master_clock, G_TYPE_OBJECT); +static gboolean +_clutter_master_clock_has_running_timeline (ClutterMasterClock *master_clock) +{ + GSList *l; + + if (master_clock->last_advance) + return TRUE; + + if (master_clock->timelines == NULL) + return FALSE; + + for (l = master_clock->timelines; l != NULL; l = l->next) + { + if (clutter_timeline_is_playing (l->data)) + return TRUE; + } + + return FALSE; +} + +static GSource * +clutter_clock_source_new (ClutterMasterClock *master_clock) +{ + GSource *source = g_source_new (&clock_funcs, sizeof (ClutterClockSource)); + ClutterClockSource *clock_source = (ClutterClockSource *) source; + + clock_source->master_clock = master_clock; + + return source; +} + +static gboolean +clutter_clock_prepare (GSource *source, + gint *timeout) +{ + ClutterClockSource *clock_source = (ClutterClockSource *) source; + ClutterMasterClock *master_clock = clock_source->master_clock; + gboolean retval; + + *timeout = -1; + + retval = _clutter_master_clock_has_running_timeline (master_clock); + + return retval; +} + +static gboolean +clutter_clock_check (GSource *source) +{ + ClutterClockSource *clock_source = (ClutterClockSource *) source; + ClutterMasterClock *master_clock = clock_source->master_clock; + gboolean retval; + + retval = _clutter_master_clock_has_running_timeline (master_clock); + + return retval; +} + +static gboolean +clutter_clock_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + ClutterClockSource *clock_source = (ClutterClockSource *) source; + ClutterMasterClock *master_clock = clock_source->master_clock; + ClutterStageManager *stage_manager = clutter_stage_manager_get_default (); + GSList *stages, *l; + + CLUTTER_NOTE (SCHEDULER, "Master clock [tick]"); + + stages = clutter_stage_manager_list_stages (stage_manager); + + for (l = stages; l != NULL; l = l->next) + clutter_actor_queue_redraw (l->data); + + g_slist_free (stages); + + if (master_clock->last_advance) + { + master_clock->last_advance = FALSE; + master_clock->prev_tick.tv_sec = 0; + } + + return TRUE; +} + static void timeline_weak_ref (gpointer data, GObject *object_pointer) { ClutterMasterClock *master_clock = data; - master_clock->timelines = g_slist_remove (master_clock->timelines, - object_pointer); + master_clock->timelines = + g_slist_remove (master_clock->timelines, object_pointer); if (master_clock->timelines == NULL) - { - if (master_clock->tick_id != 0) - { - g_source_remove (master_clock->tick_id); - master_clock->tick_id = 0; - } - - master_clock->prev_tick.tv_sec = 0; - } + master_clock->prev_tick.tv_sec = 0; } static void @@ -100,12 +212,6 @@ clutter_master_clock_finalize (GObject *gobject) ClutterMasterClock *master_clock = CLUTTER_MASTER_CLOCK (gobject); GSList *l; - if (master_clock->tick_id != 0) - { - g_source_remove (master_clock->tick_id); - master_clock->tick_id = 0; - } - for (l = master_clock->timelines; l != NULL; l = l->next) { ClutterTimeline *timeline = l->data; @@ -115,7 +221,7 @@ clutter_master_clock_finalize (GObject *gobject) master_clock); g_signal_handlers_disconnect_by_func (timeline, - G_CALLBACK (on_timeline_start), + G_CALLBACK (on_timeline_started), master_clock); g_signal_handlers_disconnect_by_func (timeline, G_CALLBACK (on_timeline_completed), @@ -141,6 +247,14 @@ clutter_master_clock_class_init (ClutterMasterClockClass *klass) static void clutter_master_clock_init (ClutterMasterClock *self) { + GSource *source; + + source = clutter_clock_source_new (self); + self->source = source; + + g_source_set_priority (source, CLUTTER_PRIORITY_REDRAW); + g_source_set_can_recurse (source, FALSE); + g_source_attach (source, NULL); } /* @@ -163,80 +277,34 @@ _clutter_master_clock_get_default (void) return default_clock; } -static gboolean -master_clock_tick (gpointer data) -{ - ClutterStageManager *stage_manager = clutter_stage_manager_get_default (); - GSList *stages, *l; - - CLUTTER_NOTE (SCHEDULER, "Master clock [tick]"); - - stages = clutter_stage_manager_list_stages (stage_manager); - - for (l = stages; l != NULL; l = l->next) - clutter_actor_queue_redraw (l->data); - - g_slist_free (stages); - - return TRUE; -} - -static gboolean -has_running_timeline (ClutterMasterClock *master_clock) -{ - GSList *l; - - for (l = master_clock->timelines; l != NULL; l = l->next) - { - if (clutter_timeline_is_playing (l->data)) - return TRUE; - } - - return FALSE; -} - static void -on_timeline_start (ClutterTimeline *timeline, - ClutterMasterClock *master_clock) +on_timeline_started (ClutterTimeline *timeline, + ClutterMasterClock *master_clock) { - if (has_running_timeline (master_clock) && - master_clock->tick_id == 0) - { - master_clock->prev_tick.tv_sec = 0; - - master_clock->tick_id = - clutter_threads_add_frame_source (clutter_get_default_frame_rate (), - master_clock_tick, - master_clock); - } + if (!_clutter_master_clock_has_running_timeline (master_clock)) + master_clock->prev_tick.tv_sec = 0; } static void on_timeline_completed (ClutterTimeline *timeline, ClutterMasterClock *master_clock) { - if (!has_running_timeline (master_clock) && - master_clock->tick_id != 0) - { - master_clock->prev_tick.tv_sec = 0; - - g_source_remove (master_clock->tick_id); - master_clock->tick_id = 0; - } + /* if this is the last timeline we need to turn :last-advance + * on in order to queue the redraw of the scene for the last + * frame; otherwise the ClockSource will fail the prepare and + * check phases and the last frame will not be painted + */ + if (!_clutter_master_clock_has_running_timeline (master_clock)) + master_clock->last_advance = TRUE; } static void on_timeline_paused (ClutterTimeline *timeline, ClutterMasterClock *master_clock) { - if (!has_running_timeline (master_clock) && - master_clock->tick_id != 0) - { - master_clock->prev_tick.tv_sec = 0; - - g_source_remove (master_clock->tick_id); - master_clock->tick_id = 0; - } + /* see the comment in on_timeline_completed */ + if (!_clutter_master_clock_has_running_timeline (master_clock)) + master_clock->last_advance = TRUE; } /* @@ -267,7 +335,7 @@ _clutter_master_clock_add_timeline (ClutterMasterClock *master_clock, master_clock); g_signal_connect (timeline, "started", - G_CALLBACK (on_timeline_start), + G_CALLBACK (on_timeline_started), master_clock); g_signal_connect (timeline, "completed", G_CALLBACK (on_timeline_completed), @@ -301,7 +369,7 @@ _clutter_master_clock_remove_timeline (ClutterMasterClock *master_clock, master_clock); g_signal_handlers_disconnect_by_func (timeline, - G_CALLBACK (on_timeline_start), + G_CALLBACK (on_timeline_started), master_clock); g_signal_handlers_disconnect_by_func (timeline, G_CALLBACK (on_timeline_completed), @@ -311,15 +379,7 @@ _clutter_master_clock_remove_timeline (ClutterMasterClock *master_clock, master_clock); if (master_clock->timelines == NULL) - { - if (master_clock->tick_id != 0) - { - g_source_remove (master_clock->tick_id); - master_clock->tick_id = 0; - } - - master_clock->prev_tick.tv_sec = 0; - } + master_clock->prev_tick.tv_sec = 0; } /* @@ -339,6 +399,9 @@ _clutter_master_clock_advance (ClutterMasterClock *master_clock) g_return_if_fail (CLUTTER_IS_MASTER_CLOCK (master_clock)); + if (master_clock->timelines == NULL) + return; + g_get_current_time (&cur_tick); if (master_clock->prev_tick.tv_sec == 0) diff --git a/clutter/clutter-private.h b/clutter/clutter-private.h index 768ab9dbc..30870ee62 100644 --- a/clutter/clutter-private.h +++ b/clutter/clutter-private.h @@ -44,6 +44,7 @@ #include "clutter-event.h" #include "clutter-feature.h" #include "clutter-id-pool.h" +#include "clutter-master-clock.h" #include "clutter-stage-manager.h" #include "clutter-stage-window.h" #include "clutter-stage.h" @@ -129,6 +130,7 @@ struct _ClutterMainContext guint32 last_event_time; + ClutterMasterClock *master_clock; gulong redraw_count; }; diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c index d5235550c..d34f43f6a 100644 --- a/clutter/clutter-stage.c +++ b/clutter/clutter-stage.c @@ -379,20 +379,22 @@ redraw_update_idle (gpointer user_data) */ _clutter_stage_maybe_relayout (CLUTTER_ACTOR (stage)); + /* redrawing will advance the master clock */ CLUTTER_NOTE (PAINT, "redrawing via idle for stage[%p]", stage); clutter_redraw (stage); + /* reset the guard, so that new redraws are possible */ + priv->update_idle = 0; + if (CLUTTER_CONTEXT ()->redraw_count > 0) { - CLUTTER_NOTE (PAINT, "Queued %lu redraws during the last cycle", + CLUTTER_NOTE (SCHEDULER, "Queued %lu redraws during the last cycle", CLUTTER_CONTEXT ()->redraw_count); CLUTTER_CONTEXT ()->redraw_count = 0; } - priv->update_idle = 0; - - return retval; + return FALSE; } static void