clutter-effect: Add a 'run' virtual

This adds a new virtual to ClutterEffect which is intended to be a
more flexible replacement for the pre and post_paint functions. The
implementation of a run virtual would look something like this:

void
effect_run (ClutterEffect *effect,
            ClutterEffectRunFlags flags)
{
  /* Set up state */
  /* ... */

  /* Chain to the next item in the paint sequence */
  clutter_actor_continue_paint (priv->actor);

  /* Clean up state */
  /* ... */
}

ClutterActor now just calls this virtual instead of the pre_paint and
post_paint functions. It keeps track of the next effect in the list so
that it knows what to do when clutter_actor_continue_paint is
called. clutter_actor_continue_paint is a new function added just for
implementing effects.

The default implementation of the run virtual just calls pre_paint and
post_paint so that existing effects will continue to work.

An effect is allowed to conditionally skip calling
clutter_actor_continue_paint(). This is useful to implement effects
that cache the image of an actor. The flags parameter can be used to
determine if the actor is dirty since the last paint. ClutterActor
sets this flag whenever propagated_one_redraw is TRUE which means that
a redraw for this actor or one of its children was queued.
This commit is contained in:
Neil Roberts 2011-03-02 16:55:10 +00:00
parent 05a7419e11
commit c3aa4d24bf
6 changed files with 189 additions and 99 deletions

View File

@ -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))
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
*/

View File

@ -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);

View File

@ -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

View File

@ -38,34 +38,74 @@
*
* <refsect2 id="ClutterEffect-implementation">
* <title>Implementing a ClutterEffect</title>
* <para>Creating a sub-class of #ClutterEffect requires the implementation
* of two virtual functions:</para>
* <para>
* Creating a sub-class of #ClutterEffect requires overriding the
* run method. The implementation of the function should look
* something like this:
* </para>
* <programlisting>
* void effect_run (ClutterEffect *effect, ClutterEffectRunFlags flags)
* {
* /&ast; Set up initialisation of the paint such as binding a
* CoglOffscreen or other operations &ast;/
*
* /&ast; 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. &ast;/
* ClutterActor *actor =
* clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect));
* clutter_actor_continue_paint (actor);
*
* /&ast; perform any cleanup of state, such as popping the
* CoglOffscreen &ast;/
* }
* </programlisting>
* <para>
* 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.
* </para>
* <para>
* The run virtual was added in Clutter 1.8. Prior to that there
* were two separate functions as follows.
* </para>
* <itemizedlist>
* <listitem><simpara><function>pre_paint()</function>, which is called
* before painting the #ClutterActor.</simpara></listitem>
* <listitem><simpara><function>post_paint()</function>, which is called
* after painting the #ClutterActor.</simpara></listitem>
* </itemizedlist>
* <para>The <function>pre_paint()</function> function should be used to set
* <para>The <function>pre_paint()</function> 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
* <function>post_paint()</function> invocation will follow.</para>
* <para>The <function>post_paint()</function> function is called after the
* <para>The <function>post_paint()</function> function was called after the
* #ClutterActor's paint sequence.</para>
* <para>The <function>pre_paint()</function> phase could be seen as a custom
* handler to the #ClutterActor::paint signal, while the
* <function>post_paint()</function> phase could be seen as a custom handler
* to the #ClutterActor::paint signal connected using
* g_signal_connect_after().</para>
* <para>
* 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.
* </para>
* <example id="ClutterEffect-example">
* <title>A simple ClutterEffect implementation</title>
* <para>The example below creates two rectangles: one will be painted
* "behind" the actor, while another will be painted "on top" of the actor.
* The <function>set_actor()</function> implementation will create the two
* materials used for the two different rectangles; the
* <function>pre_paint()</function> function will paint the first material
* using cogl_rectangle(), while the <function>post_paint()</function>
* phase will paint the second material.</para>
* <para>The example below creates two rectangles: one will be
* painted "behind" the actor, while another will be painted "on
* top" of the actor. The <function>set_actor()</function>
* implementation will create the two materials used for the two
* different rectangles; the <function>run()</function> function
* will paint the first material using cogl_rectangle(), before
* continuing and then it will paint paint the second material
* after.</para>
* <programlisting>
* 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;
*
* /&ast; If we were disabled we don't need to paint anything &ast;/
* if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect)))
* return FALSE;
*
* clutter_actor_get_size (self-&gt;actor, &amp;width, &amp;height);
*
* /&ast; Paint the first rectangle in the upper left quadrant &ast;/
* cogl_set_source (self-&gt;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-&gt;actor, &amp;width, &amp;height);
* /&ast; Continue to the rest of the paint sequence &ast;/
* clutter_actor_continue_paint (self-&gt;actor);
*
* /&ast; Paint the second rectangle in the lower right quadrant &ast;/
* cogl_set_source (self-&gt;rect_2);
@ -154,8 +182,7 @@
*
* meta_class-&gt;set_actor = my_effect_set_actor;
*
* klass-&gt;pre_paint = my_effect_pre_paint;
* klass-&gt;post_paint = my_effect_post_paint;
* klass-&gt;run = my_effect_run;
* }
* </programlisting>
* </example>
@ -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)

View File

@ -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);

View File

@ -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
<FILE>clutter-effect</FILE>
ClutterEffect
ClutterEffectClass
ClutterEffectRunFlags
<SUBSECTION Standard>
CLUTTER_TYPE_EFFECT
CLUTTER_EFFECT