diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 1b7171020..476a38d2d 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -175,6 +175,7 @@ source_c = \ $(srcdir)/clutter-feature.c \ $(srcdir)/clutter-fixed.c \ $(srcdir)/clutter-fixed-layout.c \ + $(srcdir)/clutter-flatten-effect.c \ $(srcdir)/clutter-flow-layout.c \ $(srcdir)/clutter-frame-source.c \ $(srcdir)/clutter-group.c \ @@ -227,6 +228,7 @@ source_h_priv = \ $(srcdir)/clutter-effect-private.h \ $(srcdir)/clutter-event-translator.h \ $(srcdir)/clutter-event-private.h \ + $(srcdir)/clutter-flatten-effect.h \ $(srcdir)/clutter-id-pool.h \ $(srcdir)/clutter-master-clock.h \ $(srcdir)/clutter-model-private.h \ diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c index b9e1e62fa..e7d0a7d65 100644 --- a/clutter/clutter-actor.c +++ b/clutter/clutter-actor.c @@ -299,6 +299,7 @@ #include "clutter-enum-types.h" #include "clutter-main.h" #include "clutter-marshal.h" +#include "clutter-flatten-effect.h" #include "clutter-paint-volume-private.h" #include "clutter-private.h" #include "clutter-profile.h" @@ -442,6 +443,12 @@ struct _ClutterActorPrivate guint8 opacity; gint opacity_override; + ClutterOffscreenRedirect offscreen_redirect; + + /* This is an internal effect used to implement the + offscreen-redirect property */ + ClutterEffect *flatten_effect; + ClutterActor *parent_actor; GList *children; gint n_children; @@ -545,6 +552,8 @@ enum PROP_OPACITY, + PROP_OFFSCREEN_REDIRECT, + PROP_VISIBLE, PROP_MAPPED, PROP_REALIZED, @@ -3017,6 +3026,10 @@ clutter_actor_set_property (GObject *object, clutter_actor_set_opacity (actor, g_value_get_uint (value)); break; + case PROP_OFFSCREEN_REDIRECT: + clutter_actor_set_offscreen_redirect (actor, g_value_get_enum (value)); + break; + case PROP_NAME: clutter_actor_set_name (actor, g_value_get_string (value)); break; @@ -3308,6 +3321,10 @@ clutter_actor_get_property (GObject *object, g_value_set_uint (value, priv->opacity); break; + case PROP_OFFSCREEN_REDIRECT: + g_value_set_enum (value, priv->offscreen_redirect); + break; + case PROP_NAME: g_value_set_string (value, priv->name); break; @@ -3544,6 +3561,12 @@ clutter_actor_dispose (GObject *object) 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_OBJECT_CLASS (clutter_actor_parent_class)->dispose (object); @@ -4016,6 +4039,26 @@ clutter_actor_class_init (ClutterActorClass *klass) obj_props[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: * @@ -5061,6 +5104,7 @@ clutter_actor_init (ClutterActor *self) priv->parent_actor = NULL; priv->has_clip = FALSE; priv->opacity = 0xff; + priv->offscreen_redirect = CLUTTER_OFFSCREEN_REDIRECT_NEVER; priv->id = _clutter_context_acquire_id (self); priv->pick_id = -1; priv->scale_x = 1.0; @@ -7259,7 +7303,16 @@ clutter_actor_set_opacity (ClutterActor *self, { 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]); } @@ -7350,6 +7403,131 @@ clutter_actor_get_opacity (ClutterActor *self) 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. + * + *
+ * Sample of using an offscreen redirect for transparency + * + *
+ * + * 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: * @self: A #ClutterActor diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h index 32096175f..06e1ae73a 100644 --- a/clutter/clutter-actor.h +++ b/clutter/clutter-actor.h @@ -116,6 +116,26 @@ typedef enum CLUTTER_ACTOR_NO_LAYOUT = 1 << 5 } 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: * @CLUTTER_ALLOCATION_NONE: No flag set @@ -424,6 +444,10 @@ guint8 clutter_actor_get_opacity (ClutterActor guint8 clutter_actor_get_paint_opacity (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, const gchar *name); diff --git a/clutter/clutter-flatten-effect.c b/clutter/clutter-flatten-effect.c new file mode 100644 index 000000000..d844299a4 --- /dev/null +++ b/clutter/clutter-flatten-effect.c @@ -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 . + * + * Authors: + * Neil Roberts + */ + +/* 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; + } +} diff --git a/clutter/clutter-flatten-effect.h b/clutter/clutter-flatten-effect.h new file mode 100644 index 000000000..7508f8ce8 --- /dev/null +++ b/clutter/clutter-flatten-effect.h @@ -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 . + * + * Authors: + * Neil Roberts + */ + +#ifndef __CLUTTER_FLATTEN_EFFECT_H__ +#define __CLUTTER_FLATTEN_EFFECT_H__ + +#include + +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__ */ diff --git a/doc/reference/clutter/Makefile.am b/doc/reference/clutter/Makefile.am index 82b8a83f3..648013bc0 100644 --- a/doc/reference/clutter/Makefile.am +++ b/doc/reference/clutter/Makefile.am @@ -122,7 +122,8 @@ HTML_IMAGES=\ event-flow.png \ flow-layout-horizontal.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). # e.g. content_files=running.sgml building.sgml changes-2.0.sgml @@ -179,4 +180,5 @@ EXTRA_DIST += \ event-flow.png \ flow-layout-horizontal.png \ flow-layout-vertical.png \ - path-alpha-func.png + path-alpha-func.png \ + offscreen-redirect.png diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index 93c09d437..844b08ea8 100644 --- a/doc/reference/clutter/clutter-sections.txt +++ b/doc/reference/clutter/clutter-sections.txt @@ -341,6 +341,9 @@ clutter_actor_get_z_rotation_gravity clutter_actor_is_rotated clutter_actor_set_opacity clutter_actor_get_opacity +ClutterOffscreenRedirect +clutter_actor_set_offscreen_redirect +clutter_actor_get_offscreen_redirect clutter_actor_set_name clutter_actor_get_name clutter_actor_get_gid diff --git a/doc/reference/clutter/offscreen-redirect.png b/doc/reference/clutter/offscreen-redirect.png new file mode 100644 index 000000000..ff36c8082 Binary files /dev/null and b/doc/reference/clutter/offscreen-redirect.png differ