clutter-actor: Add an 'offscreen-redirect' property

This adds a property which can be used to redirect the actor through
an FBO before painting so that it becomes flattened in an image. The
image can be used as a cache to avoid having to repaint the actor if
something unrelated in the scene changes. It can also be used to
implement correct opacity even if the actor has overlapping
primitives. The property is an enum that takes three values:

CLUTTER_OFFSCREEN_REDIRECT_NEVER: The default behaviour which is to
  never flatten the actor.

CLUTTER_OFFSCREEN_REDIRECT_ALWAYS: The actor is always redirected
  through an FBO.

CLUTTER_OFFSCREEN_REDIRECT_ONLY_FOR_OPACITY: The actor is only
  redirected through an FBO if the paint opacity is not 255. This
  value would be used if the actor wants correct opacity. It will
  avoid the overhead of using an FBO whenever the actor is fully
  opaque.

The property is implemented by installing a ClutterFlattenEffect.
ClutterFlattenEffect is a new internal class which subclasses
ClutterOffscreen to redirect the painting to an FBO. When
ClutterOffscreen paints, the effect sets an opacity override on the
actor so that the image will always contain the actor at full
opacity. The opacity is then applied to the resulting image before
painting it to the stage. This means the actor does not need to be
redrawn while the opacity is being animated.

The effect has a high internal priority so that it will always occur
before any other effects and it gets hidden from the application.
This commit is contained in:
Neil Roberts 2011-02-28 15:05:51 +00:00
parent 8df8d4ff0f
commit 7f78237ee5
8 changed files with 438 additions and 3 deletions

View File

@ -175,6 +175,7 @@ source_c = \
$(srcdir)/clutter-feature.c \ $(srcdir)/clutter-feature.c \
$(srcdir)/clutter-fixed.c \ $(srcdir)/clutter-fixed.c \
$(srcdir)/clutter-fixed-layout.c \ $(srcdir)/clutter-fixed-layout.c \
$(srcdir)/clutter-flatten-effect.c \
$(srcdir)/clutter-flow-layout.c \ $(srcdir)/clutter-flow-layout.c \
$(srcdir)/clutter-frame-source.c \ $(srcdir)/clutter-frame-source.c \
$(srcdir)/clutter-group.c \ $(srcdir)/clutter-group.c \
@ -227,6 +228,7 @@ source_h_priv = \
$(srcdir)/clutter-effect-private.h \ $(srcdir)/clutter-effect-private.h \
$(srcdir)/clutter-event-translator.h \ $(srcdir)/clutter-event-translator.h \
$(srcdir)/clutter-event-private.h \ $(srcdir)/clutter-event-private.h \
$(srcdir)/clutter-flatten-effect.h \
$(srcdir)/clutter-id-pool.h \ $(srcdir)/clutter-id-pool.h \
$(srcdir)/clutter-master-clock.h \ $(srcdir)/clutter-master-clock.h \
$(srcdir)/clutter-model-private.h \ $(srcdir)/clutter-model-private.h \

View File

@ -299,6 +299,7 @@
#include "clutter-enum-types.h" #include "clutter-enum-types.h"
#include "clutter-main.h" #include "clutter-main.h"
#include "clutter-marshal.h" #include "clutter-marshal.h"
#include "clutter-flatten-effect.h"
#include "clutter-paint-volume-private.h" #include "clutter-paint-volume-private.h"
#include "clutter-private.h" #include "clutter-private.h"
#include "clutter-profile.h" #include "clutter-profile.h"
@ -442,6 +443,12 @@ struct _ClutterActorPrivate
guint8 opacity; guint8 opacity;
gint opacity_override; gint opacity_override;
ClutterOffscreenRedirect offscreen_redirect;
/* This is an internal effect used to implement the
offscreen-redirect property */
ClutterEffect *flatten_effect;
ClutterActor *parent_actor; ClutterActor *parent_actor;
GList *children; GList *children;
gint n_children; gint n_children;
@ -545,6 +552,8 @@ enum
PROP_OPACITY, PROP_OPACITY,
PROP_OFFSCREEN_REDIRECT,
PROP_VISIBLE, PROP_VISIBLE,
PROP_MAPPED, PROP_MAPPED,
PROP_REALIZED, PROP_REALIZED,
@ -3017,6 +3026,10 @@ clutter_actor_set_property (GObject *object,
clutter_actor_set_opacity (actor, g_value_get_uint (value)); clutter_actor_set_opacity (actor, g_value_get_uint (value));
break; break;
case PROP_OFFSCREEN_REDIRECT:
clutter_actor_set_offscreen_redirect (actor, g_value_get_enum (value));
break;
case PROP_NAME: case PROP_NAME:
clutter_actor_set_name (actor, g_value_get_string (value)); clutter_actor_set_name (actor, g_value_get_string (value));
break; break;
@ -3308,6 +3321,10 @@ clutter_actor_get_property (GObject *object,
g_value_set_uint (value, priv->opacity); g_value_set_uint (value, priv->opacity);
break; break;
case PROP_OFFSCREEN_REDIRECT:
g_value_set_enum (value, priv->offscreen_redirect);
break;
case PROP_NAME: case PROP_NAME:
g_value_set_string (value, priv->name); g_value_set_string (value, priv->name);
break; break;
@ -3544,6 +3561,12 @@ clutter_actor_dispose (GObject *object)
priv->effects = NULL; priv->effects = NULL;
} }
if (priv->flatten_effect != NULL)
{
g_object_unref (priv->flatten_effect);
priv->flatten_effect = NULL;
}
g_signal_emit (self, actor_signals[DESTROY], 0); g_signal_emit (self, actor_signals[DESTROY], 0);
G_OBJECT_CLASS (clutter_actor_parent_class)->dispose (object); G_OBJECT_CLASS (clutter_actor_parent_class)->dispose (object);
@ -4016,6 +4039,26 @@ clutter_actor_class_init (ClutterActorClass *klass)
obj_props[PROP_OPACITY] = pspec; obj_props[PROP_OPACITY] = pspec;
g_object_class_install_property (object_class, PROP_OPACITY, pspec); g_object_class_install_property (object_class, PROP_OPACITY, pspec);
/**
* ClutterActor:offscreen-redirect:
*
* Whether to flatten the actor into a single image. See
* clutter_actor_set_offscreen_redirect() for details.
*
* Since: 1.8
*/
pspec = g_param_spec_enum ("offscreen-redirect",
P_("Offscreen redirect"),
P_("Whether to flatten the actor into a "
"single image"),
CLUTTER_TYPE_OFFSCREEN_REDIRECT,
CLUTTER_OFFSCREEN_REDIRECT_NEVER,
CLUTTER_PARAM_READWRITE);
obj_props[PROP_OFFSCREEN_REDIRECT] = pspec;
g_object_class_install_property (object_class,
PROP_OFFSCREEN_REDIRECT,
pspec);
/** /**
* ClutterActor:visible: * ClutterActor:visible:
* *
@ -5061,6 +5104,7 @@ clutter_actor_init (ClutterActor *self)
priv->parent_actor = NULL; priv->parent_actor = NULL;
priv->has_clip = FALSE; priv->has_clip = FALSE;
priv->opacity = 0xff; priv->opacity = 0xff;
priv->offscreen_redirect = CLUTTER_OFFSCREEN_REDIRECT_NEVER;
priv->id = _clutter_context_acquire_id (self); priv->id = _clutter_context_acquire_id (self);
priv->pick_id = -1; priv->pick_id = -1;
priv->scale_x = 1.0; priv->scale_x = 1.0;
@ -7259,7 +7303,16 @@ clutter_actor_set_opacity (ClutterActor *self,
{ {
priv->opacity = opacity; priv->opacity = opacity;
clutter_actor_queue_redraw (self); /* Queue a redraw from the flatten effect so that it can use
its cached image if available instead of having to redraw the
actual actor. If it doesn't end up using the FBO then the
effect is still able to continue the paint anyway. If there
is no flatten effect yet then this is equivalent to queueing
a full redraw */
_clutter_actor_queue_redraw_full (self,
0, /* flags */
NULL, /* clip */
priv->flatten_effect);
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_OPACITY]); g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_OPACITY]);
} }
@ -7350,6 +7403,131 @@ clutter_actor_get_opacity (ClutterActor *self)
return self->priv->opacity; return self->priv->opacity;
} }
/**
* clutter_actor_set_offscreen_redirect:
* @self: A #ClutterActor
* @redirect: New offscreen redirect value for the actor.
*
* Sets whether to redirect the actor into an offscreen image. The
* offscreen image is used to flatten the actor into a single image
* while painting for two main reasons. Firstly, when the actor is
* painted a second time without any of its contents changing it can
* simply repaint the cached image without descending further down the
* actor hierarchy. Secondly, it will make the opacity look correct
* even if there are overlapping primitives in the actor.
*
* Caching the actor could in some cases be a performance win and in
* some cases be a performance lose so it is important to determine
* which value is right for an actor before modifying this value. For
* example, there is never any reason to flatten an actor that is just
* a single texture (such as a ClutterTexture) because it is
* effectively already cached in an image so the offscreen would be
* redundant. Also if the actor contains primitives that are far apart
* with a large transparent area in the middle (such as a large
* CluterGroup with a small actor in the top left and a small actor in
* the bottom right) then the cached image will contain the entire
* image of the large area and the paint will waste time blending all
* of the transparent pixels in the middle.
*
* The default method of implementing opacity on a container simply
* forwards on the opacity to all of the children. If the children are
* overlapping then it will appear as if they are two separate glassy
* objects and there will be a break in the color where they
* overlap. By setting the offscreen-redirect to
* %CLUTTER_OFFSCREEN_REDIRECT_OPACITY_ONLY it will be as if the two
* opaque objects are combined into one and then made transparent
* which is usually what is expected.
*
* The image below demonstrates the difference between the fast
* default opacity and the correct but inefficient opacity achieved
* through the offscreen redirect. The image shows two Clutter groups,
* each containing a red and a green rectangle which overlap. The
* opacity on the group is set to 128 (which is 50%). When the
* offscreen redirect is not used, the red rectangle can be seen
* through the blue rectangle as if the two rectangles were separately
* transparent. When the redirect is used the group as a whole is
* transparent instead so the red rectangle is not visible where they
* overlap.
*
* <figure id="offscreen-redirect">
* <title>Sample of using an offscreen redirect for transparency</title>
* <graphic fileref="offscreen-redirect.png" format="PNG"/>
* </figure>
*
* The default value is %CLUTTER_OFFSCREEN_REDIRECT_NEVER.
*
* Since: 1.8
*/
void
clutter_actor_set_offscreen_redirect (ClutterActor *self,
ClutterOffscreenRedirect redirect)
{
ClutterActorPrivate *priv;
g_return_if_fail (CLUTTER_IS_ACTOR (self));
priv = self->priv;
if (priv->offscreen_redirect != redirect)
{
priv->offscreen_redirect = redirect;
if (priv->flatten_effect == NULL)
{
ClutterActorMeta *actor_meta;
gint priority;
priv->flatten_effect = _clutter_flatten_effect_new ();
/* Keep a reference to the effect so that we can queue
redraws from it */
g_object_ref_sink (priv->flatten_effect);
/* Set the priority of the effect to high so that it will
always be applied to the actor first. It uses an internal
priority so that it won't be visible to applications */
actor_meta = CLUTTER_ACTOR_META (priv->flatten_effect);
priority = CLUTTER_ACTOR_META_PRIORITY_INTERNAL_HIGH;
_clutter_actor_meta_set_priority (actor_meta, priority);
/* This will also queue a full redraw of the actor */
clutter_actor_add_effect (self, priv->flatten_effect);
}
else
{
/* Queue a redraw from the effect so that it can use its
cached image if available instead of having to redraw the
actual actor. If it doesn't end up using the FBO then the
effect is still able to continue the paint anyway */
_clutter_actor_queue_redraw_full (self,
0, /* flags */
NULL, /* clip */
priv->flatten_effect);
}
g_object_notify_by_pspec (G_OBJECT (self),
obj_props[PROP_OFFSCREEN_REDIRECT]);
}
}
/**
* clutter_actor_get_offscreen_redirect:
* @self: a #ClutterActor
*
* Retrieves whether to redirect the actor to an offscreen buffer, as
* set by clutter_actor_set_offscreen_redirect().
*
* Return value: the value of the offscreen-redirect property of the actor
*
* Since: 1.8
*/
ClutterOffscreenRedirect
clutter_actor_get_offscreen_redirect (ClutterActor *self)
{
g_return_val_if_fail (CLUTTER_IS_ACTOR (self), 0);
return self->priv->offscreen_redirect;
}
/** /**
* clutter_actor_set_name: * clutter_actor_set_name:
* @self: A #ClutterActor * @self: A #ClutterActor

View File

@ -116,6 +116,26 @@ typedef enum
CLUTTER_ACTOR_NO_LAYOUT = 1 << 5 CLUTTER_ACTOR_NO_LAYOUT = 1 << 5
} ClutterActorFlags; } ClutterActorFlags;
/**
* ClutterOffscreenRedirect:
* @CLUTTER_OFFSCREEN_REDIRECT_NEVER: Never redirect the actor to an
* offscreen buffer.
* @CLUTTER_OFFSCREEN_REDIRECT_ALWAYS: Always redirect the actor to an
* offscreen buffer.
* @CLUTTER_OFFSCREEN_REDIRECT_OPACITY_ONLY: Only redirect the actor if
* it is semi-transparent.
*
* Possible values to pass to clutter_actor_set_offscreen_redirect().
*
* Since: 1.8
*/
typedef enum
{
CLUTTER_OFFSCREEN_REDIRECT_NEVER,
CLUTTER_OFFSCREEN_REDIRECT_ALWAYS,
CLUTTER_OFFSCREEN_REDIRECT_OPACITY_ONLY
} ClutterOffscreenRedirect;
/** /**
* ClutterAllocationFlags: * ClutterAllocationFlags:
* @CLUTTER_ALLOCATION_NONE: No flag set * @CLUTTER_ALLOCATION_NONE: No flag set
@ -424,6 +444,10 @@ guint8 clutter_actor_get_opacity (ClutterActor
guint8 clutter_actor_get_paint_opacity (ClutterActor *self); guint8 clutter_actor_get_paint_opacity (ClutterActor *self);
gboolean clutter_actor_get_paint_visibility (ClutterActor *self); gboolean clutter_actor_get_paint_visibility (ClutterActor *self);
void clutter_actor_set_offscreen_redirect (ClutterActor *self,
ClutterOffscreenRedirect redirect);
ClutterOffscreenRedirect
clutter_actor_get_offscreen_redirect (ClutterActor *self);
void clutter_actor_set_name (ClutterActor *self, void clutter_actor_set_name (ClutterActor *self,
const gchar *name); const gchar *name);

View File

@ -0,0 +1,151 @@
/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright (C) 2011 Intel Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Neil Roberts <neil@linux.intel.com>
*/
/* This is an internal-only effect used to implement the
'flatness' property of ClutterActor */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "clutter-flatten-effect.h"
#include "clutter-private.h"
#include "clutter-actor-private.h"
static void
_clutter_flatten_effect_set_actor (ClutterActorMeta *meta,
ClutterActor *actor);
static void
_clutter_flatten_effect_run (ClutterEffect *effect,
ClutterEffectRunFlags flags);
G_DEFINE_TYPE (ClutterFlattenEffect,
_clutter_flatten_effect,
CLUTTER_TYPE_OFFSCREEN_EFFECT);
#define CLUTTER_FLATTEN_EFFECT_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_FLATTEN_EFFECT, \
ClutterFlattenEffectPrivate))
struct _ClutterFlattenEffectPrivate
{
ClutterActor *actor;
/* This records whether the last paint went through the FBO or if it
was painted directly. We need to know this so we can force the
offscreen effect to clear its image when we switch from rendering
directly to rendering through the FBO */
gboolean last_paint_used_fbo;
};
static void
_clutter_flatten_effect_class_init (ClutterFlattenEffectClass *klass)
{
ClutterActorMetaClass *actor_meta_class = (ClutterActorMetaClass *) klass;
ClutterEffectClass *effect_class = (ClutterEffectClass *) klass;
actor_meta_class->set_actor = _clutter_flatten_effect_set_actor;
effect_class->run = _clutter_flatten_effect_run;
g_type_class_add_private (klass, sizeof (ClutterFlattenEffectPrivate));
}
static void
_clutter_flatten_effect_init (ClutterFlattenEffect *self)
{
self->priv = CLUTTER_FLATTEN_EFFECT_GET_PRIVATE (self);
}
ClutterEffect *
_clutter_flatten_effect_new (void)
{
return g_object_new (CLUTTER_TYPE_FLATTEN_EFFECT, NULL);
}
static gboolean
_clutter_flatten_effect_is_using_fbo (ClutterFlattenEffect *opacity_effect)
{
ClutterFlattenEffectPrivate *priv = opacity_effect->priv;
switch (clutter_actor_get_offscreen_redirect (priv->actor))
{
case CLUTTER_OFFSCREEN_REDIRECT_NEVER:
return FALSE;
case CLUTTER_OFFSCREEN_REDIRECT_ALWAYS:
return TRUE;
case CLUTTER_OFFSCREEN_REDIRECT_OPACITY_ONLY:
return clutter_actor_get_paint_opacity (priv->actor) < 255;
}
g_assert_not_reached ();
}
static void
_clutter_flatten_effect_set_actor (ClutterActorMeta *meta,
ClutterActor *actor)
{
ClutterFlattenEffect *opacity_effect = CLUTTER_FLATTEN_EFFECT (meta);
ClutterFlattenEffectPrivate *priv = opacity_effect->priv;
CLUTTER_ACTOR_META_CLASS (_clutter_flatten_effect_parent_class)->
set_actor (meta, actor);
/* we keep a back pointer here, to avoid going through the ActorMeta */
priv->actor = clutter_actor_meta_get_actor (meta);
}
static void
_clutter_flatten_effect_run (ClutterEffect *effect,
ClutterEffectRunFlags flags)
{
ClutterFlattenEffect *opacity_effect = CLUTTER_FLATTEN_EFFECT (effect);
ClutterFlattenEffectPrivate *priv = opacity_effect->priv;
if (_clutter_flatten_effect_is_using_fbo (opacity_effect))
{
/* If the last paint bypassed the FBO then we'll pretend the
actor is dirty so that the offscreen will clear its image */
if (!priv->last_paint_used_fbo)
{
flags |= CLUTTER_EFFECT_RUN_ACTOR_DIRTY;
priv->last_paint_used_fbo = TRUE;
}
/* Let the offscreen effect paint the actor through the FBO */
CLUTTER_EFFECT_CLASS (_clutter_flatten_effect_parent_class)->
run (effect, flags);
}
else
{
/* Just let the actor paint directly to the stage */
clutter_actor_continue_paint (priv->actor);
priv->last_paint_used_fbo = FALSE;
}
}

View File

@ -0,0 +1,75 @@
/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright (C) 2011 Intel Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Neil Roberts <neil@linux.intel.com>
*/
#ifndef __CLUTTER_FLATTEN_EFFECT_H__
#define __CLUTTER_FLATTEN_EFFECT_H__
#include <clutter/clutter-offscreen-effect.h>
G_BEGIN_DECLS
#define CLUTTER_TYPE_FLATTEN_EFFECT \
(_clutter_flatten_effect_get_type())
#define CLUTTER_FLATTEN_EFFECT(obj) \
(G_TYPE_CHECK_INSTANCE_CAST ((obj), \
CLUTTER_TYPE_FLATTEN_EFFECT, \
ClutterFlattenEffect))
#define CLUTTER_FLATTEN_EFFECT_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST ((klass), \
CLUTTER_TYPE_FLATTEN_EFFECT, \
ClutterFlattenEffectClass))
#define CLUTTER_IS_FLATTEN_EFFECT(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
CLUTTER_TYPE_FLATTEN_EFFECT))
#define CLUTTER_IS_FLATTEN_EFFECT_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE ((klass), \
CLUTTER_TYPE_FLATTEN_EFFECT))
#define CLUTTER_FLATTEN_EFFECT_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), \
CLUTTER_FLATTEN_EFFECT, \
ClutterFlattenEffectClass))
typedef struct _ClutterFlattenEffect ClutterFlattenEffect;
typedef struct _ClutterFlattenEffectClass ClutterFlattenEffectClass;
typedef struct _ClutterFlattenEffectPrivate ClutterFlattenEffectPrivate;
struct _ClutterFlattenEffectClass
{
ClutterOffscreenEffectClass parent_class;
};
struct _ClutterFlattenEffect
{
ClutterOffscreenEffect parent;
ClutterFlattenEffectPrivate *priv;
};
GType _clutter_flatten_effect_get_type (void) G_GNUC_CONST;
ClutterEffect *_clutter_flatten_effect_new (void);
G_END_DECLS
#endif /* __CLUTTER_FLATTEN_EFFECT_H__ */

View File

@ -122,7 +122,8 @@ HTML_IMAGES=\
event-flow.png \ event-flow.png \
flow-layout-horizontal.png \ flow-layout-horizontal.png \
flow-layout-vertical.png \ flow-layout-vertical.png \
path-alpha-func.png path-alpha-func.png \
offscreen-redirect.png
# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). # Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
# e.g. content_files=running.sgml building.sgml changes-2.0.sgml # e.g. content_files=running.sgml building.sgml changes-2.0.sgml
@ -179,4 +180,5 @@ EXTRA_DIST += \
event-flow.png \ event-flow.png \
flow-layout-horizontal.png \ flow-layout-horizontal.png \
flow-layout-vertical.png \ flow-layout-vertical.png \
path-alpha-func.png path-alpha-func.png \
offscreen-redirect.png

View File

@ -341,6 +341,9 @@ clutter_actor_get_z_rotation_gravity
clutter_actor_is_rotated clutter_actor_is_rotated
clutter_actor_set_opacity clutter_actor_set_opacity
clutter_actor_get_opacity clutter_actor_get_opacity
ClutterOffscreenRedirect
clutter_actor_set_offscreen_redirect
clutter_actor_get_offscreen_redirect
clutter_actor_set_name clutter_actor_set_name
clutter_actor_get_name clutter_actor_get_name
clutter_actor_get_gid clutter_actor_get_gid

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB