diff options
-rw-r--r-- | clutter/clutter-blur-effect.c | 474 | ||||
-rw-r--r-- | clutter/clutter-blur-effect.h | 4 |
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__ */ |