[clutter/wip/gaussian-blur] blur-effect: Use a gaussian blur



commit 0627bb65089efdc49af05de2556bfd3e99db55d8
Author: Neil Roberts <neil linux intel com>
Date:   Wed Feb 15 09:41:20 2012 +0000

    blur-effect: Use a gaussian blur
    
    This replaces the 3x3 box blur of ClutterBlurEffect with a two-pass
    gaussian blur. The sigma value for the gaussian function (ie, the
    bluriness) can be adjusted with a property. The radius for sampling is
    limited to â6ÏâÃâ6Ïâ as suggested by Wikipedia. The factors for the
    sampling are stored in an array in a uniform so the effect only needs
    to switch shaders when the sigma jumps to the next radius size. This
    makes it feasible to animate the bluriness without depending on
    conditional jumps in the shader.
    
    The effect creates a second texture at the same size as the
    offscreen's texture to implement the second pass. The effect avoids
    redrawing the first pass if the actor is redrawn without being
    dirtied. However if the sigma value changes it does need to redraw
    both passes but it can still avoid repainting the actual actor.
    
    The two passes share a single shader program with a uniform to
    change the direction. The pipelines for each radius are cached in a
    hash table attached to the class struct so they can be shared across
    multiple instances of the effect.

 clutter/clutter-blur-effect.c |  466 ++++++++++++++++++++++++++++++++++-------
 clutter/clutter-blur-effect.h |    4 +
 2 files changed, 398 insertions(+), 72 deletions(-)
---
diff --git a/clutter/clutter-blur-effect.c b/clutter/clutter-blur-effect.c
index 69811b1..e660287 100644
--- a/clutter/clutter-blur-effect.c
+++ b/clutter/clutter-blur-effect.c
@@ -3,7 +3,7 @@
  *
  * An OpenGL based 'interactive canvas' library.
  *
- * Copyright (C) 2010  Intel Corporation.
+ * Copyright (C) 2010, 2012  Intel Corporation.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -20,6 +20,7 @@
  *
  * Author:
  *   Emmanuele Bassi <ebassi linux intel com>
+ *   Neil Roberts <neil linux intel com>
  */
 
 /**
@@ -41,6 +42,8 @@
 #include "config.h"
 #endif
 
+#include <math.h>
+
 #include "clutter-blur-effect.h"
 
 #include "cogl/cogl.h"
@@ -51,30 +54,6 @@
 
 #define BLUR_PADDING    2
 
-/* FIXME - lame shader; we should really have a decoupled
- * horizontal/vertical two pass shader for the gaussian blur
- */
-static const gchar *box_blur_glsl_declarations =
-"uniform vec2 pixel_step;\n";
-/* FIXME: Is this shader right? It is doing 10 samples (ie, sampling
-   the middle texel twice) and then only dividing by 9 */
-#define SAMPLE(offx, offy) \
-  "cogl_texel += texture2D (cogl_sampler, cogl_tex_coord.st + pixel_step * " \
-  "vec2 (" G_STRINGIFY (offx) ", " G_STRINGIFY (offy) ") * 2.0);\n"
-static const gchar *box_blur_glsl_shader =
-"  cogl_texel = texture2D (cogl_sampler, cogl_tex_coord.st);\n"
-  SAMPLE (-1.0, -1.0)
-  SAMPLE ( 0.0, -1.0)
-  SAMPLE (+1.0, -1.0)
-  SAMPLE (-1.0,  0.0)
-  SAMPLE ( 0.0,  0.0)
-  SAMPLE (+1.0,  0.0)
-  SAMPLE (-1.0, +1.0)
-  SAMPLE ( 0.0, +1.0)
-  SAMPLE (+1.0, +1.0)
-"  cogl_texel /= 9.0;\n";
-#undef SAMPLE
-
 struct _ClutterBlurEffect
 {
   ClutterOffscreenEffect parent_instance;
@@ -82,21 +61,44 @@ struct _ClutterBlurEffect
   /* a back pointer to our actor, so that we can query it */
   ClutterActor *actor;
 
-  gint pixel_step_uniform;
+  gint radius;
+  gfloat sigma;
 
   gint tex_width;
   gint tex_height;
 
-  CoglPipeline *pipeline;
+  gboolean vertical_texture_dirty;
+
+  CoglHandle horizontal_texture;
+  CoglHandle vertical_texture;
+  CoglHandle vertical_fbo;
+
+  CoglPipeline *horizontal_pipeline;
+  gint horizontal_pixel_step_uniform;
+  gint horizontal_factors_uniform;
+  CoglPipeline *vertical_pipeline;
+  gint vertical_pixel_step_uniform;
+  gint vertical_factors_uniform;
 };
 
 struct _ClutterBlurEffectClass
 {
   ClutterOffscreenEffectClass parent_class;
 
-  CoglPipeline *base_pipeline;
+  GHashTable *pipeline_cache;
 };
 
+enum
+{
+  PROP_0,
+
+  PROP_SIGMA,
+
+  PROP_LAST
+};
+
+static GParamSpec *obj_props[PROP_LAST];
+
 G_DEFINE_TYPE (ClutterBlurEffect,
                clutter_blur_effect,
                CLUTTER_TYPE_OFFSCREEN_EFFECT);
@@ -127,36 +129,172 @@ clutter_blur_effect_pre_paint (ClutterEffect *effect)
     }
 
   parent_class = CLUTTER_EFFECT_CLASS (clutter_blur_effect_parent_class);
-  if (parent_class->pre_paint (effect))
+  return parent_class->pre_paint (effect);
+}
+
+static CoglPipeline *
+get_blur_pipeline (ClutterBlurEffectClass *klass,
+                   int                     radius)
+{
+  CoglPipeline *pipeline;
+
+  /* This generates a pipeline using a snippet to sample a line of
+     samples with the given radius. The same snippet is used for both
+     the horizontal and vertical phases. The snippet is made vertical
+     by changing the direction in the 2-component âpixel_stepâ
+     uniform. The samples are multiplied by some factors stored in a
+     uniform array before being added together so that if the radius
+     doesn't change then the effect can reuse the same snippets. */
+
+  pipeline = g_hash_table_lookup (klass->pipeline_cache,
+                                  GINT_TO_POINTER (radius));
+
+  if (pipeline == NULL)
     {
-      ClutterOffscreenEffect *offscreen_effect =
-        CLUTTER_OFFSCREEN_EFFECT (effect);
-      CoglHandle texture;
+      CoglSnippet *snippet;
+      GString *source = g_string_new (NULL);
+      int i;
 
-      texture = clutter_offscreen_effect_get_texture (offscreen_effect);
-      self->tex_width = cogl_texture_get_width (texture);
-      self->tex_height = cogl_texture_get_height (texture);
+      g_string_append_printf (source,
+                              "uniform vec2 pixel_step;\n"
+                              "uniform float factors[%i];\n",
+                              radius * 2 + 1);
 
-      if (self->pixel_step_uniform > -1)
-        {
-          gfloat pixel_step[2];
+      snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_TEXTURE_LOOKUP,
+                                  source->str,
+                                  NULL /* post */);
 
-          pixel_step[0] = 1.0f / self->tex_width;
-          pixel_step[1] = 1.0f / self->tex_height;
+      g_string_set_size (source, 0);
 
-          cogl_pipeline_set_uniform_float (self->pipeline,
-                                           self->pixel_step_uniform,
-                                           2, /* n_components */
-                                           1, /* count */
-                                           pixel_step);
+      pipeline = cogl_pipeline_new ();
+      cogl_pipeline_set_layer_null_texture (pipeline,
+                                            0, /* layer_num */
+                                            COGL_TEXTURE_TYPE_2D);
+      cogl_pipeline_set_layer_wrap_mode (pipeline,
+                                         0, /* layer_num */
+                                         COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE);
+      cogl_pipeline_set_layer_filters (pipeline,
+                                       0, /* layer_num */
+                                       COGL_PIPELINE_FILTER_NEAREST,
+                                       COGL_PIPELINE_FILTER_NEAREST);
+
+      for (i = 0; i < radius * 2 + 1; i++)
+        {
+          g_string_append (source, "cogl_texel ");
+
+          if (i == 0)
+            g_string_append (source, "=");
+          else
+            g_string_append (source, "+=");
+
+          g_string_append_printf (source,
+                                  " texture2D (cogl_sampler, "
+                                  "cogl_tex_coord.st");
+          if (i != radius)
+            g_string_append_printf (source,
+                                    " + pixel_step * %f",
+                                    (float) (i - radius));
+          g_string_append_printf (source,
+                                  ") * factors[%i];\n",
+                                  i);
         }
 
-      cogl_pipeline_set_layer_texture (self->pipeline, 0, texture);
+      cogl_snippet_set_replace (snippet, source->str);
+
+      g_string_free (source, TRUE);
+
+      cogl_pipeline_add_layer_snippet (pipeline, 0, snippet);
 
-      return TRUE;
+      cogl_object_unref (snippet);
+
+      g_hash_table_insert (klass->pipeline_cache,
+                           GINT_TO_POINTER (radius),
+                           pipeline);
     }
-  else
-    return FALSE;
+
+  return pipeline;
+}
+
+static void
+update_horizontal_pipeline_texture (ClutterBlurEffect *self)
+{
+  float pixel_step[2];
+
+  cogl_pipeline_set_layer_texture (self->horizontal_pipeline,
+                                   0, /* layer_num */
+                                   self->horizontal_texture);
+  pixel_step[0] = 1.0f / self->tex_width;
+  pixel_step[1] = 0.0f;
+  cogl_pipeline_set_uniform_float (self->horizontal_pipeline,
+                                   self->horizontal_pixel_step_uniform,
+                                   2, /* n_components */
+                                   1, /* count */
+                                   pixel_step);
+}
+
+static void
+update_vertical_pipeline_texture (ClutterBlurEffect *self)
+{
+  float pixel_step[2];
+
+  cogl_pipeline_set_layer_texture (self->vertical_pipeline,
+                                   0, /* layer_num */
+                                   self->vertical_texture);
+  pixel_step[0] = 0.0f;
+  pixel_step[1] = 1.0f / self->tex_height;
+  cogl_pipeline_set_uniform_float (self->vertical_pipeline,
+                                   self->vertical_pixel_step_uniform,
+                                   2, /* n_components */
+                                   1, /* count */
+                                   pixel_step);
+}
+
+static void
+clutter_blur_effect_post_paint (ClutterEffect *effect)
+{
+  ClutterBlurEffect *self = CLUTTER_BLUR_EFFECT (effect);
+  ClutterEffectClass *parent_class;
+  CoglHandle horizontal_texture;
+
+  horizontal_texture =
+    clutter_offscreen_effect_get_texture (CLUTTER_OFFSCREEN_EFFECT (effect));
+
+  if (horizontal_texture != self->horizontal_texture)
+    {
+      if (self->horizontal_texture)
+        cogl_object_unref (self->horizontal_texture);
+      self->horizontal_texture = cogl_object_ref (horizontal_texture);
+
+      self->tex_width = cogl_texture_get_width (horizontal_texture);
+      self->tex_height = cogl_texture_get_height (horizontal_texture);
+
+      update_horizontal_pipeline_texture (self);
+
+      if (self->vertical_texture == NULL ||
+          self->tex_width != cogl_texture_get_width (self->vertical_texture) ||
+          self->tex_height != cogl_texture_get_height (self->vertical_texture))
+        {
+          if (self->vertical_texture)
+            {
+              cogl_object_unref (self->vertical_texture);
+              cogl_object_unref (self->vertical_fbo);
+            }
+
+          self->vertical_texture =
+            cogl_texture_new_with_size (self->tex_width, self->tex_height,
+                                        COGL_TEXTURE_NO_SLICING,
+                                        COGL_PIXEL_FORMAT_RGBA_8888_PRE);
+          self->vertical_fbo =
+            cogl_offscreen_new_to_texture (self->vertical_texture);
+
+          update_vertical_pipeline_texture (self);
+        }
+    }
+
+  self->vertical_texture_dirty = TRUE;
+
+  parent_class = CLUTTER_EFFECT_CLASS (clutter_blur_effect_parent_class);
+  parent_class->post_paint (effect);
 }
 
 static void
@@ -165,14 +303,25 @@ clutter_blur_effect_paint_target (ClutterOffscreenEffect *effect)
   ClutterBlurEffect *self = CLUTTER_BLUR_EFFECT (effect);
   guint8 paint_opacity;
 
+  if (self->vertical_texture_dirty)
+    {
+      cogl_push_framebuffer (self->vertical_fbo);
+      cogl_push_source (self->horizontal_pipeline);
+      cogl_rectangle (-1.0f, 1.0f, 1.0f, -1.0f);
+      cogl_pop_source ();
+      cogl_pop_framebuffer ();
+
+      self->vertical_texture_dirty = FALSE;
+    }
+
   paint_opacity = clutter_actor_get_paint_opacity (self->actor);
 
-  cogl_pipeline_set_color4ub (self->pipeline,
+  cogl_pipeline_set_color4ub (self->vertical_pipeline,
                               paint_opacity,
                               paint_opacity,
                               paint_opacity,
                               paint_opacity);
-  cogl_push_source (self->pipeline);
+  cogl_push_source (self->vertical_pipeline);
 
   cogl_rectangle (0, 0, self->tex_width, self->tex_height);
 
@@ -206,16 +355,82 @@ clutter_blur_effect_dispose (GObject *gobject)
 {
   ClutterBlurEffect *self = CLUTTER_BLUR_EFFECT (gobject);
 
-  if (self->pipeline != NULL)
+  if (self->horizontal_pipeline != NULL)
     {
-      cogl_object_unref (self->pipeline);
-      self->pipeline = NULL;
+      cogl_object_unref (self->horizontal_pipeline);
+      self->horizontal_pipeline = NULL;
+    }
+
+  if (self->vertical_pipeline != NULL)
+    {
+      cogl_object_unref (self->vertical_pipeline);
+      self->vertical_pipeline = NULL;
+    }
+
+  if (self->horizontal_texture)
+    {
+      cogl_object_unref (self->horizontal_texture);
+      self->horizontal_texture = NULL;
+    }
+
+  if (self->vertical_texture)
+    {
+      cogl_object_unref (self->vertical_texture);
+      self->vertical_texture = NULL;
+    }
+
+  if (self->vertical_fbo)
+    {
+      cogl_object_unref (self->vertical_fbo);
+      self->vertical_fbo = NULL;
     }
 
   G_OBJECT_CLASS (clutter_blur_effect_parent_class)->dispose (gobject);
 }
 
 static void
+clutter_blur_effect_set_property (GObject      *gobject,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  ClutterBlurEffect *effect = CLUTTER_BLUR_EFFECT (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_SIGMA:
+      clutter_blur_effect_set_sigma (effect,
+                                     g_value_get_float (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_blur_effect_get_property (GObject      *gobject,
+                                  guint         prop_id,
+                                  GValue       *value,
+                                  GParamSpec   *pspec)
+{
+  ClutterBlurEffect *effect = CLUTTER_BLUR_EFFECT (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_SIGMA:
+      g_value_set_float (value,
+                         clutter_blur_effect_get_sigma (effect));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
 clutter_blur_effect_class_init (ClutterBlurEffectClass *klass)
 {
   ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass);
@@ -223,41 +438,148 @@ clutter_blur_effect_class_init (ClutterBlurEffectClass *klass)
   ClutterOffscreenEffectClass *offscreen_class;
 
   gobject_class->dispose = clutter_blur_effect_dispose;
+  gobject_class->set_property = clutter_blur_effect_set_property;
+  gobject_class->get_property = clutter_blur_effect_get_property;
 
   effect_class->pre_paint = clutter_blur_effect_pre_paint;
+  effect_class->post_paint = clutter_blur_effect_post_paint;
   effect_class->get_paint_volume = clutter_blur_effect_get_paint_volume;
 
   offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);
   offscreen_class->paint_target = clutter_blur_effect_paint_target;
+
+
+  obj_props[PROP_SIGMA] =
+    g_param_spec_float ("sigma",
+                        P_("Sigma"),
+                        P_("The sigma value for the gaussian function"),
+                        0.0, 10.0,
+                        1.0,
+                        CLUTTER_PARAM_READWRITE);
+  g_object_class_install_properties (gobject_class,
+                                     PROP_LAST,
+                                     obj_props);
+
+  klass->pipeline_cache =
+    g_hash_table_new_full (g_direct_hash,
+                           g_direct_equal,
+                           NULL, /* key destroy notify */
+                           (GDestroyNotify) cogl_object_unref);
 }
 
 static void
-clutter_blur_effect_init (ClutterBlurEffect *self)
+clutter_blur_effect_set_sigma_real (ClutterBlurEffect *self,
+                                    gfloat             sigma)
 {
-  ClutterBlurEffectClass *klass = CLUTTER_BLUR_EFFECT_GET_CLASS (self);
+  int radius;
+  float *factors;
+  float sum = 0.0f;
+  int i;
 
-  if (G_UNLIKELY (klass->base_pipeline == NULL))
-    {
-      CoglSnippet *snippet;
+  if (self->sigma == sigma)
+    return;
 
-      klass->base_pipeline = cogl_pipeline_new ();
+  /* According to wikipedia, using a matrix with dimensions
+     â6ÏâÃâ6Ïâ gives good enough results in practice */
+  radius = floorf (ceilf (6 * sigma) / 2.0f);
 
-      snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_TEXTURE_LOOKUP,
-                                  box_blur_glsl_declarations,
-                                  NULL);
-      cogl_snippet_set_replace (snippet, box_blur_glsl_shader);
-      cogl_pipeline_add_layer_snippet (klass->base_pipeline, 0, snippet);
-      cogl_object_unref (snippet);
+  if (self->horizontal_pipeline && radius != self->radius)
+    {
+      cogl_object_unref (self->horizontal_pipeline);
+      self->horizontal_pipeline = NULL;
+      cogl_object_unref (self->vertical_pipeline);
+      self->vertical_pipeline = NULL;
+    }
 
-      cogl_pipeline_set_layer_null_texture (klass->base_pipeline,
-                                            0, /* layer number */
-                                            COGL_TEXTURE_TYPE_2D);
+  if (self->horizontal_pipeline == NULL)
+    {
+      CoglPipeline *base_pipeline =
+        get_blur_pipeline (CLUTTER_BLUR_EFFECT_GET_CLASS (self),
+                           radius);
+
+      self->horizontal_pipeline = cogl_pipeline_copy (base_pipeline);
+      self->horizontal_pixel_step_uniform =
+        cogl_pipeline_get_uniform_location (self->horizontal_pipeline,
+                                            "pixel_step");
+      self->horizontal_factors_uniform =
+        cogl_pipeline_get_uniform_location (self->horizontal_pipeline,
+                                            "factors");
+      update_horizontal_pipeline_texture (self);
+
+      self->vertical_pipeline = cogl_pipeline_copy (base_pipeline);
+      self->vertical_pixel_step_uniform =
+        cogl_pipeline_get_uniform_location (self->vertical_pipeline,
+                                            "pixel_step");
+      self->vertical_factors_uniform =
+        cogl_pipeline_get_uniform_location (self->vertical_pipeline,
+                                            "factors");
+      update_vertical_pipeline_texture (self);
+
+      /* To avoid needing to clear the vertical texture we going to
+         disable blending in the horizontal pipeline and just fill it
+         with the horizontal texture */
+      cogl_pipeline_set_blend (self->horizontal_pipeline,
+                               "RGBA = ADD (SRC_COLOR, 0)",
+                               NULL);
+    }
+
+  factors = g_alloca (sizeof (float) * (radius * 2 + 1));
+  /* Special case when the radius is zero to make it just draw the
+     image normally */
+  if (radius == 0)
+    factors[0] = 1.0f;
+  else
+    {
+      for (i = -radius; i <= radius; i++)
+        factors[i + radius] = (powf (G_E, -(i * i) / (2.0f * sigma * sigma))
+                               / sqrtf (2.0f * G_PI * sigma * sigma));
+      /* Normalize all of the factors */
+      for (i = -radius; i <= radius; i++)
+        sum += factors[i + radius];
+      for (i = -radius; i <= radius; i++)
+        factors[i + radius] /= sum;
     }
 
-  self->pipeline = cogl_pipeline_copy (klass->base_pipeline);
+  cogl_pipeline_set_uniform_float (self->horizontal_pipeline,
+                                   self->horizontal_factors_uniform,
+                                   1, /* n_components */
+                                   radius * 2 + 1,
+                                   factors);
+  cogl_pipeline_set_uniform_float (self->vertical_pipeline,
+                                   self->vertical_factors_uniform,
+                                   1, /* n_components */
+                                   radius * 2 + 1,
+                                   factors);
+
+  self->sigma = sigma;
+  self->radius = radius;
+
+  self->vertical_texture_dirty = TRUE;
+}
+
+void
+clutter_blur_effect_set_sigma (ClutterBlurEffect *self,
+                               gfloat             sigma)
+{
+  g_return_if_fail (CLUTTER_IS_BLUR_EFFECT (self));
+
+  clutter_blur_effect_set_sigma_real (self, sigma);
+  g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SIGMA]);
+  clutter_effect_queue_repaint (CLUTTER_EFFECT (self));
+}
+
+gfloat
+clutter_blur_effect_get_sigma (ClutterBlurEffect *self)
+{
+  g_return_val_if_fail (CLUTTER_IS_BLUR_EFFECT (self), 0.0f);
+
+  return self->sigma;
+}
 
-  self->pixel_step_uniform =
-    cogl_pipeline_get_uniform_location (self->pipeline, "pixel_step");
+static void
+clutter_blur_effect_init (ClutterBlurEffect *self)
+{
+  clutter_blur_effect_set_sigma_real (self, 0.84089642f);
 }
 
 /**
diff --git a/clutter/clutter-blur-effect.h b/clutter/clutter-blur-effect.h
index 27466bb..0807e32 100644
--- a/clutter/clutter-blur-effect.h
+++ b/clutter/clutter-blur-effect.h
@@ -52,6 +52,10 @@ GType clutter_blur_effect_get_type (void) G_GNUC_CONST;
 
 ClutterEffect *clutter_blur_effect_new (void);
 
+void   clutter_blur_effect_set_sigma (ClutterBlurEffect *effect,
+                                      gfloat             sigma);
+gfloat clutter_blur_effect_get_sigma (ClutterBlurEffect *effect);
+
 G_END_DECLS
 
 #endif /* __CLUTTER_BLUR_EFFECT_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]