summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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__ */