From c3ab32ae6887a222472f4fc9025437ab91d0bbce Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Mon, 17 May 2010 11:39:27 +0100 Subject: [PATCH] effect: Add OffscreenEffect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OffscreenEffect class is meant to be used to implement Effect sub-classes that create an offscreen framebuffer and redirect the actor's paint sequence there. The OffscreenEffect is useful for effects using fragment shaders. Any shader-based effect being applied to an actor through an offscreen buffer should be used before painting the resulting target material and not for every actor. This means that doing: pre_paint: cogl_program_use(program) set up offscreen buffer paint: [ actors ] → offscreen buffer → target material post_paint: paint target material cogl_program_use(null) Is not correct. Unfortunately, we cannot really do: post_paint: cogl_program_use(program) paint target material cogl_program_use(null) Because the OffscreenEffect::post_paint() implementation also pops the offscreen buffer and re-instates the previous framebuffer: post_paint: cogl_program_use(program) change frame buffer ← ouch! paint target material cogl_program_use(null) One way to fix it is to allow using the shader right before painting the target material - which means adding a new virtual inside the OffscreenEffect class vtable in additions to the ones defined by the parent Effect class. The newly-added paint_target() virtual allows the correct sequence of actions by adding an entry point for sub-classes to wrap the "paint target material" operation with custom code, in order to implement the case above correctly as: post_paint: change frame buffer cogl_program_use(program) paint target material cogl_program_use(null) The added upside is that sub-classes of OffscreenEffect involving shaders really just need to override the prepare() and paint_target() virtuals, since the pre_paint() and post_paint() do all that's needed. --- clutter/Makefile.am | 2 + clutter/clutter-offscreen-effect.c | 336 +++++++++++++++++++++++++++++ clutter/clutter-offscreen-effect.h | 96 +++++++++ clutter/clutter.h | 1 + 4 files changed, 435 insertions(+) create mode 100644 clutter/clutter-offscreen-effect.c create mode 100644 clutter/clutter-offscreen-effect.h diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 676df678e..b12a2d926 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -113,6 +113,7 @@ source_h = \ $(srcdir)/clutter-main.h \ $(srcdir)/clutter-media.h \ $(srcdir)/clutter-model.h \ + $(srcdir)/clutter-offscreen-effect.h \ $(srcdir)/clutter-path.h \ $(srcdir)/clutter-rectangle.h \ $(srcdir)/clutter-score.h \ @@ -197,6 +198,7 @@ source_c = \ $(srcdir)/clutter-master-clock.c \ $(srcdir)/clutter-media.c \ $(srcdir)/clutter-model.c \ + $(srcdir)/clutter-offscreen-effect.c \ $(srcdir)/clutter-path.c \ $(srcdir)/clutter-rectangle.c \ $(srcdir)/clutter-score.c \ diff --git a/clutter/clutter-offscreen-effect.c b/clutter/clutter-offscreen-effect.c new file mode 100644 index 000000000..f3bdc336d --- /dev/null +++ b/clutter/clutter-offscreen-effect.c @@ -0,0 +1,336 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2010 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 . + * + * Author: + * Emmanuele Bassi + */ + +/** + * SECTION:clutter-offscreen-effect + * @short_description: Base class for effects using offscreen buffers + * @see_also: #ClutterBlurEffect, #ClutterEffect + * + * #ClutterOffscreenEffect is an abstract class that can be used by + * #ClutterEffect sub-classes requiring access to an offscreen buffer. + * + * Some effects, like the fragment shader based effects, can only use GL + * textures, and in order to apply those effects to any kind of actor they + * require that all drawing operations are applied to an offscreen framebuffer + * that gets redirected to a texture. + * + * #ClutterOffscreenEffect provides all the heavy-lifting for creating the + * offscreen framebuffer, the redirection and the final paint of the texture on + * the desired stage. + * + * + * Implementing a ClutterOffscreenEffect + * Creating a sub-class of #ClutterOffscreenEffect requires, in case + * of overriding the #ClutterEffect virtual functions, to chain up to the + * #ClutterOffscreenEffect's implementation. + * On top of the #ClutterEffect's virtual functions, + * #ClutterOffscreenEffect also provides a paint_target() + * function, which encapsulates the effective painting of the texture that + * contains the result of the offscreen redirection. + * + * + * #ClutterOffscreenEffect is available since Clutter 1.4 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "clutter-offscreen-effect.h" + +#include "cogl/cogl.h" + +#include "clutter-debug.h" +#include "clutter-private.h" + +struct _ClutterOffscreenEffectPrivate +{ + CoglHandle offscreen; + CoglHandle target; + + ClutterActor *actor; +}; + +G_DEFINE_ABSTRACT_TYPE (ClutterOffscreenEffect, + clutter_offscreen_effect, + CLUTTER_TYPE_EFFECT); + +static void +clutter_offscreen_effect_set_actor (ClutterActorMeta *meta, + ClutterActor *actor) +{ + ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (meta); + ClutterOffscreenEffectPrivate *priv = self->priv; + ClutterActorMetaClass *meta_class; + ClutterPerspective perspective; + gfloat width, height, z_camera; + ClutterActorBox allocation; + gfloat fb_width, fb_height; + ClutterActor *stage; + CoglHandle texture; + CoglMatrix matrix; + + meta_class = CLUTTER_ACTOR_META_CLASS (clutter_offscreen_effect_parent_class); + meta_class->set_actor (meta, actor); + + /* clear out the previous state */ + if (priv->offscreen != COGL_INVALID_HANDLE) + { + cogl_handle_unref (priv->offscreen); + priv->offscreen = COGL_INVALID_HANDLE; + } + + if (priv->target != COGL_INVALID_HANDLE) + { + cogl_handle_unref (priv->target); + priv->target = COGL_INVALID_HANDLE; + } + + /* we keep a back pointer here, to avoid going through the ActorMeta */ + priv->actor = clutter_actor_meta_get_actor (meta); + if (priv->actor == NULL) + return; + + stage = clutter_actor_get_stage (priv->actor); + if (stage == NULL) + { + CLUTTER_NOTE (MISC, "The actor '%s' is not part of a stage", + clutter_actor_get_name (actor) == NULL + ? G_OBJECT_TYPE_NAME (actor) + : clutter_actor_get_name (actor)); + + /* we forcibly disable the effect here */ + clutter_actor_meta_set_enabled (meta, FALSE); + return; + } + + clutter_stage_get_perspective (CLUTTER_STAGE (stage), &perspective); + clutter_actor_get_allocation_box (stage, &allocation); + clutter_actor_box_get_size (&allocation, &fb_width, &fb_height); + + clutter_actor_get_allocation_box (priv->actor, &allocation); + clutter_actor_box_get_size (&allocation, &width, &height); + + priv->target = cogl_material_new (); + + texture = cogl_texture_new_with_size (MAX (width, 1), MAX (height, 1), + COGL_TEXTURE_NO_SLICING, + COGL_PIXEL_FORMAT_RGBA_8888_PRE); + cogl_material_set_layer (priv->target, 0, texture); + cogl_handle_unref (texture); + + cogl_material_set_layer_filters (priv->target, 0, + COGL_MATERIAL_FILTER_LINEAR, + COGL_MATERIAL_FILTER_LINEAR); + + priv->offscreen = cogl_offscreen_new_to_texture (texture); + + if (priv->offscreen == COGL_INVALID_HANDLE) + { + g_warning ("%s: Unable to create an Offscreen buffer", G_STRLOC); + + /* we forcibly disable the effect here */ + clutter_actor_meta_set_enabled (meta, FALSE); + return; + } + + cogl_push_framebuffer (priv->offscreen); + + width = cogl_texture_get_width (texture); + height = cogl_texture_get_height (texture); + fb_width /= fb_width / width; + fb_height /= fb_height / height; + + cogl_set_viewport (0, 0, width, height); + cogl_perspective (perspective.fovy, + perspective.aspect, + perspective.z_near, + perspective.z_far); + + cogl_get_projection_matrix (&matrix); + z_camera = 0.5 * matrix.xx; + + cogl_matrix_init_identity (&matrix); + cogl_matrix_translate (&matrix, -0.5f, -0.5f, -z_camera); + cogl_matrix_scale (&matrix, + 1.0f / fb_width, + -1.0f / fb_height, + 1.0f / fb_width); + cogl_matrix_translate (&matrix, 0.0f, -1.0f * fb_height, 0.0f); + cogl_set_modelview_matrix (&matrix); + + cogl_pop_framebuffer (); +} + +static gboolean +clutter_offscreen_effect_pre_paint (ClutterEffect *effect) +{ + ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect); + ClutterOffscreenEffectPrivate *priv = self->priv; + + if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect))) + return FALSE; + + if (priv->offscreen != COGL_INVALID_HANDLE) + { + CoglColor transparent; + + cogl_push_framebuffer (priv->offscreen); + cogl_push_matrix (); + + cogl_color_set_from_4ub (&transparent, 0, 0, 0, 0); + cogl_clear (&transparent, + COGL_BUFFER_BIT_COLOR | + COGL_BUFFER_BIT_STENCIL | + COGL_BUFFER_BIT_DEPTH); + + return TRUE; + } + + return FALSE; +} + +static void +clutter_offscreen_effect_real_paint_target (ClutterOffscreenEffect *effect) +{ + ClutterOffscreenEffectPrivate *priv = effect->priv; + ClutterActorBox allocation; + gfloat width, height; + guint8 paint_opacity; + + paint_opacity = clutter_actor_get_paint_opacity (priv->actor); + + clutter_actor_get_allocation_box (priv->actor, &allocation); + clutter_actor_box_get_size (&allocation, &width, &height); + + cogl_material_set_color4ub (priv->target, + paint_opacity, + paint_opacity, + paint_opacity, + paint_opacity); + cogl_set_source (priv->target); + cogl_rectangle_with_texture_coords (0, 0, width, height, + 0.0, 0.0, + 1.0, 1.0); +} + +static void +clutter_offscreen_effect_post_paint (ClutterEffect *effect) +{ + ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect); + ClutterOffscreenEffectPrivate *priv = self->priv; + + if (priv->offscreen != COGL_INVALID_HANDLE && + priv->target != COGL_INVALID_HANDLE && + priv->actor != NULL) + { + cogl_pop_matrix (); + cogl_pop_framebuffer (); + + /* paint the target material; this is virtualized for + * sub-classes that require special hand-holding + */ + clutter_offscreen_effect_paint_target (self); + } +} + +static void +clutter_offscreen_effect_finalize (GObject *gobject) +{ + ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (gobject); + ClutterOffscreenEffectPrivate *priv = self->priv; + + if (priv->offscreen) + cogl_handle_unref (priv->offscreen); + + if (priv->target) + cogl_handle_unref (priv->target); + + G_OBJECT_CLASS (clutter_offscreen_effect_parent_class)->finalize (gobject); +} + +static void +clutter_offscreen_effect_class_init (ClutterOffscreenEffectClass *klass) +{ + ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass); + ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (ClutterOffscreenEffectPrivate)); + + klass->paint_target = clutter_offscreen_effect_real_paint_target; + + meta_class->set_actor = clutter_offscreen_effect_set_actor; + + effect_class->pre_paint = clutter_offscreen_effect_pre_paint; + effect_class->post_paint = clutter_offscreen_effect_post_paint; + + gobject_class->finalize = clutter_offscreen_effect_finalize; +} + +static void +clutter_offscreen_effect_init (ClutterOffscreenEffect *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + CLUTTER_TYPE_OFFSCREEN_EFFECT, + ClutterOffscreenEffectPrivate); +} + +/** + * clutter_offscreen_effect_get_target: + * @effect: a #ClutterOffscreenEffect + * + * Retrieves the material used as a render target for the offscreen + * buffer created by @effect + * + * Return value: (transfer none): a handle for a #CoglMaterial, or + * %COGL_INVALID_HANDLE. The returned handle is owned by Clutter + * and it should not be modified or freed + * + * Since: 1.4 + */ +CoglHandle +clutter_offscreen_effect_get_target (ClutterOffscreenEffect *effect) +{ + g_return_val_if_fail (CLUTTER_IS_OFFSCREEN_EFFECT (effect), + COGL_INVALID_HANDLE); + + return effect->priv->target; +} + +/** + * clutter_offscreen_effect_paint_target: + * @effect: a #ClutterOffscreenEffect + * + * Calls the paint_target() virtual function of the @effect + * + * Since: 1.4 + */ +void +clutter_offscreen_effect_paint_target (ClutterOffscreenEffect *effect) +{ + g_return_if_fail (CLUTTER_IS_OFFSCREEN_EFFECT (effect)); + + CLUTTER_OFFSCREEN_EFFECT_GET_CLASS (effect)->paint_target (effect); +} diff --git a/clutter/clutter-offscreen-effect.h b/clutter/clutter-offscreen-effect.h new file mode 100644 index 000000000..df6026f2a --- /dev/null +++ b/clutter/clutter-offscreen-effect.h @@ -0,0 +1,96 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2010 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 . + * + * Author: + * Emmanuele Bassi + */ + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_OFFSCREEN_EFFECT_H__ +#define __CLUTTER_OFFSCREEN_EFFECT_H__ + +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_OFFSCREEN_EFFECT (clutter_offscreen_effect_get_type ()) +#define CLUTTER_OFFSCREEN_EFFECT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CLUTTER_TYPE_OFFSCREEN_EFFECT, ClutterOffscreenEffect)) +#define CLUTTER_IS_OFFSCREEN_EFFECT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CLUTTER_TYPE_OFFSCREEN_EFFECT)) +#define CLUTTER_OFFSCREEN_EFFECT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CLUTTER_TYPE_OFFSCREEN_EFFECT, ClutterOffscreenEffectClass)) +#define CLUTTER_IS_OFFSCREEN_EFFECT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CLUTTER_TYPE_OFFSCREEN_EFFECT)) +#define CLUTTER_OFFSCREEN_EFFECT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CLUTTER_TYPE_OFFSCREEN_EFFECT, ClutterOffscreenEffectClass)) + +typedef struct _ClutterOffscreenEffect ClutterOffscreenEffect; +typedef struct _ClutterOffscreenEffectPrivate ClutterOffscreenEffectPrivate; +typedef struct _ClutterOffscreenEffectClass ClutterOffscreenEffectClass; + +/** + * ClutterOffscreenEffect: + * + * The #ClutterOffscreenEffect structure contains only private data + * and should be accessed using the provided API + * + * Since: 1.4 + */ +struct _ClutterOffscreenEffect +{ + /*< private >*/ + ClutterEffect parent_instance; + + ClutterOffscreenEffectPrivate *priv; +}; + +/** + * ClutterOffscreenEffectClass: + * @paint_target: virtual function + * + * The #ClutterOffscreenEffectClass structure contains only private data + * + * Since: 1.4 + */ +struct _ClutterOffscreenEffectClass +{ + /*< private >*/ + ClutterEffectClass parent_class; + + /*< public >*/ + void (* paint_target) (ClutterOffscreenEffect *effect); + + /*< private >*/ + void (* _clutter_offscreen1) (void); + void (* _clutter_offscreen2) (void); + void (* _clutter_offscreen3) (void); + void (* _clutter_offscreen4) (void); + void (* _clutter_offscreen5) (void); + void (* _clutter_offscreen6) (void); + void (* _clutter_offscreen7) (void); +}; + +GType clutter_offscreen_effect_get_type (void) G_GNUC_CONST; + +CoglHandle clutter_offscreen_effect_get_target (ClutterOffscreenEffect *effect); +void clutter_offscreen_effect_paint_target (ClutterOffscreenEffect *effect); + +G_END_DECLS + +#endif /* __CLUTTER_OFFSCREEN_EFFECT_H__ */ diff --git a/clutter/clutter.h b/clutter/clutter.h index a9dc99263..c7d9d9c6a 100644 --- a/clutter/clutter.h +++ b/clutter/clutter.h @@ -76,6 +76,7 @@ #include "clutter-main.h" #include "clutter-media.h" #include "clutter-model.h" +#include "clutter-offscreen-effect.h" #include "clutter-path.h" #include "clutter-rectangle.h" #include "clutter-score.h"