From dcd8d2831455f1ae950cbab073f37d0ad36d2295 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Sat, 6 Jun 2009 22:48:15 -0400 Subject: [PATCH] Limit the frame rate when not syncing to VBLANK clutter-master-clock.c clutter-master-clock.h: When the SYNC_TO_VBLANK feature is not available, wait for 1/frame_rate seconds since the start of the last frame before drawing the next frame. Add _clutter_master_clock_start_running() to abstract the usage of g_main_context_wakeup() clutter-stage.c: Add _clutter_master_clock_start_running() clutter-main.c: Update docs for clutter_set_default_frame_rate() clutter_get_default_frame_rate() to no longer talk about timeline frame rates. test-text-perf.c test-text.c: Set a frame rate of 1000fps so that frame-rate limiting doesn't affect the result. http://bugzilla.openedhand.com/show_bug.cgi?id=1637 Signed-off-by: Emmanuele Bassi --- clutter/clutter-main.c | 9 +- clutter/clutter-master-clock.c | 131 ++++++++++++++++++++++------- clutter/clutter-master-clock.h | 2 + clutter/clutter-stage.c | 18 ++-- tests/micro-bench/test-text-perf.c | 1 + tests/micro-bench/test-text.c | 1 + 6 files changed, 124 insertions(+), 38 deletions(-) diff --git a/clutter/clutter-main.c b/clutter/clutter-main.c index 610900b3c..a11d7666c 100644 --- a/clutter/clutter-main.c +++ b/clutter/clutter-main.c @@ -2293,8 +2293,7 @@ clutter_base_init (void) /** * clutter_get_default_frame_rate: * - * Retrieves the default frame rate used when creating #ClutterTimelines. + * Retrieves the default frame rate. See clutter_set_default_frame_rate(). * * Return value: the default frame rate * @@ -2314,8 +2313,10 @@ clutter_get_default_frame_rate (void) * clutter_set_default_frame_rate: * @frames_per_sec: the new default frame rate * - * Sets the default frame rate to be used when creating #ClutterTimelines + * Sets the default frame rate. This frame rate will be used to limit + * the number of frames drawn if Clutter is not able to synchronize + * with the vertical refresh rate of the display. When synchronization + * is possible, this value is ignored. * * Since: 0.6 */ diff --git a/clutter/clutter-master-clock.c b/clutter/clutter-master-clock.c index 919877051..8e6d569b1 100644 --- a/clutter/clutter-master-clock.c +++ b/clutter/clutter-master-clock.c @@ -53,6 +53,10 @@ struct _ClutterMasterClock /* the list of timelines handled by the clock */ GSList *timelines; + /* the current state of the clock + */ + GTimeVal cur_tick; + /* the previous state of the clock, used to compute * the delta */ @@ -62,6 +66,8 @@ struct _ClutterMasterClock * a redraw on the stage and drive the animations */ GSource *source; + + guint timelines_running : 1; }; struct _ClutterMasterClockClass @@ -122,6 +128,59 @@ master_clock_is_running (ClutterMasterClock *master_clock) return FALSE; } +/* + * master_clock_next_frame_delay: + * @master_clock: a #ClutterMasterClock + * + * Computes the number of delay before we need to draw the next frame. + * + * Return value: -1 if there is no next frame pending, otherwise the + * number of millseconds before the we need to draw the next frame + */ +static gint +master_clock_next_frame_delay (ClutterMasterClock *master_clock) +{ + GTimeVal now; + GTimeVal next; + + if (!master_clock_is_running (master_clock)) + return -1; + + if (clutter_feature_available (CLUTTER_FEATURE_SYNC_TO_VBLANK)) + { + /* When we have sync-to-vblank, we count on that to throttle + * our frame rate, and otherwise draw frames as fast as possible. + */ + return 0; + } + + if (master_clock->prev_tick.tv_sec == 0) + { + /* If we weren't previously running, then draw the next frame + * immediately + */ + return 0; + } + + /* Otherwise, wait at least 1/frame_rate seconds since we last started a frame */ + + g_source_get_current_time (master_clock->source, &now); + + next = master_clock->prev_tick; + g_time_val_add (&next, 1000000 / clutter_get_default_frame_rate ()); + + if (next.tv_sec < now.tv_sec || + (next.tv_sec == now.tv_sec && next.tv_usec < now.tv_usec)) + { + return 0; + } + else + { + return ((next.tv_sec - now.tv_sec) * 1000 + + (next.tv_usec - now.tv_usec) / 1000); + } +} + /* * clutter_clock_source_new: * @master_clock: a #ClutterMasterClock for the source @@ -150,14 +209,13 @@ clutter_clock_prepare (GSource *source, { ClutterClockSource *clock_source = (ClutterClockSource *) source; ClutterMasterClock *master_clock = clock_source->master_clock; - gboolean retval; + int delay; - /* just like an idle source, we are ready if nothing else is */ - *timeout = -1; + delay = master_clock_next_frame_delay (master_clock); - retval = master_clock_is_running (master_clock); + *timeout = delay; - return retval; + return delay == 0; } static gboolean @@ -165,11 +223,11 @@ clutter_clock_check (GSource *source) { ClutterClockSource *clock_source = (ClutterClockSource *) source; ClutterMasterClock *master_clock = clock_source->master_clock; - gboolean retval; + int delay; - retval = master_clock_is_running (master_clock); + delay = master_clock_next_frame_delay (master_clock); - return retval; + return delay == 0; } static gboolean @@ -184,6 +242,10 @@ clutter_clock_dispatch (GSource *source, CLUTTER_NOTE (SCHEDULER, "Master clock [tick]"); + /* Get the time to use for this frame. + */ + g_source_get_current_time (source, &master_clock->cur_tick); + /* We need to protect ourselves against stages being destroyed during * event handling */ @@ -206,6 +268,8 @@ clutter_clock_dispatch (GSource *source, g_slist_foreach (stages, (GFunc)g_object_unref, NULL); g_slist_free (stages); + master_clock->prev_tick = master_clock->cur_tick; + return TRUE; } @@ -283,15 +347,7 @@ _clutter_master_clock_add_timeline (ClutterMasterClock *master_clock, timeline); if (is_first) - { - /* Start timing from scratch */ - master_clock->prev_tick.tv_sec = 0; - - /* If called from a different thread, we need to wake up the - * main loop to start running the timelines - */ - g_main_context_wakeup (NULL); - } + _clutter_master_clock_start_running (master_clock); } /* @@ -308,6 +364,24 @@ _clutter_master_clock_remove_timeline (ClutterMasterClock *master_clock, { master_clock->timelines = g_slist_remove (master_clock->timelines, timeline); + if (master_clock->timelines == NULL) + master_clock->timelines_running = FALSE; +} + +/* + * _clutter_master_clock_start_running: + * @master_clock: a #ClutterMasterClock + * + * Called when we have events or redraws to process; if the clock + * is stopped, does the processing necessary to wake it up again. + */ +void +_clutter_master_clock_start_running (ClutterMasterClock *master_clock) +{ + /* If called from a different thread, we need to wake up the + * main loop to start running the timelines + */ + g_main_context_wakeup (NULL); } /* @@ -321,7 +395,6 @@ _clutter_master_clock_remove_timeline (ClutterMasterClock *master_clock, void _clutter_master_clock_advance (ClutterMasterClock *master_clock) { - GTimeVal cur_tick = { 0, }; gulong msecs; GSList *l; @@ -330,13 +403,18 @@ _clutter_master_clock_advance (ClutterMasterClock *master_clock) if (master_clock->timelines == NULL) return; - g_get_current_time (&cur_tick); + if (!master_clock->timelines_running) + { + /* When we start running, we count the first frame as time 0, + * so we don't need an advance. (And master_clock->prev_tick + * doesn't meaningfully relate to these timelines.) + */ + master_clock->timelines_running = TRUE; + return; + } - if (master_clock->prev_tick.tv_sec == 0) - master_clock->prev_tick = cur_tick; - - msecs = (cur_tick.tv_sec - master_clock->prev_tick.tv_sec) * 1000 - + (cur_tick.tv_usec - master_clock->prev_tick.tv_usec) / 1000; + msecs = (master_clock->cur_tick.tv_sec - master_clock->prev_tick.tv_sec) * 1000 + + (master_clock->cur_tick.tv_usec - master_clock->prev_tick.tv_usec) / 1000; if (msecs == 0) return; @@ -352,9 +430,4 @@ _clutter_master_clock_advance (ClutterMasterClock *master_clock) if (clutter_timeline_is_playing (timeline)) clutter_timeline_advance_delta (timeline, msecs); } - - /* store the previous state so that we can use - * it for the next advancement - */ - master_clock->prev_tick = cur_tick; } diff --git a/clutter/clutter-master-clock.h b/clutter/clutter-master-clock.h index 5a3f7cd32..4bccefd8d 100644 --- a/clutter/clutter-master-clock.h +++ b/clutter/clutter-master-clock.h @@ -42,6 +42,8 @@ void _clutter_master_clock_add_timeline (ClutterMasterClock *m void _clutter_master_clock_remove_timeline (ClutterMasterClock *master_clock, ClutterTimeline *timeline); void _clutter_master_clock_advance (ClutterMasterClock *master_clock); +void _clutter_master_clock_start_running (ClutterMasterClock *master_clock); + G_END_DECLS diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c index 4f1826722..5a6d94c2c 100644 --- a/clutter/clutter-stage.c +++ b/clutter/clutter-stage.c @@ -396,13 +396,22 @@ _clutter_stage_queue_event (ClutterStage *stage, ClutterEvent *event) { ClutterStagePrivate *priv; + gboolean first_event; g_return_if_fail (CLUTTER_IS_STAGE (stage)); priv = stage->priv; + first_event = priv->event_queue->length == 0; + g_queue_push_tail (priv->event_queue, clutter_event_copy (event)); + + if (first_event) + { + ClutterMasterClock *master_clock = _clutter_master_clock_get_default (); + _clutter_master_clock_start_running (master_clock); + } } gboolean @@ -544,13 +553,12 @@ clutter_stage_real_queue_redraw (ClutterActor *actor, if (!priv->redraw_pending) { + ClutterMasterClock *master_clock; + priv->redraw_pending = TRUE; - /* If called from a thread, we need to wake up the main loop - * out of its sleep so the clock source notices that we have - * a redraw pending - */ - g_main_context_wakeup (NULL); + master_clock = _clutter_master_clock_get_default (); + _clutter_master_clock_start_running (master_clock); } else CLUTTER_CONTEXT ()->redraw_count += 1; diff --git a/tests/micro-bench/test-text-perf.c b/tests/micro-bench/test-text-perf.c index cd8d9056e..2be7f27fd 100644 --- a/tests/micro-bench/test-text-perf.c +++ b/tests/micro-bench/test-text-perf.c @@ -77,6 +77,7 @@ main (int argc, char *argv[]) int row, col; g_setenv ("CLUTTER_VBLANK", "none", FALSE); + g_setenv ("CLUTTER_DEFAULT_FPS", "1000", FALSE); clutter_init (&argc, &argv); diff --git a/tests/micro-bench/test-text.c b/tests/micro-bench/test-text.c index 480f63f3e..da86b2b3b 100644 --- a/tests/micro-bench/test-text.c +++ b/tests/micro-bench/test-text.c @@ -48,6 +48,7 @@ main (int argc, char *argv[]) ClutterActor *group; g_setenv ("CLUTTER_VBLANK", "none", FALSE); + g_setenv ("CLUTTER_DEFAULT_FPS", "1000", FALSE); clutter_init (&argc, &argv);