effect: Add OffscreenEffect
authorEmmanuele Bassi <ebassi@linux.intel.com>
Mon, 17 May 2010 10:39:27 +0000 (11:39 +0100)
committerEmmanuele Bassi <ebassi@linux.intel.com>
Thu, 3 Jun 2010 13:10:55 +0000 (14:10 +0100)
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
clutter/clutter-offscreen-effect.c [new file with mode: 0644]
clutter/clutter-offscreen-effect.h [new file with mode: 0644]
clutter/clutter.h

index 676df67..b12a2d9 100644 (file)
@@ -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 (file)
index 0000000..f3bdc33
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ *   Emmanuele Bassi <ebassi@linux.intel.com>
+ */
+
+/**
+ * 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.
+ *
+ * <refsect2 id="ClutterOffscreenEffect-implementing">
+ *   <title>Implementing a ClutterOffscreenEffect</title>
+ *   <para>Creating a sub-class of #ClutterOffscreenEffect requires, in case
+ *   of overriding the #ClutterEffect virtual functions, to chain up to the
+ *   #ClutterOffscreenEffect's implementation.</para>
+ *   <para>On top of the #ClutterEffect's virtual functions,
+ *   #ClutterOffscreenEffect also provides a <function>paint_target()</function>
+ *   function, which encapsulates the effective painting of the texture that
+ *   contains the result of the offscreen redirection.</para>
+ * </refsect2>
+ *
+ * #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 (file)
index 0000000..df6026f
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ *   Emmanuele Bassi <ebassi@linux.intel.com>
+ */
+
+#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
+#error "Only <clutter/clutter.h> can be included directly."
+#endif
+
+#ifndef __CLUTTER_OFFSCREEN_EFFECT_H__
+#define __CLUTTER_OFFSCREEN_EFFECT_H__
+
+#include <clutter/clutter-effect.h>
+
+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__ */
index a9dc992..c7d9d9c 100644 (file)
@@ -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"