summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNeil Roberts <neil@linux.intel.com>2012-02-15 09:41:20 +0000
committerNeil Roberts <neil@linux.intel.com>2012-09-03 15:07:44 +0100
commitc16b31e565e9acef713620131300cd8090607f69 (patch)
tree1b099e346c78739da101594046ae77b1d842c90d
parent03f4f1c69e7c66702cbed7e158a36668ae3683b9 (diff)
downloadclutter-wip/gaussian-blur.tar.gz
blur-effect: Use a gaussian blurwip/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.
-rw-r--r--clutter/clutter-blur-effect.c474
-rw-r--r--clutter/clutter-blur-effect.h4
2 files changed, 401 insertions, 77 deletions
diff --git a/clutter/clutter-blur-effect.c b/clutter/clutter-blur-effect.c
index 1e36a0cbd..b93f4a75d 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>
*/
/**
@@ -43,6 +44,8 @@
#define CLUTTER_ENABLE_EXPERIMENTAL_API
+#include <math.h>
+
#include "clutter-blur-effect.h"
#include "cogl/cogl.h"
@@ -53,30 +56,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;
@@ -84,21 +63,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);
@@ -129,36 +131,174 @@ 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);
+ CoglContext *ctx =
+ clutter_backend_get_cogl_context (clutter_get_default_backend ());
+ 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 (ctx);
+ 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);
+
+ cogl_object_unref (snippet);
+
+ g_hash_table_insert (klass->pipeline_cache,
+ GINT_TO_POINTER (radius),
+ pipeline);
+ }
+
+ 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);
- return TRUE;
+ 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);
+ }
}
- else
- return FALSE;
+
+ self->vertical_texture_dirty = TRUE;
+
+ parent_class = CLUTTER_EFFECT_CLASS (clutter_blur_effect_parent_class);
+ parent_class->post_paint (effect);
}
static void
@@ -167,18 +307,27 @@ clutter_blur_effect_paint_target (ClutterOffscreenEffect *effect)
ClutterBlurEffect *self = CLUTTER_BLUR_EFFECT (effect);
guint8 paint_opacity;
+ if (self->vertical_texture_dirty)
+ {
+ cogl_framebuffer_draw_rectangle (self->vertical_fbo,
+ self->horizontal_pipeline,
+ -1.0f, 1.0f, 1.0f, -1.0f);
+
+ 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_rectangle (0, 0, self->tex_width, self->tex_height);
- cogl_pop_source ();
+ cogl_framebuffer_draw_rectangle (cogl_get_draw_framebuffer (),
+ self->vertical_pipeline,
+ 0, 0,
+ self->tex_width, self->tex_height);
}
static gboolean
@@ -208,16 +357,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->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->pipeline);
- self->pipeline = NULL;
+ 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);
@@ -225,43 +440,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;
- CoglContext *ctx =
- clutter_backend_get_cogl_context (clutter_get_default_backend ());
+ if (self->sigma == sigma)
+ return;
- klass->base_pipeline = cogl_pipeline_new (ctx);
+ /* 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);
}
- self->pipeline = cogl_pipeline_copy (klass->base_pipeline);
+ 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;
+ }
+
+ 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));
+}
- self->pixel_step_uniform =
- cogl_pipeline_get_uniform_location (self->pipeline, "pixel_step");
+gfloat
+clutter_blur_effect_get_sigma (ClutterBlurEffect *self)
+{
+ g_return_val_if_fail (CLUTTER_IS_BLUR_EFFECT (self), 0.0f);
+
+ return self->sigma;
+}
+
+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 27466bb48..0807e324e 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__ */