clutter-effect: Add clutter_effect_queue_rerun

This adds a new public function to queue a rerun of an effect. If
nothing else queues a redraw then when the effect's actor is painted
the effect will be run without the CLUTTER_EFFECT_RUN_ACTOR_DIRTY
flag. This allows parametrised offscreen effects to report that they
need to redraw the image without having to redraw the underlying
actor. This will be used to implement the 'transparency' effect of
ClutterActor.

If multiple redraws are queued with different effects then redrawing
is started from the one that occurs last in the list of effects.

Internally the function is a wrapper around the new function
_clutter_actor_queue_redraw_full. This is intended to be the sole
point of code for queuing redraws on an actor. It has parameters for
the clip and the effect. The other two existing functions to queue a
redraw (one with a clip and one without) now wrap around this function
by passing a NULL effect.
This commit is contained in:
Neil Roberts 2011-02-28 12:16:55 +00:00
parent 11443ed480
commit 00b733a85a
5 changed files with 262 additions and 118 deletions

View File

@ -147,6 +147,11 @@ void _clutter_actor_set_has_pointer (ClutterActor *self,
void _clutter_actor_queue_redraw_with_clip (ClutterActor *self,
ClutterRedrawFlags flags,
ClutterPaintVolume *clip_volume);
void _clutter_actor_queue_redraw_full (ClutterActor *self,
ClutterRedrawFlags flags,
ClutterPaintVolume *volume,
ClutterEffect *effect);
ClutterPaintVolume *_clutter_actor_get_queue_redraw_clip (ClutterActor *self);
void _clutter_actor_set_queue_redraw_clip (ClutterActor *self,
ClutterPaintVolume *clip_volume);

View File

@ -472,7 +472,14 @@ struct _ClutterActorPrivate
ClutterMetaGroup *effects;
/* used when painting, to update the paint volume */
ClutterActorMeta *current_effect;
ClutterEffect *current_effect;
/* This is used to store an effect which needs to be redrawn. A
redraw can be queued to start from a particular effect. This is
used by parametrised effects that can cache an image of the
actor. If a parameter of the effect changes then it only needs to
redraw the cached image, not the actual actor */
ClutterEffect *effect_to_redraw;
ClutterPaintVolume paint_volume;
@ -1910,6 +1917,14 @@ clutter_actor_real_queue_redraw (ClutterActor *self,
self->priv->propagated_one_redraw = TRUE;
/* If the queue redraw is coming from a child actor then we'll
assume the queued effect is no longer valid. If this actor has
had a redraw queued then that will mean it will instead redraw
the whole actor. If it hasn't had a redraw queued then it will
stay that way */
if (self != origin)
self->priv->effect_to_redraw = NULL;
/* notify parents, if they are all visible eventually we'll
* queue redraw on the stage, which queues the redraw idle.
*/
@ -2854,7 +2869,7 @@ clutter_actor_continue_paint (ClutterActor *self)
}
else
{
ClutterActorMeta *old_current_effect;
ClutterEffect *old_current_effect;
ClutterEffectRunFlags run_flags = 0;
/* Cache the current effect so that we can put it back before
@ -2865,9 +2880,18 @@ clutter_actor_continue_paint (ClutterActor *self)
priv->next_effect_to_paint = priv->next_effect_to_paint->next;
if (priv->propagated_one_redraw)
run_flags |= CLUTTER_EFFECT_RUN_ACTOR_DIRTY;
{
/* If there's an effect queued with this redraw then all
effects up to that one will be considered dirty. It is
expected the queued effect will paint the cached image
and not call clutter_actor_continue_paint again (although
it should work ok if it does) */
if (priv->effect_to_redraw == NULL ||
priv->current_effect != priv->effect_to_redraw)
run_flags |= CLUTTER_EFFECT_RUN_ACTOR_DIRTY;
}
_clutter_effect_run (CLUTTER_EFFECT (priv->current_effect), run_flags);
_clutter_effect_run (priv->current_effect, run_flags);
priv->current_effect = old_current_effect;
}
@ -5172,38 +5196,60 @@ _clutter_actor_finish_queue_redraw (ClutterActor *self,
priv->queue_redraw_entry = NULL;
}
/**
* clutter_actor_queue_redraw:
* @self: A #ClutterActor
*
* Queues up a redraw of an actor and any children. The redraw occurs
* once the main loop becomes idle (after the current batch of events
* has been processed, roughly).
*
* Applications rarely need to call this, as redraws are handled
* automatically by modification functions.
*
* This function will not do anything if @self is not visible, or
* if the actor is inside an invisible part of the scenegraph.
*
* Also be aware that painting is a NOP for actors with an opacity of
* 0
*
* When you are implementing a custom actor you must queue a redraw
* whenever some private state changes that will affect painting or
* picking of your actor.
*/
void
clutter_actor_queue_redraw (ClutterActor *self)
static void
_clutter_actor_get_allocation_clip (ClutterActor *self,
ClutterActorBox *clip)
{
ClutterActorBox allocation;
/* XXX: we don't care if we get an out of date allocation here
* because clutter_actor_queue_redraw_with_clip knows to ignore
* the clip if the actor's allocation is invalid.
*
* This is noted because clutter_actor_get_allocation_box does some
* unnecessary work to support buggy code with a comment suggesting
* that it could be changed later which would be good for this use
* case!
*/
clutter_actor_get_allocation_box (self, &allocation);
/* NB: clutter_actor_queue_redraw_with_clip expects a box in the
* actor's own coordinate space but the allocation is in parent
* coordinates */
clip->x1 = 0;
clip->y1 = 0;
clip->x2 = allocation.x2 - allocation.x1;
clip->y2 = allocation.y2 - allocation.y1;
}
void
_clutter_actor_queue_redraw_full (ClutterActor *self,
ClutterRedrawFlags flags,
ClutterPaintVolume *volume,
ClutterEffect *effect)
{
ClutterPaintVolume allocation_pv;
ClutterActorPrivate *priv;
ClutterPaintVolume *pv;
gboolean should_free_pv;
ClutterActor *stage;
gboolean was_dirty;
g_return_if_fail (CLUTTER_IS_ACTOR (self));
priv = self->priv;
/* Here's an outline of the actor queue redraw mechanism:
*
* The process starts either here or in
* _clutter_actor_queue_redraw_with_clip.
* The process starts in one of the following two functions which
* are wrappers for this function:
* clutter_actor_queue_redraw
* _clutter_actor_queue_redraw_with_clip
*
* These functions queue an entry in a list associated with the
* additionally, an effect can queue a redraw by wrapping this
* function in clutter_effect_queue_rerun
*
* This functions queues an entry in a list associated with the
* stage which is a list of actors that queued a redraw while
* updating the timelines, performing layouting and processing other
* mainloop sources before the next paint starts.
@ -5232,8 +5278,9 @@ clutter_actor_queue_redraw (ClutterActor *self)
* difference to performance.
*
* So the control flow goes like this:
* clutter_actor_queue_redraw and
* _clutter_actor_queue_redraw_with_clip
* One of clutter_actor_queue_redraw,
* _clutter_actor_queue_redraw_with_clip
* or clutter_effect_queue_rerun
*
* then control moves to:
* _clutter_stage_queue_actor_redraw
@ -5261,44 +5308,125 @@ clutter_actor_queue_redraw (ClutterActor *self)
* paint.
*/
g_return_if_fail (CLUTTER_IS_ACTOR (self));
stage = _clutter_actor_get_stage_internal (self);
/* Ignore queuing a redraw for actors not descended from a stage */
stage = _clutter_actor_get_stage_internal (self);
if (stage == NULL)
return;
self->priv->queue_redraw_entry =
_clutter_stage_queue_actor_redraw (CLUTTER_STAGE (stage),
self->priv->queue_redraw_entry,
self,
NULL);
if (flags & CLUTTER_REDRAW_CLIPPED_TO_ALLOCATION)
{
ClutterActorBox allocation_clip;
ClutterVertex origin;
/* If the actor doesn't have a valid allocation then we will
* queue a full stage redraw. */
if (priv->needs_allocation)
{
/* NB: NULL denotes an undefined clip which will result in a
* full redraw... */
_clutter_actor_set_queue_redraw_clip (self, NULL);
_clutter_actor_signal_queue_redraw (self, self);
return;
}
_clutter_paint_volume_init_static (&allocation_pv, self);
pv = &allocation_pv;
_clutter_actor_get_allocation_clip (self, &allocation_clip);
origin.x = allocation_clip.x1;
origin.y = allocation_clip.y1;
origin.z = 0;
clutter_paint_volume_set_origin (pv, &origin);
clutter_paint_volume_set_width (pv,
allocation_clip.x2 - allocation_clip.x1);
clutter_paint_volume_set_height (pv,
allocation_clip.y2 -
allocation_clip.y1);
should_free_pv = TRUE;
}
else
{
pv = volume;
should_free_pv = FALSE;
}
was_dirty = priv->queue_redraw_entry != NULL;
_clutter_stage_queue_actor_redraw (CLUTTER_STAGE (stage),
priv->queue_redraw_entry,
self,
pv);
if (should_free_pv)
clutter_paint_volume_free (pv);
/* If this is the first redraw queued then we can directly use the
effect parameter */
if (!was_dirty)
priv->effect_to_redraw = effect;
/* Otherwise we need to merge it with the existing effect parameter */
else if (effect)
{
/* If there's already an effect then we need to use whichever is
later in the chain of actors. Otherwise a full redraw has
already been queued on the actor so we need to ignore the
effect parameter */
if (priv->effect_to_redraw)
{
if (priv->effects == NULL)
g_warning ("Redraw queued with an effect that is "
"not applied to the actor");
else
{
const GList *l;
for (l = _clutter_meta_group_peek_metas (priv->effects);
l != NULL;
l = l->next)
{
if (l->data == priv->effect_to_redraw ||
l->data == effect)
priv->effect_to_redraw = l->data;
}
}
}
}
else
/* If no effect is specified then we need to redraw the whole
actor */
priv->effect_to_redraw = NULL;
}
static void
_clutter_actor_get_allocation_clip (ClutterActor *self,
ClutterActorBox *clip)
/**
* clutter_actor_queue_redraw:
* @self: A #ClutterActor
*
* Queues up a redraw of an actor and any children. The redraw occurs
* once the main loop becomes idle (after the current batch of events
* has been processed, roughly).
*
* Applications rarely need to call this, as redraws are handled
* automatically by modification functions.
*
* This function will not do anything if @self is not visible, or
* if the actor is inside an invisible part of the scenegraph.
*
* Also be aware that painting is a NOP for actors with an opacity of
* 0
*
* When you are implementing a custom actor you must queue a redraw
* whenever some private state changes that will affect painting or
* picking of your actor.
*/
void
clutter_actor_queue_redraw (ClutterActor *self)
{
ClutterActorBox allocation;
/* XXX: we don't care if we get an out of date allocation here
* because clutter_actor_queue_redraw_with_clip knows to ignore
* the clip if the actor's allocation is invalid.
*
* This is noted because clutter_actor_get_allocation_box does some
* unnecessary work to support buggy code with a comment suggesting
* that it could be changed later which would be good for this use
* case!
*/
clutter_actor_get_allocation_box (self, &allocation);
/* NB: clutter_actor_queue_redraw_with_clip expects a box in the
* actor's own coordinate space but the allocation is in parent
* coordinates */
clip->x1 = 0;
clip->y1 = 0;
clip->x2 = allocation.x2 - allocation.x1;
clip->y2 = allocation.y2 - allocation.y1;
_clutter_actor_queue_redraw_full (self,
0, /* flags */
NULL, /* clip volume */
NULL /* effect */);
}
/*
@ -5343,62 +5471,10 @@ _clutter_actor_queue_redraw_with_clip (ClutterActor *self,
ClutterRedrawFlags flags,
ClutterPaintVolume *volume)
{
ClutterPaintVolume allocation_pv;
ClutterPaintVolume *pv;
gboolean should_free_pv;
ClutterActor *stage;
g_return_if_fail (CLUTTER_IS_ACTOR (self));
if (flags & CLUTTER_REDRAW_CLIPPED_TO_ALLOCATION)
{
ClutterActorBox allocation_clip;
ClutterVertex origin;
/* If the actor doesn't have a valid allocation then we will
* queue a full stage redraw. */
if (self->priv->needs_allocation)
{
/* NB: NULL denotes an undefined clip which will result in a
* full redraw... */
_clutter_actor_set_queue_redraw_clip (self, NULL);
_clutter_actor_signal_queue_redraw (self, self);
return;
}
_clutter_paint_volume_init_static (&allocation_pv, self);
pv = &allocation_pv;
_clutter_actor_get_allocation_clip (self, &allocation_clip);
origin.x = allocation_clip.x1;
origin.y = allocation_clip.y1;
origin.z = 0;
clutter_paint_volume_set_origin (pv, &origin);
clutter_paint_volume_set_width (pv,
allocation_clip.x2 - allocation_clip.x1);
clutter_paint_volume_set_height (pv,
allocation_clip.y2 -
allocation_clip.y1);
should_free_pv = TRUE;
}
else
{
pv = volume;
should_free_pv = FALSE;
}
/* Ignore queuing a redraw for actors not descended from a stage */
stage = _clutter_actor_get_stage_internal (self);
if (stage != NULL)
_clutter_stage_queue_actor_redraw (CLUTTER_STAGE (stage),
self->priv->queue_redraw_entry,
self,
pv);
if (should_free_pv)
clutter_paint_volume_free (pv);
_clutter_actor_queue_redraw_full (self,
flags, /* flags */
volume, /* clip volume */
NULL /* effect */);
}
static void

View File

@ -203,6 +203,7 @@
#include "clutter-enum-types.h"
#include "clutter-marshal.h"
#include "clutter-private.h"
#include "clutter-actor-private.h"
G_DEFINE_ABSTRACT_TYPE (ClutterEffect,
clutter_effect,
@ -316,3 +317,62 @@ _clutter_effect_get_paint_volume (ClutterEffect *effect,
return CLUTTER_EFFECT_GET_CLASS (effect)->get_paint_volume (effect, volume);
}
/**
* clutter_effect_queue_rerun:
* @effect: A #ClutterEffect which needs redrawing
*
* Queues a rerun of the effect. The effect can detect when the run
* method is called as a result of this function because it will not
* have the %CLUTTER_EFFECT_RUN_ACTOR_DIRTY flag set. In that case the
* effect is free to assume that the actor has not changed its
* appearance since the last time it was painted so it doesn't need to
* call clutter_actor_continue_paint() if it can draw a cached
* image. This is mostly intended for effects that are using a
* %CoglOffscreen to redirect the actor (such as
* %ClutterOffscreenEffect). In that case the effect can save a bit of
* rendering time by painting the cached texture without causing the
* entire actor to be painted.
*
* This function can be used by effects that have their own animatable
* parameters. For example, an effect which adds a varying degree of a
* red tint to an actor by redirecting it through a CoglOffscreen
* might have a property to specify the level of tint. When this value
* changes, the underlying actor doesn't need to be redrawn so the
* effect can call clutter_effect_queue_rerun() to make sure the
* effect is repainted.
*
* Note however that modifying the position of the parent of an actor
* may change the appearance of the actor because its transformation
* matrix would change. In this case a redraw wouldn't be queued on
* the actor itself so the %CLUTTER_EFFECT_RUN_ACTOR_DIRTY would still
* not be set. The effect can detect this case by keeping track of the
* last modelview matrix that was used to render the actor and
* veryifying that it remains the same in the next paint.
*
* Any other effects that are layered on top of the passed in effect
* will still be passed the %CLUTTER_EFFECT_RUN_ACTOR_DIRTY flag. If
* anything queues a redraw on the actor without specifying an effect
* or with an effect that is lower in the chain of effects than this
* one then that will override this call. In that case this effect
* will instead be called with the %CLUTTER_EFFECT_RUN_ACTOR_DIRTY
* flag set.
*
* Since: 1.8
*/
void
clutter_effect_queue_rerun (ClutterEffect *effect)
{
ClutterActor *actor;
g_return_if_fail (CLUTTER_IS_EFFECT (effect));
actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect));
/* If the effect has no actor then nothing needs to be done */
if (actor != NULL)
_clutter_actor_queue_redraw_full (actor,
0, /* flags */
NULL, /* clip volume */
effect /* effect */);
}

View File

@ -105,6 +105,8 @@ struct _ClutterEffectClass
GType clutter_effect_get_type (void) G_GNUC_CONST;
void clutter_effect_queue_rerun (ClutterEffect *effect);
/*
* ClutterActor API
*/

View File

@ -2469,6 +2469,7 @@ ClutterClickActionPrivate
ClutterEffect
ClutterEffectClass
ClutterEffectRunFlags
clutter_effect_queue_rerun
<SUBSECTION Standard>
CLUTTER_TYPE_EFFECT
CLUTTER_EFFECT