[gnome-shell/gbsneto/blur: 2/3] screenShield: Blur background



commit 2dae743c7533721be355c41ce4c8157de4e9c121
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Mon Oct 7 14:38:19 2019 -0300

    screenShield: Blur background
    
    This is a moderately fast two-pass gaussian blur implementation.
    It downscales the framebuffer dynamically before applying the
    gaussian shader, which cuts down rendering time quite considerably.
    
    The blur shader takes 2 uniforms as input: the blur radius; and
    whether to blur vertically or horizontally.
    
    The blur radius is treated as an integer in C land to simplify
    calculations. The vertical parameter is treated as an integer by
    the shader simply due to Cogl not having proper boolean support
    in snippets.
    
    At last, brightness is also added to avoid needing to use an extra
    effect to achieve that. Brightness is applied in a different pipeline
    than blur, so we can control it more tightly.
    
    ShellBlurEffect also implements a "below" mode, where the contents
    beneath the actor are blurred, but not the actor itself. This mode
    is performance-heavy.
    
    Related: https://gitlab.gnome.org/GNOME/gnome-shell/issues/1848
    
    https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/864

 js/ui/screenShield.js   |    9 +
 src/meson.build         |    2 +
 src/shell-blur-effect.c | 1076 +++++++++++++++++++++++++++++++++++++++++++++++
 src/shell-blur-effect.h |   57 +++
 4 files changed, 1144 insertions(+)
---
diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js
index ed41f12975..7071c77812 100644
--- a/js/ui/screenShield.js
+++ b/js/ui/screenShield.js
@@ -27,6 +27,10 @@ const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
 const DISABLE_LOCK_KEY = 'disable-lock-screen';
 
 const LOCKED_STATE_STR = 'screenShield.locked';
+
+const BLUR_BRIGHTNESS = 0.55;
+const BLUR_RADIUS = 200;
+
 // fraction of screen height the arrow must reach before completing
 // the slide up automatically
 var ARROW_DRAG_THRESHOLD = 0.1;
@@ -607,6 +611,11 @@ var ScreenShield = class {
         this._bgManagers.push(bgManager);
 
         this._backgroundGroup.add_child(widget);
+
+        widget.add_effect(new Shell.BlurEffect({
+            brightness: BLUR_BRIGHTNESS,
+            blur_radius: BLUR_RADIUS,
+        }));
     }
 
     _updateBackgrounds() {
diff --git a/src/meson.build b/src/meson.build
index caf8881e34..eac1dfcc3e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -95,6 +95,7 @@ libshell_public_headers = [
   'shell-app.h',
   'shell-app-system.h',
   'shell-app-usage.h',
+  'shell-blur-effect.h',
   'shell-embedded-window.h',
   'shell-glsl-effect.h',
   'shell-gtk-embed.h',
@@ -129,6 +130,7 @@ libshell_sources = [
   'shell-app.c',
   'shell-app-system.c',
   'shell-app-usage.c',
+  'shell-blur-effect.c',
   'shell-embedded-window.c',
   'shell-embedded-window-private.h',
   'shell-global.c',
diff --git a/src/shell-blur-effect.c b/src/shell-blur-effect.c
new file mode 100644
index 0000000000..55ce6bb194
--- /dev/null
+++ b/src/shell-blur-effect.c
@@ -0,0 +1,1076 @@
+/* shell-blur-effect.c
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "shell-blur-effect.h"
+
+#include "shell-enum-types.h"
+
+/**
+ * SECTION:shell-blur-effect
+ * @short_description: Blur effect for actors
+ *
+ * #ShellBlurEffect is a moderately fast gaussian blur implementation. It also has
+ * an optional brighness property.
+ *
+ * # Modes
+ *
+ * #ShellBlurEffect can work in @SHELL_BLUR_MODE_BACKGROUND and @SHELL_BLUR_MODE_ACTOR
+ * modes. The actor mode blurs the actor itself, and all of its children. The
+ * background mode blurs the pixels beneath the actor, but not the actor itself.
+ *
+ * @SHELL_BLUR_MODE_BACKGROUND can be computationally expensive, since the contents
+ * beneath the actor cannot be cached, so beware of the performance implications
+ * of using this blur mode.
+ *
+ * # Optimizations
+ *
+ * There are a number of optimizations in place to make this blur implementation
+ * real-time.
+ *
+ * ## Multipass
+ *
+ * It is implemented in 2 passes: vertical and horizontal.
+ *
+ * ## Downscaling
+ *
+ * #ShellBlurEffect 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 actor size, among others.
+ *
+ * The actor is drawn into a downscaled framebuffer; the blur passes are applied
+ * on the downscaled actor 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/
+ *
+ */
+
+static const gchar *gaussian_blur_glsl_declarations =
+"uniform float blur_radius;                                        \n"
+"uniform float pixel_step;                                         \n"
+"uniform int vertical;                                             \n"
+"                                                                  \n"
+"float gaussian (float sigma, float x) {                           \n"
+"  return exp ( - (x * x) / (2.0 * sigma * sigma));                \n"
+"}                                                                 \n"
+"                                                                  \n";
+
+static const gchar *gaussian_blur_glsl =
+"  float total = 0.0;                                              \n"
+"  int horizontal = 1 - vertical;                                  \n"
+"                                                                  \n"
+"  vec4 ret = vec4 (0);                                            \n"
+"  vec2 uv = vec2(cogl_tex_coord.st);                              \n"
+"                                                                  \n"
+"  float half_radius = blur_radius / 2.0;                          \n"
+"  int n_steps = max(int(ceil(half_radius)), 1);                   \n"
+"                                                                  \n"
+"  for (int i = 0; i < n_steps; i += 2) {                          \n"
+"    float weight0 = gaussian (half_radius, float(i));             \n"
+"    float weight1 = gaussian (half_radius, float(i + 1));         \n"
+"    float weight = weight0 + weight1;                             \n"
+"                                                                  \n"
+"    float step0 = float(i) * pixel_step;                          \n"
+"    float step1 = float(i + 1) * pixel_step;                      \n"
+"                                                                  \n"
+"    float foffset = (step0 * weight0 + step1 * weight1) / weight; \n"
+"    vec2 offset = vec2(foffset * float(horizontal),               \n"
+"                       foffset * float(vertical));                \n"
+"                                                                  \n"
+"    vec4 c = texture2D(cogl_sampler, uv + offset);                \n"
+"    total += weight;                                              \n"
+"    ret += c * weight;                                            \n"
+"                                                                  \n"
+"    c = texture2D(cogl_sampler, uv - offset);                     \n"
+"    total += weight;                                              \n"
+"    ret += c * weight;                                            \n"
+"  }                                                               \n"
+"                                                                  \n"
+"  cogl_texel = vec4 (ret / total);                                \n";
+
+static const gchar *brightness_glsl_declarations =
+"uniform float brightness;                                         \n";
+
+static const gchar *brightness_glsl =
+"  cogl_color_out.rgb *= brightness;                               \n";
+
+#define MIN_DOWNSCALE_SIZE 256.f
+#define MAX_BLUR_RADIUS 10.f
+
+typedef enum
+{
+  VERTICAL,
+  HORIZONTAL,
+} BlurType;
+
+typedef struct
+{
+  CoglFramebuffer *framebuffer;
+  CoglPipeline *pipeline;
+  CoglTexture *texture;
+} FramebufferData;
+
+typedef struct
+{
+  FramebufferData data;
+  BlurType type;
+  int blur_radius_uniform;
+  int pixel_step_uniform;
+  int vertical_uniform;
+} BlurData;
+
+struct _ShellBlurEffect
+{
+  ClutterEffect parent_instance;
+
+  ClutterActor *actor;
+  uint8_t old_opacity_override;
+
+  BlurData blur[2];
+
+  unsigned int tex_width;
+  unsigned int tex_height;
+
+  /* The cached contents */
+  FramebufferData actor_fb;
+  FramebufferData background_fb;
+  FramebufferData brightness_fb;
+  int brightness_uniform;
+
+  ShellBlurMode mode;
+  float downscale_factor;
+  float brightness;
+  int blur_radius;
+};
+
+G_DEFINE_TYPE (ShellBlurEffect, shell_blur_effect, CLUTTER_TYPE_EFFECT)
+
+enum {
+  PROP_0,
+  PROP_BLUR_RADIUS,
+  PROP_BRIGHTNESS,
+  PROP_MODE,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS] = { NULL, };
+
+static CoglPipeline*
+create_base_pipeline (void)
+{
+  static CoglPipeline *base_pipeline = NULL;
+
+  if (G_UNLIKELY (base_pipeline == NULL))
+    {
+      CoglContext *ctx =
+        clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+      base_pipeline = cogl_pipeline_new (ctx);
+      cogl_pipeline_set_layer_null_texture (base_pipeline, 0);
+      cogl_pipeline_set_layer_filters (base_pipeline,
+                                       0,
+                                       COGL_PIPELINE_FILTER_LINEAR,
+                                       COGL_PIPELINE_FILTER_LINEAR);
+      cogl_pipeline_set_layer_wrap_mode (base_pipeline,
+                                         0,
+                                         COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE);
+    }
+
+  return cogl_pipeline_copy (base_pipeline);
+}
+
+static CoglPipeline*
+create_blur_pipeline (void)
+{
+  static CoglPipeline *blur_pipeline = NULL;
+
+  if (G_UNLIKELY (blur_pipeline == NULL))
+    {
+      CoglSnippet *snippet;
+
+      blur_pipeline = create_base_pipeline ();
+
+      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);
+    }
+
+  return cogl_pipeline_copy (blur_pipeline);
+}
+
+
+static CoglPipeline*
+create_brightness_pipeline (void)
+{
+  static CoglPipeline *brightness_pipeline = NULL;
+
+  if (G_UNLIKELY (brightness_pipeline == NULL))
+    {
+      CoglSnippet *snippet;
+
+      brightness_pipeline = create_base_pipeline ();
+
+      snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
+                                  brightness_glsl_declarations,
+                                  brightness_glsl);
+      cogl_pipeline_add_snippet (brightness_pipeline, snippet);
+      cogl_object_unref (snippet);
+    }
+
+  return cogl_pipeline_copy (brightness_pipeline);
+}
+
+static void
+setup_blur (BlurData *blur,
+            BlurType  type)
+{
+  blur->type = type;
+  blur->data.pipeline = create_blur_pipeline ();
+
+  blur->blur_radius_uniform =
+    cogl_pipeline_get_uniform_location (blur->data.pipeline, "blur_radius");
+  blur->pixel_step_uniform =
+    cogl_pipeline_get_uniform_location (blur->data.pipeline, "pixel_step");
+  blur->vertical_uniform =
+    cogl_pipeline_get_uniform_location (blur->data.pipeline, "vertical");
+}
+
+static void
+update_blur_uniforms (ShellBlurEffect *self,
+                      BlurData        *blur)
+{
+  gboolean is_vertical = blur->type == VERTICAL;
+
+  if (blur->pixel_step_uniform > -1)
+    {
+      float pixel_step;
+
+      if (is_vertical)
+        pixel_step = 1.f / cogl_texture_get_height (blur->data.texture);
+      else
+        pixel_step = 1.f / cogl_texture_get_width (blur->data.texture);
+
+      cogl_pipeline_set_uniform_1f (blur->data.pipeline,
+                                    blur->pixel_step_uniform,
+                                    pixel_step);
+    }
+
+  if (blur->blur_radius_uniform > -1)
+    {
+      cogl_pipeline_set_uniform_1f (blur->data.pipeline,
+                                    blur->blur_radius_uniform,
+                                    self->blur_radius / self->downscale_factor);
+    }
+
+  if (blur->vertical_uniform > -1)
+    {
+      cogl_pipeline_set_uniform_1i (blur->data.pipeline,
+                                    blur->vertical_uniform,
+                                    is_vertical);
+    }
+}
+
+static void
+update_brightness_uniform (ShellBlurEffect *self)
+{
+  if (self->brightness_uniform > -1)
+    {
+      cogl_pipeline_set_uniform_1f (self->brightness_fb.pipeline,
+                                    self->brightness_uniform,
+                                    self->brightness);
+    }
+}
+
+static void
+setup_projection_matrix (CoglFramebuffer *framebuffer,
+                         float            width,
+                         float            height,
+                         float            downscale_factor)
+{
+  CoglMatrix projection;
+  float downscaled_width = width / downscale_factor;
+  float downscaled_height = height / downscale_factor;
+
+  cogl_matrix_init_identity (&projection);
+  cogl_matrix_scale (&projection,
+                     2.0 / downscaled_width,
+                     -2.0 / downscaled_height,
+                     1.f);
+  cogl_matrix_translate (&projection,
+                         -downscaled_width / 2.0,
+                         -downscaled_height / 2.0,
+                         0);
+
+  cogl_framebuffer_set_projection_matrix (framebuffer, &projection);
+}
+
+static gboolean
+update_fbo (FramebufferData *data,
+            unsigned int     width,
+            unsigned int     height,
+            float            downscale_factor)
+{
+  CoglContext *ctx =
+    clutter_backend_get_cogl_context (clutter_get_default_backend ());
+
+  g_clear_pointer (&data->texture, cogl_object_unref);
+  g_clear_pointer (&data->framebuffer, cogl_object_unref);
+
+  data->texture =
+    cogl_texture_2d_new_with_size (ctx,
+                                   width / downscale_factor,
+                                   height / downscale_factor);
+  if (!data->texture)
+    return FALSE;
+
+  cogl_pipeline_set_layer_texture (data->pipeline, 0, data->texture);
+
+  data->framebuffer = cogl_offscreen_new_with_texture (data->texture);
+  if (!data->framebuffer)
+    {
+      g_warning ("%s: Unable to create an Offscreen buffer", G_STRLOC);
+      return FALSE;
+    }
+
+  setup_projection_matrix (data->framebuffer, width, height, downscale_factor);
+
+  return TRUE;
+}
+
+static gboolean
+update_actor_fbo (ShellBlurEffect *self,
+                  unsigned int     width,
+                  unsigned int     height,
+                  float            downscale_factor)
+{
+  if (self->tex_width == width &&
+      self->tex_height == height &&
+      self->downscale_factor == downscale_factor &&
+      self->actor_fb.framebuffer)
+    {
+      return TRUE;
+    }
+
+  return update_fbo (&self->actor_fb, width, height, downscale_factor);
+}
+
+static gboolean
+update_brightness_fbo (ShellBlurEffect *self,
+                       unsigned int     width,
+                       unsigned int     height,
+                       float            downscale_factor)
+{
+  if (self->tex_width == width &&
+      self->tex_height == height &&
+      self->downscale_factor == downscale_factor &&
+      self->brightness_fb.framebuffer)
+    {
+      return TRUE;
+    }
+
+  return update_fbo (&self->brightness_fb,
+                     width, height,
+                     downscale_factor);
+}
+
+static gboolean
+update_blur_fbo (ShellBlurEffect *self,
+                 BlurData        *blur,
+                 unsigned int     width,
+                 unsigned int     height,
+                 float            downscale_factor)
+{
+  if (self->tex_width == width &&
+      self->tex_height == height &&
+      self->downscale_factor == downscale_factor &&
+      blur->data.framebuffer)
+    {
+      return TRUE;
+    }
+
+  return update_fbo (&blur->data,
+                     width, height,
+                     downscale_factor);
+}
+
+static gboolean
+update_background_fbo (ShellBlurEffect *self,
+                    unsigned int     width,
+                    unsigned int     height)
+{
+  if (self->tex_width == width &&
+      self->tex_height == height &&
+      self->background_fb.framebuffer)
+    {
+      return TRUE;
+    }
+
+  return update_fbo (&self->background_fb, width, height, 1.0);
+}
+
+static void
+clear_framebuffer_data (FramebufferData *fb_data)
+{
+  g_clear_pointer (&fb_data->texture, cogl_object_unref);
+  g_clear_pointer (&fb_data->framebuffer, cogl_object_unref);
+}
+
+static float
+calculate_downscale_factor (float width,
+                            float height,
+                            float blur_radius)
+{
+  float downscale_factor = 1.0;
+  float scaled_width = width;
+  float scaled_height = height;
+  float scaled_radius = blur_radius;
+
+  /* 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_radius > MAX_BLUR_RADIUS &&
+         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_radius = blur_radius / downscale_factor;
+    }
+
+  return downscale_factor;
+}
+
+static void
+clear_framebuffer (CoglFramebuffer *framebuffer)
+{
+  static CoglColor transparent;
+  static gboolean initialized = FALSE;
+
+  if (!initialized)
+    {
+      cogl_color_init_from_4ub (&transparent, 0, 0, 0, 0);
+      initialized = TRUE;
+    }
+
+  cogl_framebuffer_clear (framebuffer, COGL_BUFFER_BIT_COLOR, &transparent);
+}
+
+static void
+shell_blur_effect_set_actor (ClutterActorMeta *meta,
+                             ClutterActor     *actor)
+{
+  ShellBlurEffect *self = SHELL_BLUR_EFFECT (meta);
+  ClutterActorMetaClass *meta_class;
+
+  meta_class = CLUTTER_ACTOR_META_CLASS (shell_blur_effect_parent_class);
+  meta_class->set_actor (meta, actor);
+
+  /* clear out the previous state */
+  clear_framebuffer_data (&self->actor_fb);
+  clear_framebuffer_data (&self->brightness_fb);
+  clear_framebuffer_data (&self->blur[VERTICAL].data);
+  clear_framebuffer_data (&self->blur[HORIZONTAL].data);
+
+  /* we keep a back pointer here, to avoid going through the ActorMeta */
+  self->actor = clutter_actor_meta_get_actor (meta);
+}
+
+static void
+get_target_size (ShellBlurEffect *self,
+                 float           *width,
+                 float           *height)
+{
+  float resource_scale = 1.0;
+  float ceiled_resource_scale;
+  float transformed_width;
+  float transformed_height;
+
+  clutter_actor_get_resource_scale (self->actor, &resource_scale);
+  ceiled_resource_scale = ceilf (resource_scale);
+
+  clutter_actor_get_transformed_size (self->actor,
+                                      &transformed_width,
+                                      &transformed_height);
+
+  *width = ceilf (transformed_width * ceiled_resource_scale);
+  *height = ceilf (transformed_height * ceiled_resource_scale);
+}
+
+static void
+paint_texture (ShellBlurEffect     *self,
+               ClutterPaintContext *paint_context)
+{
+  CoglFramebuffer *framebuffer;
+  CoglMatrix modelview;
+  float width, height;
+  float resource_scale;
+
+  framebuffer = clutter_paint_context_get_framebuffer (paint_context);
+
+  cogl_framebuffer_push_matrix (framebuffer);
+  cogl_framebuffer_get_modelview_matrix (framebuffer, &modelview);
+
+  if (clutter_actor_get_resource_scale (self->actor, &resource_scale) &&
+      resource_scale != 1.0f)
+    {
+      float paint_scale = 1.0f / resource_scale;
+      cogl_matrix_scale (&modelview, paint_scale, paint_scale, 1);
+    }
+
+  cogl_framebuffer_set_modelview_matrix (framebuffer, &modelview);
+
+  /* Use the untransformed actor size here, since the framebuffer itself already
+   * has the actor transform matrix applied.
+   */
+  clutter_actor_get_size (self->actor, &width, &height);
+
+  update_brightness_uniform (self);
+  cogl_framebuffer_draw_rectangle (framebuffer,
+                                   self->brightness_fb.pipeline,
+                                   0, 0,
+                                   ceilf (width),
+                                   ceilf (height));
+
+  cogl_framebuffer_pop_matrix (framebuffer);
+}
+
+static void
+apply_blur (ShellBlurEffect     *self,
+            ClutterPaintContext *paint_context,
+            FramebufferData     *from)
+{
+  ClutterActor *actor;
+  BlurData *vblur;
+  BlurData *hblur;
+  guint8 paint_opacity;
+
+  vblur = &self->blur[VERTICAL];
+  hblur = &self->blur[HORIZONTAL];
+
+  actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self));
+  paint_opacity = clutter_actor_get_paint_opacity (actor);
+
+  /* Copy the actor contents into the vblur framebuffer */
+  cogl_pipeline_set_color4ub (from->pipeline,
+                              paint_opacity,
+                              paint_opacity,
+                              paint_opacity,
+                              paint_opacity);
+
+  clear_framebuffer (vblur->data.framebuffer);
+  cogl_framebuffer_draw_rectangle (vblur->data.framebuffer,
+                                   from->pipeline,
+                                   0, 0,
+                                   cogl_texture_get_width (vblur->data.texture),
+                                   cogl_texture_get_height (vblur->data.texture));
+
+  /* Pass 1:
+   *
+   * Draw the actor contents (which is in the vblur framebuffer
+   * at this point) into the hblur framebuffer. This will run the
+   * vertical blur fragment shader, and will output a vertically
+   * blurred image.
+   */
+  update_blur_uniforms (self, vblur);
+  cogl_pipeline_set_color4ub (vblur->data.pipeline,
+                              paint_opacity,
+                              paint_opacity,
+                              paint_opacity,
+                              paint_opacity);
+
+  clear_framebuffer (hblur->data.framebuffer);
+  cogl_framebuffer_draw_rectangle (hblur->data.framebuffer,
+                                   vblur->data.pipeline,
+                                   0, 0,
+                                   cogl_texture_get_width (hblur->data.texture),
+                                   cogl_texture_get_height (hblur->data.texture));
+
+  /* Pass 2:
+   *
+   * Now the opposite; draw the vertically blurred image using the
+   * horizontal blur pipeline into the brightness framebuffer.
+   */
+  update_blur_uniforms (self, hblur);
+  cogl_pipeline_set_color4ub (hblur->data.pipeline,
+                              paint_opacity,
+                              paint_opacity,
+                              paint_opacity,
+                              paint_opacity);
+
+  clear_framebuffer (self->brightness_fb.framebuffer);
+  cogl_framebuffer_draw_rectangle (self->brightness_fb.framebuffer,
+                                   hblur->data.pipeline,
+                                   0, 0,
+                                   cogl_texture_get_width (self->brightness_fb.texture),
+                                   cogl_texture_get_height (self->brightness_fb.texture));
+}
+
+static gboolean
+paint_background (ShellBlurEffect     *self,
+                  ClutterPaintContext *paint_context)
+{
+  g_autoptr (GError) error = NULL;
+  CoglFramebuffer *framebuffer;
+  float transformed_x = 0.f;
+  float transformed_y = 0.f;
+  guint8 paint_opacity;
+
+  framebuffer = clutter_paint_context_get_framebuffer (paint_context);
+  paint_opacity = clutter_actor_get_paint_opacity (self->actor);
+
+  cogl_pipeline_set_color4ub (self->background_fb.pipeline,
+                              paint_opacity,
+                              paint_opacity,
+                              paint_opacity,
+                              paint_opacity);
+
+  clutter_actor_get_transformed_position (self->actor,
+                                          &transformed_x,
+                                          &transformed_y);
+
+  clear_framebuffer (self->background_fb.framebuffer);
+  cogl_blit_framebuffer (framebuffer,
+                         self->background_fb.framebuffer,
+                         floor (transformed_x),
+                         floor (transformed_y),
+                         0, 0,
+                         self->tex_width,
+                         self->tex_height,
+                         &error);
+
+  if (error)
+    {
+      g_warning ("Error blitting overlay framebuffer: %s", error->message);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+update_framebuffers (ShellBlurEffect *self)
+{
+  gboolean updated = FALSE;
+  float downscale_factor;
+  float height = -1;
+  float width = -1;
+
+  if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (self)))
+    return FALSE;
+
+  if (!self->actor)
+    return FALSE;
+
+  get_target_size (self, &width, &height);
+  downscale_factor = calculate_downscale_factor (width, height, self->blur_radius);
+
+  updated =
+    update_actor_fbo (self, width, height, downscale_factor) &&
+    update_blur_fbo (self, &self->blur[VERTICAL], width, height, downscale_factor) &&
+    update_blur_fbo (self, &self->blur[HORIZONTAL], width, height, downscale_factor) &&
+    update_brightness_fbo (self, width, height, downscale_factor);
+
+  if (self->mode == SHELL_BLUR_MODE_BACKGROUND)
+    updated |= update_background_fbo (self, width, height);
+
+  self->tex_width = width;
+  self->tex_height = height;
+  self->downscale_factor = downscale_factor;
+
+  return updated;
+}
+
+static void
+paint_actor_offscreen (ShellBlurEffect     *self,
+                       ClutterPaintContext *paint_context)
+{
+  self->old_opacity_override = clutter_actor_get_opacity_override (self->actor);
+  clutter_actor_set_opacity_override (self->actor, 0xff);
+
+  /* Draw the actor contents into the actor offscreen framebuffer */
+  clear_framebuffer (self->actor_fb.framebuffer);
+
+  cogl_framebuffer_push_matrix (self->actor_fb.framebuffer);
+  cogl_framebuffer_scale (self->actor_fb.framebuffer,
+                          1.f / self->downscale_factor,
+                          1.f / self->downscale_factor,
+                          1.f);
+
+  clutter_paint_context_push_framebuffer (paint_context,
+                                          self->actor_fb.framebuffer);
+
+  clutter_actor_continue_paint (self->actor, paint_context);
+
+  cogl_framebuffer_pop_matrix (self->actor_fb.framebuffer);
+  clutter_paint_context_pop_framebuffer (paint_context);
+
+  clutter_actor_set_opacity_override (self->actor, self->old_opacity_override);
+}
+
+static gboolean
+needs_repaint (ShellBlurEffect         *self,
+               ClutterEffectPaintFlags  flags)
+{
+  gboolean dirty_actor;
+
+  dirty_actor = (flags & CLUTTER_EFFECT_PAINT_ACTOR_DIRTY);
+
+  switch (self->mode)
+    {
+    case SHELL_BLUR_MODE_ACTOR:
+      return !self->brightness_fb.framebuffer || dirty_actor;
+
+    case SHELL_BLUR_MODE_BACKGROUND:
+      return TRUE;
+    }
+
+  return TRUE;
+}
+
+static void
+shell_blur_effect_paint (ClutterEffect           *effect,
+                         ClutterPaintContext     *paint_context,
+                         ClutterEffectPaintFlags  flags)
+{
+  ShellBlurEffect *self = SHELL_BLUR_EFFECT (effect);
+
+  if (self->blur_radius > 0)
+    {
+      if (needs_repaint (self, flags))
+        {
+          /* Failing to create or update the offscreen framebuffers prevents
+           * the entire effect to be applied.
+           */
+          if (!update_framebuffers (self))
+            goto fail;
+
+          switch (self->mode)
+            {
+            case SHELL_BLUR_MODE_ACTOR:
+              paint_actor_offscreen (self, paint_context);
+              apply_blur (self, paint_context, &self->actor_fb);
+              break;
+
+            case SHELL_BLUR_MODE_BACKGROUND:
+              paint_background (self, paint_context);
+              apply_blur (self, paint_context, &self->background_fb);
+              break;
+            }
+        }
+
+      paint_texture (self, paint_context);
+
+      /* Background blur needs to paint the actor after painting the blurred
+       * background.
+       */
+      switch (self->mode)
+        {
+        case SHELL_BLUR_MODE_ACTOR:
+          break;
+
+        case SHELL_BLUR_MODE_BACKGROUND:
+          clutter_actor_continue_paint (self->actor, paint_context);
+          break;
+        }
+
+      return;
+    }
+
+fail:
+  /* When no blur is applied, or the offscreen framebuffers
+   * couldn't be created, fallback to simply painting the actor.
+   */
+  clutter_actor_continue_paint (self->actor, paint_context);
+}
+
+static gboolean
+shell_blur_effect_modify_paint_volume (ClutterEffect      *effect,
+                                       ClutterPaintVolume *volume)
+{
+  ShellBlurEffect *self = SHELL_BLUR_EFFECT (effect);
+  graphene_point3d_t origin;
+  float width;
+  float height;
+
+  clutter_paint_volume_get_origin (volume, &origin);
+  width = clutter_paint_volume_get_width (volume);
+  height = clutter_paint_volume_get_height (volume);
+
+  origin.y -= self->blur_radius;
+  origin.x -= self->blur_radius;
+
+  height += 2 * self->blur_radius;
+  width += 2 * self->blur_radius;
+
+  clutter_paint_volume_set_origin (volume, &origin);
+  clutter_paint_volume_set_width (volume, width);
+  clutter_paint_volume_set_height (volume, height);
+
+  return TRUE;
+}
+
+static void
+shell_blur_effect_finalize (GObject *object)
+{
+  ShellBlurEffect *self = (ShellBlurEffect *)object;
+
+  clear_framebuffer_data (&self->actor_fb);
+  clear_framebuffer_data (&self->background_fb);
+  clear_framebuffer_data (&self->brightness_fb);
+  clear_framebuffer_data (&self->blur[VERTICAL].data);
+  clear_framebuffer_data (&self->blur[HORIZONTAL].data);
+
+  g_clear_pointer (&self->actor_fb.pipeline, cogl_object_unref);
+  g_clear_pointer (&self->background_fb.pipeline, cogl_object_unref);
+  g_clear_pointer (&self->brightness_fb.pipeline, cogl_object_unref);
+  g_clear_pointer (&self->blur[VERTICAL].data.pipeline, cogl_object_unref);
+  g_clear_pointer (&self->blur[HORIZONTAL].data.pipeline, cogl_object_unref);
+
+  G_OBJECT_CLASS (shell_blur_effect_parent_class)->finalize (object);
+}
+
+static void
+shell_blur_effect_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  ShellBlurEffect *self = SHELL_BLUR_EFFECT (object);
+
+  switch (prop_id)
+    {
+    case PROP_BLUR_RADIUS:
+      g_value_set_int (value, self->blur_radius);
+      break;
+
+    case PROP_BRIGHTNESS:
+      g_value_set_int (value, self->brightness);
+      break;
+
+    case PROP_MODE:
+      g_value_set_enum (value, self->mode);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+shell_blur_effect_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  ShellBlurEffect *self = SHELL_BLUR_EFFECT (object);
+
+  switch (prop_id)
+    {
+    case PROP_BLUR_RADIUS:
+      shell_blur_effect_set_blur_radius (self, g_value_get_int (value));
+      break;
+
+    case PROP_BRIGHTNESS:
+      shell_blur_effect_set_brightness (self, g_value_get_float (value));
+      break;
+
+    case PROP_MODE:
+      shell_blur_effect_set_mode (self, g_value_get_enum (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+shell_blur_effect_class_init (ShellBlurEffectClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
+  ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass);
+
+  object_class->finalize = shell_blur_effect_finalize;
+  object_class->get_property = shell_blur_effect_get_property;
+  object_class->set_property = shell_blur_effect_set_property;
+
+  meta_class->set_actor = shell_blur_effect_set_actor;
+
+  effect_class->paint = shell_blur_effect_paint;
+  effect_class->modify_paint_volume = shell_blur_effect_modify_paint_volume;
+
+  properties[PROP_BLUR_RADIUS] =
+    g_param_spec_int ("blur-radius",
+                      "Blur radius",
+                      "Blur radius",
+                      0, G_MAXINT, 0,
+                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_BRIGHTNESS] =
+    g_param_spec_float ("brightness",
+                        "Brightness",
+                        "Brightness",
+                        0.f, 1.f, 1.f,
+                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  properties[PROP_MODE] =
+    g_param_spec_enum ("mode",
+                       "Blur mode",
+                       "Blur mode",
+                       SHELL_TYPE_BLUR_MODE,
+                       SHELL_BLUR_MODE_ACTOR,
+                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+shell_blur_effect_init (ShellBlurEffect *self)
+{
+  self->mode = SHELL_BLUR_MODE_ACTOR;
+  self->blur_radius = 0;
+  self->brightness = 1.f;
+
+  self->actor_fb.pipeline = create_base_pipeline ();
+  self->background_fb.pipeline = create_base_pipeline ();
+  self->brightness_fb.pipeline = create_brightness_pipeline ();
+  self->brightness_uniform =
+    cogl_pipeline_get_uniform_location (self->brightness_fb.pipeline, "brightness");
+
+  setup_blur (&self->blur[VERTICAL], VERTICAL);
+  setup_blur (&self->blur[HORIZONTAL], HORIZONTAL);
+}
+
+ShellBlurEffect *
+shell_blur_effect_new (void)
+{
+  return g_object_new (SHELL_TYPE_BLUR_EFFECT, NULL);
+}
+
+int
+shell_blur_effect_get_blur_radius (ShellBlurEffect *self)
+{
+  g_return_val_if_fail (SHELL_IS_BLUR_EFFECT (self), -1);
+
+  return self->blur_radius;
+}
+
+void
+shell_blur_effect_set_blur_radius (ShellBlurEffect *self,
+                                   int              radius)
+{
+  g_return_if_fail (SHELL_IS_BLUR_EFFECT (self));
+
+  if (self->blur_radius == radius)
+    return;
+
+  self->blur_radius = radius;
+  if (self->actor)
+    {
+      float width, height;
+      float downscale_factor;
+
+      /* If the new radius results in a new downscale factor, invalidate the
+       * framebuffer.
+       */
+      get_target_size (self, &width, &height);
+      downscale_factor = calculate_downscale_factor (width, height, radius);
+
+      if (radius == 0 || downscale_factor != self->downscale_factor)
+        clear_framebuffer_data (&self->brightness_fb);
+
+      clutter_effect_queue_repaint (CLUTTER_EFFECT (self));
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BLUR_RADIUS]);
+}
+
+float
+shell_blur_effect_get_brightness (ShellBlurEffect *self)
+{
+  g_return_val_if_fail (SHELL_IS_BLUR_EFFECT (self), FALSE);
+
+  return self->brightness;
+}
+
+void
+shell_blur_effect_set_brightness (ShellBlurEffect *self,
+                                  float            brightness)
+{
+  g_return_if_fail (SHELL_IS_BLUR_EFFECT (self));
+
+  if (self->brightness == brightness)
+    return;
+
+  self->brightness = brightness;
+  if (self->actor)
+    clutter_effect_queue_repaint (CLUTTER_EFFECT (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BRIGHTNESS]);
+}
+
+ShellBlurMode
+shell_blur_effect_get_mode (ShellBlurEffect *self)
+{
+  g_return_val_if_fail (SHELL_IS_BLUR_EFFECT (self), -1);
+
+  return self->mode;
+}
+
+void
+shell_blur_effect_set_mode (ShellBlurEffect *self,
+                            ShellBlurMode    mode)
+{
+  g_return_if_fail (SHELL_IS_BLUR_EFFECT (self));
+
+  if (self->mode == mode)
+    return;
+
+  self->mode = mode;
+
+  switch (mode)
+    {
+    case SHELL_BLUR_MODE_ACTOR:
+      clear_framebuffer (&self->background_fb);
+      break;
+
+    case SHELL_BLUR_MODE_BACKGROUND:
+    default:
+      /* Do nothing */
+      break;
+    }
+
+  if (self->actor)
+    clutter_effect_queue_repaint (CLUTTER_EFFECT (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODE]);
+}
diff --git a/src/shell-blur-effect.h b/src/shell-blur-effect.h
new file mode 100644
index 0000000000..94d24f6b51
--- /dev/null
+++ b/src/shell-blur-effect.h
@@ -0,0 +1,57 @@
+/* shell-blur-effect.h
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+/**
+ * ShellBlurMode:
+ * @SHELL_BLUR_MODE_ACTOR: blur the actor contents, and its children
+ * @SHELL_BLUR_MODE_BACKGROUND: blur what's beneath the actor
+ *
+ * The mode of blurring of the effect.
+ */
+typedef enum
+{
+  SHELL_BLUR_MODE_ACTOR,
+  SHELL_BLUR_MODE_BACKGROUND,
+} ShellBlurMode;
+
+#define SHELL_TYPE_BLUR_EFFECT (shell_blur_effect_get_type())
+G_DECLARE_FINAL_TYPE (ShellBlurEffect, shell_blur_effect, SHELL, BLUR_EFFECT, ClutterEffect)
+
+ShellBlurEffect *shell_blur_effect_new (void);
+
+int shell_blur_effect_get_blur_radius (ShellBlurEffect *self);
+void shell_blur_effect_set_blur_radius (ShellBlurEffect *self,
+                                        int              radius);
+
+float shell_blur_effect_get_brightness (ShellBlurEffect *self);
+void shell_blur_effect_set_brightness (ShellBlurEffect *self,
+                                       float            brightness);
+
+ShellBlurMode shell_blur_effect_get_mode (ShellBlurEffect *self);
+void shell_blur_effect_set_mode (ShellBlurEffect *self,
+                                 ShellBlurMode    mode);
+
+G_END_DECLS



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