diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c index 138e9cfe1..5395faf03 100644 --- a/clutter/clutter-actor.c +++ b/clutter/clutter-actor.c @@ -476,6 +476,11 @@ struct _ClutterActorPrivate ClutterPaintVolume paint_volume; + /* This is used when painting effects to implement the + clutter_actor_continue_paint() function. It points to the node in + the list of effects that is next in the chain */ + const GList *next_effect_to_paint; + /* NB: This volume isn't relative to this actor, it is in eye * coordinates so that it can remain valid after the actor changes. */ @@ -2342,60 +2347,6 @@ _clutter_actor_apply_modelview_transform (ClutterActor *self, CLUTTER_ACTOR_GET_CLASS (self)->apply_transform (self, matrix); } -static gboolean -_clutter_actor_effects_pre_paint (ClutterActor *self) -{ - ClutterActorPrivate *priv = self->priv; - const GList *effects, *l; - gboolean was_pre_painted = FALSE; - - priv->current_effect = NULL; - - effects = _clutter_meta_group_peek_metas (priv->effects); - for (l = effects; l != NULL; l = l->next) - { - ClutterEffect *effect = l->data; - ClutterActorMeta *meta = l->data; - - if (!clutter_actor_meta_get_enabled (meta)) - continue; - - priv->current_effect = l->data; - - was_pre_painted |= _clutter_effect_pre_paint (effect); - } - - priv->current_effect = NULL; - - return was_pre_painted; -} - -static void -_clutter_actor_effects_post_paint (ClutterActor *self) -{ - ClutterActorPrivate *priv = self->priv; - const GList *effects, *l; - - priv->current_effect = NULL; - - /* we walk the list backwards, to unwind the post-paint order */ - effects = _clutter_meta_group_peek_metas (priv->effects); - for (l = g_list_last ((GList *) effects); l != NULL; l = l->prev) - { - ClutterEffect *effect = l->data; - ClutterActorMeta *meta = l->data; - - if (!clutter_actor_meta_get_enabled (meta)) - continue; - - priv->current_effect = l->data; - - _clutter_effect_post_paint (effect); - } - - priv->current_effect = NULL; -} - /* Recursively applies the transforms associated with this actor and * its ancestors to the given matrix. Use NULL if you want this * to go all the way down to the stage. @@ -2780,8 +2731,6 @@ clutter_actor_paint (ClutterActor *self) if (pick_mode == CLUTTER_PICK_NONE) { - gboolean effect_painted = FALSE; - CLUTTER_COUNTER_INC (_clutter_uprof_context, actor_paint_counter); /* We save the current paint volume so that the next time the @@ -2822,17 +2771,20 @@ clutter_actor_paint (ClutterActor *self) goto done; } - if (priv->effects != NULL) - effect_painted = _clutter_actor_effects_pre_paint (self); - else if (actor_has_shader_data (self)) - clutter_actor_shader_pre_paint (self, FALSE); + if (priv->effects == NULL) + { + if (actor_has_shader_data (self)) + clutter_actor_shader_pre_paint (self, FALSE); + priv->next_effect_to_paint = NULL; + } + else + priv->next_effect_to_paint = + _clutter_meta_group_peek_metas (priv->effects); - priv->propagated_one_redraw = FALSE; - g_signal_emit (self, actor_signals[PAINT], 0); + clutter_actor_continue_paint (self); - if (effect_painted) - _clutter_actor_effects_post_paint (self); - else if (actor_has_shader_data (self)) + if (priv->effects == NULL && + actor_has_shader_data (self)) clutter_actor_shader_post_paint (self); if (G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_VOLUMES)) @@ -2863,6 +2815,64 @@ done: CLUTTER_UNSET_PRIVATE_FLAGS (self, CLUTTER_IN_PAINT); } +/** + * clutter_actor_continue_paint: + * @self: A #ClutterActor + * + * Run the next stage of the paint sequence. This function should only + * be called within the implementation of the ‘run’ virtual of a + * #ClutterEffect. It will cause the run method of the next effect to + * be applied, or it will paint the actual actor if the current effect + * is the last effect in the chain. + * + * Since: 1.8 + */ +void +clutter_actor_continue_paint (ClutterActor *self) +{ + ClutterActorPrivate *priv; + + g_return_if_fail (CLUTTER_IS_ACTOR (self)); + /* This should only be called from with in the ‘run’ implementation + of a ClutterEffect */ + g_return_if_fail (CLUTTER_ACTOR_IN_PAINT (self)); + + priv = self->priv; + + /* Skip any effects that are disabled */ + while (priv->next_effect_to_paint && + !clutter_actor_meta_get_enabled (priv->next_effect_to_paint->data)) + priv->next_effect_to_paint = priv->next_effect_to_paint->next; + + /* If this has come from the last effect then we'll just paint the + actual actor */ + if (priv->next_effect_to_paint == NULL) + { + priv->propagated_one_redraw = FALSE; + + g_signal_emit (self, actor_signals[PAINT], 0); + } + else + { + ClutterActorMeta *old_current_effect; + ClutterEffectRunFlags run_flags = 0; + + /* Cache the current effect so that we can put it back before + returning */ + old_current_effect = priv->current_effect; + + priv->current_effect = priv->next_effect_to_paint->data; + priv->next_effect_to_paint = priv->next_effect_to_paint->next; + + if (priv->propagated_one_redraw) + run_flags |= CLUTTER_EFFECT_RUN_ACTOR_DIRTY; + + _clutter_effect_run (CLUTTER_EFFECT (priv->current_effect), run_flags); + + priv->current_effect = old_current_effect; + } +} + /* internal helper function set the rotation angle without affecting the center point */ diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h index adc013aa8..32096175f 100644 --- a/clutter/clutter-actor.h +++ b/clutter/clutter-actor.h @@ -314,6 +314,7 @@ void clutter_actor_unrealize (ClutterActor void clutter_actor_map (ClutterActor *self); void clutter_actor_unmap (ClutterActor *self); void clutter_actor_paint (ClutterActor *self); +void clutter_actor_continue_paint (ClutterActor *self); void clutter_actor_queue_redraw (ClutterActor *self); void clutter_actor_queue_relayout (ClutterActor *self); diff --git a/clutter/clutter-effect-private.h b/clutter/clutter-effect-private.h index 8b0153cef..94a69ff28 100644 --- a/clutter/clutter-effect-private.h +++ b/clutter/clutter-effect-private.h @@ -9,6 +9,8 @@ gboolean _clutter_effect_pre_paint (ClutterEffect *eff void _clutter_effect_post_paint (ClutterEffect *effect); gboolean _clutter_effect_get_paint_volume (ClutterEffect *effect, ClutterPaintVolume *volume); +void _clutter_effect_run (ClutterEffect *effect, + ClutterEffectRunFlags flags); G_END_DECLS diff --git a/clutter/clutter-effect.c b/clutter/clutter-effect.c index cf8775d02..95550d0c4 100644 --- a/clutter/clutter-effect.c +++ b/clutter/clutter-effect.c @@ -38,34 +38,74 @@ * * * Implementing a ClutterEffect - * Creating a sub-class of #ClutterEffect requires the implementation - * of two virtual functions: + * + * Creating a sub-class of #ClutterEffect requires overriding the + * ‘run’ method. The implementation of the function should look + * something like this: + * + * + * void effect_run (ClutterEffect *effect, ClutterEffectRunFlags flags) + * { + * /* Set up initialisation of the paint such as binding a + * CoglOffscreen or other operations */ + * + * /* Chain to the next item in the paint sequence. This will either call + * ‘run’ on the next effect or just paint the actor if this is + * the last effect. */ + * ClutterActor *actor = + * clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); + * clutter_actor_continue_paint (actor); + * + * /* perform any cleanup of state, such as popping the + * CoglOffscreen */ + * } + * + * + * The effect can optionally avoid calling + * clutter_actor_continue_paint() to skip any further stages of + * the paint sequence. This is useful for example if the effect + * contains a cached image of the actor. In that case it can + * optimise painting by avoiding the actor paint and instead + * painting the cached image. The %CLUTTER_EFFECT_RUN_ACTOR_DIRTY + * flag is useful in this case. Clutter will set this flag when a + * redraw has been queued on the actor since it was last + * painted. The effect can use this information to decide if the + * cached image is still valid. + * + * + * The ‘run’ virtual was added in Clutter 1.8. Prior to that there + * were two separate functions as follows. + * * * pre_paint(), which is called * before painting the #ClutterActor. * post_paint(), which is called * after painting the #ClutterActor. * - * The pre_paint() function should be used to set + * The pre_paint() function was used to set * up the #ClutterEffect right before the #ClutterActor's paint * sequence. This function can fail, and return %FALSE; in that case, no * post_paint() invocation will follow. - * The post_paint() function is called after the + * The post_paint() function was called after the * #ClutterActor's paint sequence. - * The pre_paint() phase could be seen as a custom - * handler to the #ClutterActor::paint signal, while the - * post_paint() phase could be seen as a custom handler - * to the #ClutterActor::paint signal connected using - * g_signal_connect_after(). + * + * With these two functions it is not possible to skip the rest of + * the paint sequence. The default implementation of the ‘run’ + * virtual calls pre_paint(), clutter_actor_continue_paint() and + * then post_paint() so that existing actors that aren't using the + * run virtual will continue to work. New actors using the run + * virtual do not need to implement pre or post paint. + * * * A simple ClutterEffect implementation - * The example below creates two rectangles: one will be painted - * "behind" the actor, while another will be painted "on top" of the actor. - * The set_actor() implementation will create the two - * materials used for the two different rectangles; the - * pre_paint() function will paint the first material - * using cogl_rectangle(), while the post_paint() - * phase will paint the second material. + * The example below creates two rectangles: one will be + * painted "behind" the actor, while another will be painted "on + * top" of the actor. The set_actor() + * implementation will create the two materials used for the two + * different rectangles; the run() function + * will paint the first material using cogl_rectangle(), before + * continuing and then it will paint paint the second material + * after. * * typedef struct { * ClutterEffect parent_instance; @@ -116,31 +156,19 @@ * } * * static gboolean - * my_effect_pre_paint (ClutterEffect *effect) + * my_effect_run (ClutterEffect *effect) * { * MyEffect *self = MY_EFFECT (effect); * gfloat width, height; * - * /* If we were disabled we don't need to paint anything */ - * if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect))) - * return FALSE; - * * clutter_actor_get_size (self->actor, &width, &height); * * /* Paint the first rectangle in the upper left quadrant */ * cogl_set_source (self->rect_1); * cogl_rectangle (0, 0, width / 2, height / 2); * - * return TRUE; - * } - * - * static void - * my_effect_post_paint (ClutterEffect *effect) - * { - * MyEffect *self = MY_EFFECT (effect); - * gfloat width, height; - * - * clutter_actor_get_size (self->actor, &width, &height); + * /* Continue to the rest of the paint sequence */ + * clutter_actor_continue_paint (self->actor); * * /* Paint the second rectangle in the lower right quadrant */ * cogl_set_source (self->rect_2); @@ -154,8 +182,7 @@ * * meta_class->set_actor = my_effect_set_actor; * - * klass->pre_paint = my_effect_pre_paint; - * klass->post_paint = my_effect_post_paint; + * klass->run = my_effect_run; * } * * @@ -199,6 +226,27 @@ clutter_effect_real_get_paint_volume (ClutterEffect *effect, return TRUE; } +static void +clutter_effect_real_run (ClutterEffect *effect, + ClutterEffectRunFlags flags) +{ + ClutterActorMeta *actor_meta = CLUTTER_ACTOR_META (effect); + ClutterActor *actor; + gboolean pre_paint_succeeded; + + /* The default implementation provides a compatibility wrapper for + effects that haven't migrated to use the 'run' virtual yet. This + just calls the old pre and post virtuals before chaining on */ + + pre_paint_succeeded = _clutter_effect_pre_paint (effect); + + actor = clutter_actor_meta_get_actor (actor_meta); + clutter_actor_continue_paint (actor); + + if (pre_paint_succeeded) + _clutter_effect_post_paint (effect); +} + static void clutter_effect_notify (GObject *gobject, GParamSpec *pspec) @@ -226,6 +274,7 @@ clutter_effect_class_init (ClutterEffectClass *klass) klass->pre_paint = clutter_effect_real_pre_paint; klass->post_paint = clutter_effect_real_post_paint; klass->get_paint_volume = clutter_effect_real_get_paint_volume; + klass->run = clutter_effect_real_run; } static void @@ -249,6 +298,15 @@ _clutter_effect_post_paint (ClutterEffect *effect) CLUTTER_EFFECT_GET_CLASS (effect)->post_paint (effect); } +void +_clutter_effect_run (ClutterEffect *effect, + ClutterEffectRunFlags flags) +{ + g_return_if_fail (CLUTTER_IS_EFFECT (effect)); + + CLUTTER_EFFECT_GET_CLASS (effect)->run (effect, flags); +} + gboolean _clutter_effect_get_paint_volume (ClutterEffect *effect, ClutterPaintVolume *volume) diff --git a/clutter/clutter-effect.h b/clutter/clutter-effect.h index 422c89af5..1e2c124f2 100644 --- a/clutter/clutter-effect.h +++ b/clutter/clutter-effect.h @@ -42,6 +42,20 @@ G_BEGIN_DECLS typedef struct _ClutterEffectClass ClutterEffectClass; +/** + * ClutterEffectRunFlags: + * @CLUTTER_EFFECT_RUN_ACTOR_DIRTY: The actor or one of its children + * has queued a redraw before this paint. This implies that the effect + * should call clutter_actor_continue_paint() to chain to the next + * effect and can not cache any results from a previous paint. + * + * Flags passed to the ‘run’ method of #ClutterEffect. + */ +typedef enum +{ + CLUTTER_EFFECT_RUN_ACTOR_DIRTY = (1 << 0) +} ClutterEffectRunFlags; + /** * ClutterEffect: * @@ -61,6 +75,7 @@ struct _ClutterEffect * @pre_paint: virtual function * @post_paint: virtual function * @get_paint_volume: virtual function + * @run: virtual function * * The #ClutterEffectClass structure contains only private data * @@ -78,8 +93,10 @@ struct _ClutterEffectClass gboolean (* get_paint_volume) (ClutterEffect *effect, ClutterPaintVolume *volume); + void (* run) (ClutterEffect *effect, + ClutterEffectRunFlags flags); + /*< private >*/ - void (* _clutter_effect2) (void); void (* _clutter_effect3) (void); void (* _clutter_effect4) (void); void (* _clutter_effect5) (void); diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index 3bad5941e..0c91b9326 100644 --- a/doc/reference/clutter/clutter-sections.txt +++ b/doc/reference/clutter/clutter-sections.txt @@ -291,6 +291,7 @@ clutter_actor_hide_all clutter_actor_realize clutter_actor_unrealize clutter_actor_paint +clutter_actor_continue_paint clutter_actor_queue_redraw clutter_actor_queue_relayout clutter_actor_destroy @@ -2467,6 +2468,7 @@ ClutterClickActionPrivate clutter-effect ClutterEffect ClutterEffectClass +ClutterEffectRunFlags CLUTTER_TYPE_EFFECT CLUTTER_EFFECT