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 <ebassi@linux.intel.com>
This commit is contained in:
Owen W. Taylor 2009-06-06 22:48:15 -04:00 committed by Emmanuele Bassi
parent 64bb2e694f
commit dcd8d28314
6 changed files with 124 additions and 38 deletions

View File

@ -2293,8 +2293,7 @@ clutter_base_init (void)
/** /**
* clutter_get_default_frame_rate: * clutter_get_default_frame_rate:
* *
* Retrieves the default frame rate used when creating #ClutterTimeline<!-- * Retrieves the default frame rate. See clutter_set_default_frame_rate().
* -->s.
* *
* Return value: the default frame rate * Return value: the default frame rate
* *
@ -2314,8 +2313,10 @@ clutter_get_default_frame_rate (void)
* clutter_set_default_frame_rate: * clutter_set_default_frame_rate:
* @frames_per_sec: the new default frame rate * @frames_per_sec: the new default frame rate
* *
* Sets the default frame rate to be used when creating #ClutterTimeline<!-- * Sets the default frame rate. This frame rate will be used to limit
* -->s * 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 * Since: 0.6
*/ */

View File

@ -53,6 +53,10 @@ struct _ClutterMasterClock
/* the list of timelines handled by the clock */ /* the list of timelines handled by the clock */
GSList *timelines; GSList *timelines;
/* the current state of the clock
*/
GTimeVal cur_tick;
/* the previous state of the clock, used to compute /* the previous state of the clock, used to compute
* the delta * the delta
*/ */
@ -62,6 +66,8 @@ struct _ClutterMasterClock
* a redraw on the stage and drive the animations * a redraw on the stage and drive the animations
*/ */
GSource *source; GSource *source;
guint timelines_running : 1;
}; };
struct _ClutterMasterClockClass struct _ClutterMasterClockClass
@ -122,6 +128,59 @@ master_clock_is_running (ClutterMasterClock *master_clock)
return FALSE; 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: * clutter_clock_source_new:
* @master_clock: a #ClutterMasterClock for the source * @master_clock: a #ClutterMasterClock for the source
@ -150,14 +209,13 @@ clutter_clock_prepare (GSource *source,
{ {
ClutterClockSource *clock_source = (ClutterClockSource *) source; ClutterClockSource *clock_source = (ClutterClockSource *) source;
ClutterMasterClock *master_clock = clock_source->master_clock; ClutterMasterClock *master_clock = clock_source->master_clock;
gboolean retval; int delay;
/* just like an idle source, we are ready if nothing else is */ delay = master_clock_next_frame_delay (master_clock);
*timeout = -1;
retval = master_clock_is_running (master_clock); *timeout = delay;
return retval; return delay == 0;
} }
static gboolean static gboolean
@ -165,11 +223,11 @@ clutter_clock_check (GSource *source)
{ {
ClutterClockSource *clock_source = (ClutterClockSource *) source; ClutterClockSource *clock_source = (ClutterClockSource *) source;
ClutterMasterClock *master_clock = clock_source->master_clock; 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 static gboolean
@ -184,6 +242,10 @@ clutter_clock_dispatch (GSource *source,
CLUTTER_NOTE (SCHEDULER, "Master clock [tick]"); 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 /* We need to protect ourselves against stages being destroyed during
* event handling * event handling
*/ */
@ -206,6 +268,8 @@ clutter_clock_dispatch (GSource *source,
g_slist_foreach (stages, (GFunc)g_object_unref, NULL); g_slist_foreach (stages, (GFunc)g_object_unref, NULL);
g_slist_free (stages); g_slist_free (stages);
master_clock->prev_tick = master_clock->cur_tick;
return TRUE; return TRUE;
} }
@ -283,15 +347,7 @@ _clutter_master_clock_add_timeline (ClutterMasterClock *master_clock,
timeline); timeline);
if (is_first) if (is_first)
{ _clutter_master_clock_start_running (master_clock);
/* 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);
}
} }
/* /*
@ -308,6 +364,24 @@ _clutter_master_clock_remove_timeline (ClutterMasterClock *master_clock,
{ {
master_clock->timelines = g_slist_remove (master_clock->timelines, master_clock->timelines = g_slist_remove (master_clock->timelines,
timeline); 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 void
_clutter_master_clock_advance (ClutterMasterClock *master_clock) _clutter_master_clock_advance (ClutterMasterClock *master_clock)
{ {
GTimeVal cur_tick = { 0, };
gulong msecs; gulong msecs;
GSList *l; GSList *l;
@ -330,13 +403,18 @@ _clutter_master_clock_advance (ClutterMasterClock *master_clock)
if (master_clock->timelines == NULL) if (master_clock->timelines == NULL)
return; 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) msecs = (master_clock->cur_tick.tv_sec - master_clock->prev_tick.tv_sec) * 1000
master_clock->prev_tick = cur_tick; + (master_clock->cur_tick.tv_usec - master_clock->prev_tick.tv_usec) / 1000;
msecs = (cur_tick.tv_sec - master_clock->prev_tick.tv_sec) * 1000
+ (cur_tick.tv_usec - master_clock->prev_tick.tv_usec) / 1000;
if (msecs == 0) if (msecs == 0)
return; return;
@ -352,9 +430,4 @@ _clutter_master_clock_advance (ClutterMasterClock *master_clock)
if (clutter_timeline_is_playing (timeline)) if (clutter_timeline_is_playing (timeline))
clutter_timeline_advance_delta (timeline, msecs); 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;
} }

View File

@ -42,6 +42,8 @@ void _clutter_master_clock_add_timeline (ClutterMasterClock *m
void _clutter_master_clock_remove_timeline (ClutterMasterClock *master_clock, void _clutter_master_clock_remove_timeline (ClutterMasterClock *master_clock,
ClutterTimeline *timeline); ClutterTimeline *timeline);
void _clutter_master_clock_advance (ClutterMasterClock *master_clock); void _clutter_master_clock_advance (ClutterMasterClock *master_clock);
void _clutter_master_clock_start_running (ClutterMasterClock *master_clock);
G_END_DECLS G_END_DECLS

View File

@ -396,13 +396,22 @@ _clutter_stage_queue_event (ClutterStage *stage,
ClutterEvent *event) ClutterEvent *event)
{ {
ClutterStagePrivate *priv; ClutterStagePrivate *priv;
gboolean first_event;
g_return_if_fail (CLUTTER_IS_STAGE (stage)); g_return_if_fail (CLUTTER_IS_STAGE (stage));
priv = stage->priv; priv = stage->priv;
first_event = priv->event_queue->length == 0;
g_queue_push_tail (priv->event_queue, g_queue_push_tail (priv->event_queue,
clutter_event_copy (event)); clutter_event_copy (event));
if (first_event)
{
ClutterMasterClock *master_clock = _clutter_master_clock_get_default ();
_clutter_master_clock_start_running (master_clock);
}
} }
gboolean gboolean
@ -544,13 +553,12 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
if (!priv->redraw_pending) if (!priv->redraw_pending)
{ {
ClutterMasterClock *master_clock;
priv->redraw_pending = TRUE; priv->redraw_pending = TRUE;
/* If called from a thread, we need to wake up the main loop master_clock = _clutter_master_clock_get_default ();
* out of its sleep so the clock source notices that we have _clutter_master_clock_start_running (master_clock);
* a redraw pending
*/
g_main_context_wakeup (NULL);
} }
else else
CLUTTER_CONTEXT ()->redraw_count += 1; CLUTTER_CONTEXT ()->redraw_count += 1;

View File

@ -77,6 +77,7 @@ main (int argc, char *argv[])
int row, col; int row, col;
g_setenv ("CLUTTER_VBLANK", "none", FALSE); g_setenv ("CLUTTER_VBLANK", "none", FALSE);
g_setenv ("CLUTTER_DEFAULT_FPS", "1000", FALSE);
clutter_init (&argc, &argv); clutter_init (&argc, &argv);

View File

@ -48,6 +48,7 @@ main (int argc, char *argv[])
ClutterActor *group; ClutterActor *group;
g_setenv ("CLUTTER_VBLANK", "none", FALSE); g_setenv ("CLUTTER_VBLANK", "none", FALSE);
g_setenv ("CLUTTER_DEFAULT_FPS", "1000", FALSE);
clutter_init (&argc, &argv); clutter_init (&argc, &argv);