[mutter] clutter: Add private ClutterBlur



commit 3440fbd35883caae70529c1e1fa7d734287dba51
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Wed Dec 9 14:42:41 2020 -0300

    clutter: Add private ClutterBlur
    
    ClutterBlur is a small helper structure that implements
    blurring a texture.
    
    Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1627>

 clutter/clutter/clutter-blur-private.h |  40 ++++
 clutter/clutter/clutter-blur.c         | 422 +++++++++++++++++++++++++++++++++
 clutter/clutter/meson.build            |   2 +
 3 files changed, 464 insertions(+)
---
diff --git a/clutter/clutter/clutter-blur-private.h b/clutter/clutter/clutter-blur-private.h
new file mode 100644
index 0000000000..feb288f31e
--- /dev/null
+++ b/clutter/clutter/clutter-blur-private.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 Endless OS Foundation, LLC
+ *
+ * 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/>.
+ */
+
+#ifndef CLUTTER_BLUR_PRIVATE_H
+#define CLUTTER_BLUR_PRIVATE_H
+
+#include <glib-object.h>
+
+#include <cogl/cogl.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ClutterBlur ClutterBlur;
+
+ClutterBlur * clutter_blur_new (CoglTexture  *texture,
+                                unsigned int  sigma);
+
+void clutter_blur_apply (ClutterBlur *blur);
+
+CoglTexture * clutter_blur_get_texture (ClutterBlur *blur);
+
+void clutter_blur_free (ClutterBlur *blur);
+
+G_END_DECLS
+
+#endif /* CLUTTER_BLUR_PRIVATE_H */
diff --git a/clutter/clutter/clutter-blur.c b/clutter/clutter/clutter-blur.c
new file mode 100644
index 0000000000..0531bdd455
--- /dev/null
+++ b/clutter/clutter/clutter-blur.c
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2020 Endless OS Foundation, LLC
+ *
+ * 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/>.
+ */
+
+#include "clutter-blur-private.h"
+
+#include "clutter-backend.h"
+
+/**
+ * SECTION:clutter-blur
+ * @short_description: Blur textures
+ *
+ * #ClutterBlur is a moderately fast gaussian blur implementation.
+ *
+ * # Optimizations
+ *
+ * There are a number of optimizations in place to make this blur implementation
+ * real-time. All in all, the implementation performs best when using large
+ * blur-radii that allow downscaling the texture to smaller sizes, at small
+ * radii where no downscaling is possible this can easily halve the framerate.
+ *
+ * ## Multipass
+ *
+ * It is implemented in 2 passes: vertical and horizontal.
+ *
+ * ## Downscaling
+ *
+ * #ClutterBlur uses dynamic downscaling to speed up blurring. Downscaling
+ * happens in factors of 2 (the image is downscaled either by 2, 4, 8, 16, …)
+ * and depends on the blur radius, the texture size, among others.
+ *
+ * The texture is drawn into a downscaled framebuffer; the blur passes are
+ * applied on the downscaled texture contents; and finally, the blurred
+ * contents are drawn
+ * upscaled again.
+ *
+ * ## Hardware Interpolation
+ *
+ * This blur implementation cuts down the number of sampling operations by
+ * exploiting the hardware interpolation that is performed when sampling between
+ * pixel boundaries. This technique is described at:
+ *
+ * http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/
+ *
+ * ## Incremental gauss-factor calculation
+ *
+ * The kernel values for the gaussian kernel are computed incrementally instead
+ * of running the expensive calculations multiple times inside the blur shader.
+ * The implementation is based on the algorithm presented by K. Turkowski in
+ * GPU Gems 3, chapter 40:
+ *
+ * https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch40.html
+ *
+ */
+
+static const char *gaussian_blur_glsl_declarations =
+"uniform float sigma;                                                      \n"
+"uniform float pixel_step;                                                 \n"
+"uniform int vertical;                                                     \n";
+
+static const char *gaussian_blur_glsl =
+"  int horizontal = 1 - vertical;                                          \n"
+"                                                                          \n"
+"  vec2 uv = vec2 (cogl_tex_coord.st);                                     \n"
+"                                                                          \n"
+"  vec3 gauss_coefficient;                                                 \n"
+"  gauss_coefficient.x = 1.0 / (sqrt (2.0 * 3.14159265) * sigma);          \n"
+"  gauss_coefficient.y = exp (-0.5 / (sigma * sigma));                     \n"
+"  gauss_coefficient.z = gauss_coefficient.y * gauss_coefficient.y;        \n"
+"                                                                          \n"
+"  float gauss_coefficient_total = gauss_coefficient.x;                    \n"
+"                                                                          \n"
+"  vec4 ret = texture2D (cogl_sampler, uv) * gauss_coefficient.x;          \n"
+"  gauss_coefficient.xy *= gauss_coefficient.yz;                           \n"
+"                                                                          \n"
+"  int n_steps = int (ceil (3 * sigma));                                   \n"
+"                                                                          \n"
+"  for (int i = 1; i < n_steps; i += 2) {                                  \n"
+"    float coefficient_subtotal = gauss_coefficient.x;                     \n"
+"    gauss_coefficient.xy *= gauss_coefficient.yz;                         \n"
+"    coefficient_subtotal += gauss_coefficient.x;                          \n"
+"                                                                          \n"
+"    float gauss_ratio = gauss_coefficient.x / coefficient_subtotal;       \n"
+"                                                                          \n"
+"    float foffset = float (i) + gauss_ratio;                              \n"
+"    vec2 offset = vec2 (foffset * pixel_step * float (horizontal),        \n"
+"                        foffset * pixel_step * float (vertical));         \n"
+"                                                                          \n"
+"    ret += texture2D (cogl_sampler, uv + offset) * coefficient_subtotal;  \n"
+"    ret += texture2D (cogl_sampler, uv - offset) * coefficient_subtotal;  \n"
+"                                                                          \n"
+"    gauss_coefficient_total += 2.0 * coefficient_subtotal;                \n"
+"    gauss_coefficient.xy *= gauss_coefficient.yz;                         \n"
+"  }                                                                       \n"
+"                                                                          \n"
+"  cogl_texel = ret / gauss_coefficient_total;                             \n";
+
+#define MIN_DOWNSCALE_SIZE 256.f
+#define MAX_SIGMA 6.f
+
+enum
+{
+  VERTICAL,
+  HORIZONTAL,
+};
+
+typedef struct
+{
+  CoglFramebuffer *framebuffer;
+  CoglPipeline *pipeline;
+  CoglTexture *texture;
+  int orientation;
+  int sigma_uniform;
+  int pixel_step_uniform;
+  int vertical_uniform;
+} BlurPass;
+
+struct _ClutterBlur
+{
+  CoglTexture *source_texture;
+  unsigned int sigma;
+  float downscale_factor;
+
+  BlurPass pass[2];
+};
+
+static CoglPipeline*
+create_blur_pipeline (void)
+{
+  static CoglPipelineKey blur_pipeline_key = "clutter-blur-pipeline-private";
+  CoglContext *ctx =
+    clutter_backend_get_cogl_context (clutter_get_default_backend ());
+  CoglPipeline *blur_pipeline;
+
+  blur_pipeline =
+    cogl_context_get_named_pipeline (ctx, &blur_pipeline_key);
+
+  if (G_UNLIKELY (blur_pipeline == NULL))
+    {
+      CoglSnippet *snippet;
+
+      blur_pipeline = cogl_pipeline_new (ctx);
+      cogl_pipeline_set_layer_null_texture (blur_pipeline, 0);
+      cogl_pipeline_set_layer_filters (blur_pipeline,
+                                       0,
+                                       COGL_PIPELINE_FILTER_LINEAR,
+                                       COGL_PIPELINE_FILTER_LINEAR);
+      cogl_pipeline_set_layer_wrap_mode (blur_pipeline,
+                                         0,
+                                         COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE);
+
+      snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_TEXTURE_LOOKUP,
+                                  gaussian_blur_glsl_declarations,
+                                  NULL);
+      cogl_snippet_set_replace (snippet, gaussian_blur_glsl);
+      cogl_pipeline_add_layer_snippet (blur_pipeline, 0, snippet);
+      cogl_object_unref (snippet);
+
+      cogl_context_set_named_pipeline (ctx, &blur_pipeline_key, blur_pipeline);
+    }
+
+  return cogl_pipeline_copy (blur_pipeline);
+}
+
+static void
+update_blur_uniforms (ClutterBlur *blur,
+                      BlurPass    *pass)
+{
+  gboolean vertical = pass->orientation == VERTICAL;
+
+  pass->pixel_step_uniform =
+    cogl_pipeline_get_uniform_location (pass->pipeline, "pixel_step");
+  if (pass->pixel_step_uniform > -1)
+    {
+      float pixel_step;
+
+      if (vertical)
+        pixel_step = 1.f / cogl_texture_get_height (pass->texture);
+      else
+        pixel_step = 1.f / cogl_texture_get_width (pass->texture);
+
+      cogl_pipeline_set_uniform_1f (pass->pipeline,
+                                    pass->pixel_step_uniform,
+                                    pixel_step);
+    }
+
+  pass->sigma_uniform =
+    cogl_pipeline_get_uniform_location (pass->pipeline, "sigma");
+  if (pass->sigma_uniform > -1)
+    {
+      cogl_pipeline_set_uniform_1f (pass->pipeline,
+                                    pass->sigma_uniform,
+                                    blur->sigma / blur->downscale_factor);
+    }
+
+  pass->vertical_uniform =
+    cogl_pipeline_get_uniform_location (pass->pipeline, "vertical");
+  if (pass->vertical_uniform > -1)
+    {
+      cogl_pipeline_set_uniform_1i (pass->pipeline,
+                                    pass->vertical_uniform,
+                                    vertical);
+    }
+}
+
+static gboolean
+create_fbo (ClutterBlur *blur,
+            BlurPass    *pass)
+{
+  CoglContext *ctx =
+    clutter_backend_get_cogl_context (clutter_get_default_backend ());
+  graphene_matrix_t projection;
+  float scaled_height;
+  float scaled_width;
+  float height;
+  float width;
+
+  g_clear_pointer (&pass->texture, cogl_object_unref);
+  g_clear_object (&pass->framebuffer);
+
+  width = cogl_texture_get_width (blur->source_texture);
+  height = cogl_texture_get_height (blur->source_texture);
+  scaled_width = floorf (width / blur->downscale_factor);
+  scaled_height = floorf (height / blur->downscale_factor);
+
+  pass->texture = COGL_TEXTURE (cogl_texture_2d_new_with_size (ctx,
+                                                               scaled_width,
+                                                               scaled_height));
+  if (!pass->texture)
+    return FALSE;
+
+  pass->framebuffer =
+    COGL_FRAMEBUFFER (cogl_offscreen_new_with_texture (pass->texture));
+  if (!pass->framebuffer)
+    {
+      g_warning ("%s: Unable to create an Offscreen buffer", G_STRLOC);
+      return FALSE;
+    }
+
+  graphene_matrix_init_translate (&projection,
+                                  &GRAPHENE_POINT3D_INIT (-scaled_width / 2.f,
+                                                          -scaled_height / 2.f,
+                                                          0.f));
+  graphene_matrix_scale (&projection,
+                         2.f / scaled_width,
+                         -2.f / scaled_height,
+                         1.f);
+
+  cogl_framebuffer_set_projection_matrix (pass->framebuffer, &projection);
+
+  return TRUE;
+}
+
+static gboolean
+setup_blur_pass (ClutterBlur *blur,
+                 BlurPass    *pass,
+                 int          orientation,
+                 CoglTexture *texture)
+{
+  pass->orientation = orientation;
+  pass->pipeline = create_blur_pipeline ();
+  cogl_pipeline_set_layer_texture (pass->pipeline, 0, texture);
+
+  if (!create_fbo (blur, pass))
+    return FALSE;
+
+  update_blur_uniforms (blur, pass);
+  return TRUE;
+}
+
+static float
+calculate_downscale_factor (float width,
+                            float height,
+                            float sigma)
+{
+  float downscale_factor = 1.f;
+  float scaled_width = width;
+  float scaled_height = height;
+  float scaled_sigma = sigma;
+
+  /* This is the algorithm used by Firefox; keep downscaling until either the
+   * blur radius is lower than the threshold, or the downscaled texture is too
+   * small.
+   */
+  while (scaled_sigma > MAX_SIGMA &&
+         scaled_width > MIN_DOWNSCALE_SIZE &&
+         scaled_height > MIN_DOWNSCALE_SIZE)
+    {
+      downscale_factor *= 2.f;
+
+      scaled_width = width / downscale_factor;
+      scaled_height = height / downscale_factor;
+      scaled_sigma = sigma / downscale_factor;
+    }
+
+  return downscale_factor;
+}
+
+static void
+apply_blur_pass (BlurPass *pass)
+{
+  CoglColor transparent;
+
+  cogl_color_init_from_4ub (&transparent, 0, 0, 0, 0);
+
+  cogl_framebuffer_clear (pass->framebuffer,
+                          COGL_BUFFER_BIT_COLOR,
+                          &transparent);
+
+  cogl_framebuffer_draw_rectangle (pass->framebuffer,
+                                   pass->pipeline,
+                                   0, 0,
+                                   cogl_texture_get_width (pass->texture),
+                                   cogl_texture_get_height (pass->texture));
+}
+
+static void
+clear_blur_pass (BlurPass *pass)
+{
+  g_clear_pointer (&pass->pipeline, cogl_object_unref);
+  g_clear_pointer (&pass->texture, cogl_object_unref);
+  g_clear_object (&pass->framebuffer);
+}
+
+/**
+ * clutter_blur_new:
+ * @texture: a #CoglTexture
+ * @sigma: blur sigma
+ *
+ * Creates a new #ClutterBlur.
+ *
+ * Returns: (transfer full) (nullable): A newly created #ClutterBlur
+ */
+ClutterBlur *
+clutter_blur_new (CoglTexture  *texture,
+                  unsigned int  sigma)
+{
+  ClutterBlur *blur;
+  unsigned int height;
+  unsigned int width;
+  BlurPass *hpass;
+  BlurPass *vpass;
+
+  width = cogl_texture_get_width (texture);
+  height = cogl_texture_get_height (texture);
+
+  blur = g_new0 (ClutterBlur, 1);
+  blur->sigma = sigma;
+  blur->source_texture = cogl_object_ref (texture);
+  blur->downscale_factor = calculate_downscale_factor (width, height, sigma);
+
+  vpass = &blur->pass[VERTICAL];
+  hpass = &blur->pass[HORIZONTAL];
+
+  if (!setup_blur_pass (blur, vpass, VERTICAL, texture) ||
+      !setup_blur_pass (blur, hpass, HORIZONTAL, vpass->texture))
+    {
+      clutter_blur_free (blur);
+      return NULL;
+    }
+
+  return g_steal_pointer (&blur);
+}
+
+/**
+ * clutter_blur_apply:
+ * @blur: a #ClutterBlur
+ *
+ * Applies the blur. The resulting texture can be retrieved by
+ * clutter_blur_get_texture().
+ */
+void
+clutter_blur_apply (ClutterBlur *blur)
+{
+  apply_blur_pass (&blur->pass[VERTICAL]);
+  apply_blur_pass (&blur->pass[HORIZONTAL]);
+}
+
+/**
+ * clutter_blur_get_texture:
+ * @blur: a #ClutterBlur
+ *
+ * Retrieves the texture where the blurred contents are stored. The
+ * contents are undefined until clutter_blur_apply() is called.
+ *
+ * Returns: (transfer none): a #CoglTexture
+ */
+CoglTexture *
+clutter_blur_get_texture (ClutterBlur *blur)
+{
+  return blur->pass[HORIZONTAL].texture;
+}
+
+/**
+ * clutter_blur_free:
+ * @blur: A #ClutterBlur
+ *
+ * Frees @blur.
+ */
+void
+clutter_blur_free (ClutterBlur *blur)
+{
+  g_assert (blur);
+
+  clear_blur_pass (&blur->pass[VERTICAL]);
+  clear_blur_pass (&blur->pass[HORIZONTAL]);
+  cogl_clear_object (&blur->source_texture);
+  g_free (blur);
+}
diff --git a/clutter/clutter/meson.build b/clutter/clutter/meson.build
index f914370ec4..4b617bd989 100644
--- a/clutter/clutter/meson.build
+++ b/clutter/clutter/meson.build
@@ -101,6 +101,7 @@ clutter_sources = [
   'clutter-bind-constraint.c',
   'clutter-binding-pool.c',
   'clutter-bin-layout.c',
+  'clutter-blur.c',
   'clutter-blur-effect.c',
   'clutter-box-layout.c',
   'clutter-brightness-contrast-effect.c',
@@ -184,6 +185,7 @@ clutter_private_headers = [
   'clutter-actor-private.h',
   'clutter-backend-private.h',
   'clutter-bezier.h',
+  'clutter-blur-private.h',
   'clutter-constraint-private.h',
   'clutter-content-private.h',
   'clutter-damage-history.h',


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