diff --git a/clutter/clutter-main.c b/clutter/clutter-main.c index f87813ee9..610900b3c 100644 --- a/clutter/clutter-main.c +++ b/clutter/clutter-main.c @@ -2004,6 +2004,29 @@ generate_enter_leave_events (ClutterEvent *event) */ void clutter_do_event (ClutterEvent *event) +{ + if (!event->any.stage) + return; + + /* Instead of processing events when received, we queue them up to + * handle per-frame before animations, layout, and drawing. + * + * This gives us the chance to reliably compress motion events + * because we've "looked ahead" and know all motion events that + * will occur before drawing the frame. + */ + _clutter_stage_queue_event (event->any.stage, event); +} + +/** + * _clutter_process_event + * @event: a #ClutterEvent. + * + * Does the actual work of processing an event that was queued earlier + * out of clutter_do_event(). + */ +void +_clutter_process_event (ClutterEvent *event) { /* FIXME: This should probably be clutter_cook_event() - it would * take a raw event from the backend and 'cook' it so its more tasty. @@ -2013,8 +2036,6 @@ clutter_do_event (ClutterEvent *event) ClutterBackend *backend; ClutterActor *stage; ClutterInputDevice *device = NULL; - static gint32 motion_last_time = 0L; - gint32 local_motion_time; context = clutter_context_get_default (); backend = context->backend; @@ -2091,52 +2112,6 @@ clutter_do_event (ClutterEvent *event) case CLUTTER_MOTION: device = event->motion.device; - if (device) - local_motion_time = device->motion_last_time; - else - local_motion_time = motion_last_time; - - /* avoid rate throttling for synthetic motion events or if - * the per-actor events are disabled - */ - if (!(event->any.flags & CLUTTER_EVENT_FLAG_SYNTHETIC) || - !context->motion_events_per_actor) - { - gint32 frame_rate, delta; - - /* avoid issuing too many motion events, which leads to many - * redraws in pick mode (performance penalty) - */ - frame_rate = clutter_get_motion_events_frequency (); - delta = 1000 / frame_rate; - - CLUTTER_NOTE (EVENT, - "skip motion event: %s (last:%d, delta:%d, time:%d)", - (event->any.time < (local_motion_time + delta) ? "yes" : "no"), - local_motion_time, - delta, - event->any.time); - - /* we need to guard against roll-overs and the - * case where the time is rolled backwards and - * the backend is not ensuring a monotonic clock - * for the events. - * - * see: - * http://bugzilla.openedhand.com/show_bug.cgi?id=1130 - */ - if (event->any.time >= local_motion_time && - event->any.time < (local_motion_time + delta)) - break; - else - local_motion_time = event->any.time; - } - - if (device) - device->motion_last_time = local_motion_time; - else - motion_last_time = local_motion_time; - /* Only stage gets motion events if clutter_set_motion_events is TRUE, * and the event is not a synthetic event with source set. */ @@ -2321,9 +2296,6 @@ clutter_base_init (void) * Retrieves the default frame rate used when creating #ClutterTimelines. * - * This value is also used to compute the default frequency of motion - * events. - * * Return value: the default frame rate * * Since: 0.6 @@ -2621,61 +2593,6 @@ clutter_get_keyboard_grab (void) return context->keyboard_grab_actor; } -/** - * clutter_get_motion_events_frequency: - * - * Retrieves the number of motion events per second that are delivered - * to the stage. - * - * See clutter_set_motion_events_frequency(). - * - * Return value: the number of motion events per second - * - * Since: 0.6 - */ -guint -clutter_get_motion_events_frequency (void) -{ - ClutterMainContext *context = clutter_context_get_default (); - - if (G_LIKELY (context->motion_frequency == 0)) - { - guint frequency; - - frequency = clutter_default_fps / 4; - frequency = CLAMP (frequency, 20, 45); - - return frequency; - } - else - return context->motion_frequency; -} - -/** - * clutter_set_motion_events_frequency: - * @frequency: the number of motion events per second, or 0 for the - * default value - * - * Sets the motion events frequency. Setting this to a non-zero value - * will override the default setting, so it should be rarely used. - * - * Motion events are delivered from the default backend to the stage - * and are used to generate the enter/leave events pair. This might lead - * to a performance penalty due to the way the actors are identified. - * Using this function is possible to reduce the frequency of the motion - * events delivery to the stage. - * - * Since: 0.6 - */ -void -clutter_set_motion_events_frequency (guint frequency) -{ - ClutterMainContext *context = clutter_context_get_default (); - - /* never allow the motion events to exceed the default frame rate */ - context->motion_frequency = CLAMP (frequency, 1, clutter_default_fps); -} - /** * clutter_clear_glyph_cache: * diff --git a/clutter/clutter-main.h b/clutter/clutter-main.h index d6f1099d0..194faec6f 100644 --- a/clutter/clutter-main.h +++ b/clutter/clutter-main.h @@ -144,8 +144,6 @@ void clutter_threads_remove_repaint_func (guint handle_id void clutter_set_motion_events_enabled (gboolean enable); gboolean clutter_get_motion_events_enabled (void); -void clutter_set_motion_events_frequency (guint frequency); -guint clutter_get_motion_events_frequency (void); void clutter_set_default_frame_rate (guint frames_per_sec); guint clutter_get_default_frame_rate (void); diff --git a/clutter/clutter-master-clock.c b/clutter/clutter-master-clock.c index 9314e9118..919877051 100644 --- a/clutter/clutter-master-clock.c +++ b/clutter/clutter-master-clock.c @@ -115,7 +115,8 @@ master_clock_is_running (ClutterMasterClock *master_clock) stages = clutter_stage_manager_peek_stages (stage_manager); for (l = stages; l; l = l->next) - if (_clutter_stage_needs_update (l->data)) + if (_clutter_stage_has_queued_events (l->data) || + _clutter_stage_needs_update (l->data)) return TRUE; return FALSE; @@ -179,11 +180,20 @@ 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 (); - const GSList *stages, *l; + GSList *stages, *l; CLUTTER_NOTE (SCHEDULER, "Master clock [tick]"); - stages = clutter_stage_manager_peek_stages (stage_manager); + /* 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); + + /* Process queued events + */ + for (l = stages; l != NULL; l = l->next) + _clutter_stage_process_queued_events (l->data); _clutter_master_clock_advance (master_clock); @@ -193,6 +203,9 @@ clutter_clock_dispatch (GSource *source, for (l = stages; l != NULL; l = l->next) _clutter_stage_do_update (l->data); + g_slist_foreach (stages, (GFunc)g_object_unref, NULL); + g_slist_free (stages); + return TRUE; } diff --git a/clutter/clutter-private.h b/clutter/clutter-private.h index e367f0d73..340cc75c6 100644 --- a/clutter/clutter-private.h +++ b/clutter/clutter-private.h @@ -70,7 +70,6 @@ typedef enum { struct _ClutterInputDevice { gint id; - gint32 motion_last_time; ClutterActor *pointer_grab_actor; ClutterActor *motion_last_actor; @@ -99,7 +98,6 @@ struct _ClutterMainContext ClutterPickMode pick_mode; /* Indicates pick render mode */ - guint motion_frequency; /* Motion events per second */ gint num_reactives; /* Num of reactive actors */ ClutterIDPool *id_pool; /* mapping between reused integer ids @@ -178,6 +176,11 @@ void _clutter_stage_maybe_relayout (ClutterActor *sta gboolean _clutter_stage_needs_update (ClutterStage *stage); void _clutter_stage_do_update (ClutterStage *stage); +void _clutter_stage_queue_event (ClutterStage *stage, + ClutterEvent *event); +gboolean _clutter_stage_has_queued_events (ClutterStage *stage); +void _clutter_stage_process_queued_events (ClutterStage *stage); + /* vfuncs implemented by backend */ GType _clutter_backend_impl_get_type (void); @@ -207,6 +210,9 @@ gfloat _clutter_backend_get_units_per_em (ClutterBackend *backend void _clutter_feature_init (void); +/* Reinjecting queued events for processing */ +void _clutter_process_event (ClutterEvent *event); + /* Picking code */ ClutterActor *_clutter_do_pick (ClutterStage *stage, gint x, diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c index dcb2e8ad6..4f1826722 100644 --- a/clutter/clutter-stage.c +++ b/clutter/clutter-stage.c @@ -87,6 +87,8 @@ struct _ClutterStagePrivate gchar *title; ClutterActor *key_focused_actor; + GQueue *event_queue; + guint redraw_pending : 1; guint is_fullscreen : 1; guint is_offscreen : 1; @@ -389,6 +391,86 @@ clutter_stage_real_fullscreen (ClutterStage *stage) CLUTTER_ALLOCATION_NONE); } +void +_clutter_stage_queue_event (ClutterStage *stage, + ClutterEvent *event) +{ + ClutterStagePrivate *priv; + + g_return_if_fail (CLUTTER_IS_STAGE (stage)); + + priv = stage->priv; + + g_queue_push_tail (priv->event_queue, + clutter_event_copy (event)); +} + +gboolean +_clutter_stage_has_queued_events (ClutterStage *stage) +{ + ClutterStagePrivate *priv; + + g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE); + + priv = stage->priv; + + return priv->event_queue->length > 0; +} + +void +_clutter_stage_process_queued_events (ClutterStage *stage) +{ + ClutterStagePrivate *priv; + GList *events, *l;; + + g_return_if_fail (CLUTTER_IS_STAGE (stage)); + + priv = stage->priv; + + if (priv->event_queue->length == 0) + return; + + /* In case the stage gets destroyed during event processing */ + g_object_ref (stage); + + /* Steal events before starting processing to avoid reentrancy + * issues */ + events = priv->event_queue->head; + priv->event_queue->head = NULL; + priv->event_queue->tail = NULL; + priv->event_queue->length = 0; + + for (l = events; l; l = l->next) + { + ClutterEvent *event; + ClutterEvent *next_event; + + event = l->data; + next_event = l->next ? l->next->data : NULL; + + /* Skip consecutive motion events */ + if (next_event && + event->type == CLUTTER_MOTION && + (next_event->type == CLUTTER_MOTION || + next_event->type == CLUTTER_LEAVE)) + { + CLUTTER_NOTE (EVENT, + "Omitting motion event at %.2f, %.2f", + event->motion.x, event->motion.y); + goto next_event; + } + + _clutter_process_event (event); + + next_event: + clutter_event_free (event); + } + + g_list_free (events); + + g_object_unref (stage); +} + /** * _clutter_stage_needs_update: * @stage: A #ClutterStage @@ -635,9 +717,13 @@ static void clutter_stage_finalize (GObject *object) { ClutterStage *stage = CLUTTER_STAGE (object); + ClutterStagePrivate *priv = stage->priv; + + g_queue_foreach (priv->event_queue, (GFunc)clutter_event_free, NULL); + g_queue_free (priv->event_queue); g_free (stage->priv->title); - + G_OBJECT_CLASS (clutter_stage_parent_class)->finalize (object); } @@ -908,6 +994,8 @@ clutter_stage_init (ClutterStage *self) /* make sure that the implementation is considered a top level */ CLUTTER_SET_PRIVATE_FLAGS (priv->impl, CLUTTER_ACTOR_IS_TOPLEVEL); + priv->event_queue = g_queue_new (); + priv->is_offscreen = FALSE; priv->is_fullscreen = FALSE; priv->is_user_resizable = FALSE;