blur-effect: Use ClutterBlurNode

With ClutterBlurNode available, we can remove our own implementation
and delegate the blur shader and framebuffers. This simply replaces
the pair of layer nodes (vblur and hblur) with a ClutterBlurNode,
and removes all dead code.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1528>
This commit is contained in:
Georges Basile Stavracas Neto 2020-12-09 19:20:16 -03:00 committed by Marge Bot
parent 52ccf86599
commit f7019bdd0d

View File

@ -26,7 +26,7 @@
* SECTION:shell-blur-effect * SECTION:shell-blur-effect
* @short_description: Blur effect for actors * @short_description: Blur effect for actors
* *
* #ShellBlurEffect is a moderately fast gaussian blur implementation. It also has * #ShellBlurEffect is a blur implementation based on Clutter. It also has
* an optional brightness property. * an optional brightness property.
* *
* # Modes * # Modes
@ -38,89 +38,8 @@
* @SHELL_BLUR_MODE_BACKGROUND can be computationally expensive, since the contents * @SHELL_BLUR_MODE_BACKGROUND can be computationally expensive, since the contents
* beneath the actor cannot be cached, so beware of the performance implications * beneath the actor cannot be cached, so beware of the performance implications
* of using this blur mode. * of using this blur mode.
*
* # 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
*
* #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/
*
* ## 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 gchar *gaussian_blur_glsl_declarations =
"uniform float sigma; \n"
"uniform float pixel_step; \n"
"uniform int vertical; \n";
static const gchar *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";
static const gchar *brightness_glsl_declarations = static const gchar *brightness_glsl_declarations =
"uniform float brightness; \n"; "uniform float brightness; \n";
@ -130,12 +49,6 @@ static const gchar *brightness_glsl =
#define MIN_DOWNSCALE_SIZE 256.f #define MIN_DOWNSCALE_SIZE 256.f
#define MAX_SIGMA 6.f #define MAX_SIGMA 6.f
typedef enum
{
VERTICAL,
HORIZONTAL,
} BlurType;
typedef enum typedef enum
{ {
ACTOR_PAINTED = 1 << 0, ACTOR_PAINTED = 1 << 0,
@ -149,23 +62,12 @@ typedef struct
CoglTexture *texture; CoglTexture *texture;
} FramebufferData; } FramebufferData;
typedef struct
{
FramebufferData data;
BlurType type;
int sigma_uniform;
int pixel_step_uniform;
int vertical_uniform;
} BlurData;
struct _ShellBlurEffect struct _ShellBlurEffect
{ {
ClutterEffect parent_instance; ClutterEffect parent_instance;
ClutterActor *actor; ClutterActor *actor;
BlurData blur[2];
unsigned int tex_width; unsigned int tex_width;
unsigned int tex_height; unsigned int tex_height;
@ -219,29 +121,6 @@ create_base_pipeline (void)
return cogl_pipeline_copy (base_pipeline); 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* static CoglPipeline*
create_brightness_pipeline (void) create_brightness_pipeline (void)
{ {
@ -263,59 +142,17 @@ create_brightness_pipeline (void)
return cogl_pipeline_copy (brightness_pipeline); return cogl_pipeline_copy (brightness_pipeline);
} }
static void
setup_blur (BlurData *blur,
BlurType type)
{
blur->type = type;
blur->data.pipeline = create_blur_pipeline ();
blur->sigma_uniform =
cogl_pipeline_get_uniform_location (blur->data.pipeline, "sigma");
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 static void
update_blur_uniforms (ShellBlurEffect *self, update_brightness (ShellBlurEffect *self,
BlurData *blur) uint8_t paint_opacity)
{ {
gboolean is_vertical = blur->type == VERTICAL; cogl_pipeline_set_color4ub (self->brightness_fb.pipeline,
paint_opacity,
paint_opacity,
paint_opacity,
paint_opacity);
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->sigma_uniform > -1)
{
cogl_pipeline_set_uniform_1f (blur->data.pipeline,
blur->sigma_uniform,
self->sigma / 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) if (self->brightness_uniform > -1)
{ {
cogl_pipeline_set_uniform_1f (self->brightness_fb.pipeline, cogl_pipeline_set_uniform_1f (self->brightness_fb.pipeline,
@ -412,26 +249,6 @@ update_brightness_fbo (ShellBlurEffect *self,
downscale_factor); 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 static gboolean
update_background_fbo (ShellBlurEffect *self, update_background_fbo (ShellBlurEffect *self,
unsigned int width, unsigned int width,
@ -496,8 +313,6 @@ shell_blur_effect_set_actor (ClutterActorMeta *meta,
clear_framebuffer_data (&self->actor_fb); clear_framebuffer_data (&self->actor_fb);
clear_framebuffer_data (&self->background_fb); clear_framebuffer_data (&self->background_fb);
clear_framebuffer_data (&self->brightness_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 */ /* we keep a back pointer here, to avoid going through the ActorMeta */
self->actor = clutter_actor_meta_get_actor (meta); self->actor = clutter_actor_meta_get_actor (meta);
@ -543,7 +358,8 @@ update_actor_box (ShellBlurEffect *self,
static void static void
add_blurred_pipeline (ShellBlurEffect *self, add_blurred_pipeline (ShellBlurEffect *self,
ClutterPaintNode *node) ClutterPaintNode *node,
uint8_t paint_opacity)
{ {
g_autoptr (ClutterPaintNode) pipeline_node = NULL; g_autoptr (ClutterPaintNode) pipeline_node = NULL;
float width, height; float width, height;
@ -553,7 +369,7 @@ add_blurred_pipeline (ShellBlurEffect *self,
*/ */
clutter_actor_get_size (self->actor, &width, &height); clutter_actor_get_size (self->actor, &width, &height);
update_brightness_uniform (self); update_brightness (self, paint_opacity);
pipeline_node = clutter_pipeline_node_new (self->brightness_fb.pipeline); pipeline_node = clutter_pipeline_node_new (self->brightness_fb.pipeline);
clutter_paint_node_set_static_name (pipeline_node, "ShellBlurEffect (final)"); clutter_paint_node_set_static_name (pipeline_node, "ShellBlurEffect (final)");
@ -573,26 +389,13 @@ create_blur_nodes (ShellBlurEffect *self,
uint8_t paint_opacity) uint8_t paint_opacity)
{ {
g_autoptr (ClutterPaintNode) brightness_node = NULL; g_autoptr (ClutterPaintNode) brightness_node = NULL;
g_autoptr (ClutterPaintNode) hblur_node = NULL; g_autoptr (ClutterPaintNode) blur_node = NULL;
g_autoptr (ClutterPaintNode) vblur_node = NULL;
BlurData *vblur;
BlurData *hblur;
float width; float width;
float height; float height;
vblur = &self->blur[VERTICAL];
hblur = &self->blur[HORIZONTAL];
clutter_actor_get_size (self->actor, &width, &height); clutter_actor_get_size (self->actor, &width, &height);
update_brightness_uniform (self); update_brightness (self, paint_opacity);
cogl_pipeline_set_color4ub (self->brightness_fb.pipeline,
paint_opacity,
paint_opacity,
paint_opacity,
paint_opacity);
brightness_node = clutter_layer_node_new_to_framebuffer (self->brightness_fb.framebuffer, brightness_node = clutter_layer_node_new_to_framebuffer (self->brightness_fb.framebuffer,
self->brightness_fb.pipeline); self->brightness_fb.pipeline);
clutter_paint_node_set_static_name (brightness_node, "ShellBlurEffect (brightness)"); clutter_paint_node_set_static_name (brightness_node, "ShellBlurEffect (brightness)");
@ -603,46 +406,21 @@ create_blur_nodes (ShellBlurEffect *self,
width, height, width, height,
}); });
/* Horizontal pass: blur_node = clutter_blur_node_new (self->tex_width / self->downscale_factor,
* self->tex_height / self->downscale_factor,
* This layer node contains the vertically blurred image; draw the it using the self->sigma / self->downscale_factor);
* horizontal blur pipeline. clutter_paint_node_set_static_name (blur_node, "ShellBlurEffect (blur)");
*/ clutter_paint_node_add_child (brightness_node, blur_node);
update_blur_uniforms (self, hblur); clutter_paint_node_add_rectangle (blur_node,
hblur_node = clutter_layer_node_new_to_framebuffer (hblur->data.framebuffer,
hblur->data.pipeline);
clutter_paint_node_set_static_name (hblur_node, "ShellBlurEffect (horizontal pass)");
clutter_paint_node_add_child (brightness_node, hblur_node);
clutter_paint_node_add_rectangle (hblur_node,
&(ClutterActorBox) { &(ClutterActorBox) {
0.f, 0.f, 0.f, 0.f,
cogl_texture_get_width (self->brightness_fb.texture), cogl_texture_get_width (self->brightness_fb.texture),
cogl_texture_get_height (self->brightness_fb.texture), cogl_texture_get_height (self->brightness_fb.texture),
}); });
/* Vertical pass:
*
* Draw the actor contents into the vblur framebuffer using the vertical
* blur pipeline, which will output a vertically blurred image that will
* be used by the parent node (hblur_node).
*/
update_blur_uniforms (self, vblur);
vblur_node = clutter_layer_node_new_to_framebuffer (vblur->data.framebuffer,
vblur->data.pipeline);
clutter_paint_node_set_static_name (vblur_node, "ShellBlurEffect (vertical pass)");
clutter_paint_node_add_child (hblur_node, vblur_node);
clutter_paint_node_add_rectangle (vblur_node,
&(ClutterActorBox) {
0.f, 0.f,
cogl_texture_get_width (hblur->data.texture),
cogl_texture_get_height (hblur->data.texture)
});
self->cache_flags |= BLUR_APPLIED; self->cache_flags |= BLUR_APPLIED;
return g_steal_pointer (&vblur_node); return g_steal_pointer (&blur_node);
} }
static void static void
@ -706,11 +484,8 @@ update_framebuffers (ShellBlurEffect *self,
downscale_factor = calculate_downscale_factor (width, height, self->sigma); downscale_factor = calculate_downscale_factor (width, height, self->sigma);
updated = updated = update_actor_fbo (self, width, height, downscale_factor) &&
update_actor_fbo (self, width, height, downscale_factor) && update_brightness_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) if (self->mode == SHELL_BLUR_MODE_BACKGROUND)
updated = updated && update_background_fbo (self, width, height); updated = updated && update_background_fbo (self, width, height);
@ -831,6 +606,17 @@ shell_blur_effect_paint_node (ClutterEffect *effect,
{ {
g_autoptr (ClutterPaintNode) blur_node = NULL; g_autoptr (ClutterPaintNode) blur_node = NULL;
switch (self->mode)
{
case SHELL_BLUR_MODE_ACTOR:
paint_opacity = clutter_actor_get_paint_opacity (self->actor);
break;
case SHELL_BLUR_MODE_BACKGROUND:
paint_opacity = 255;
break;
}
if (needs_repaint (self, flags)) if (needs_repaint (self, flags))
{ {
ClutterActorBox source_actor_box; ClutterActorBox source_actor_box;
@ -843,17 +629,15 @@ shell_blur_effect_paint_node (ClutterEffect *effect,
if (!update_framebuffers (self, paint_context, &source_actor_box)) if (!update_framebuffers (self, paint_context, &source_actor_box))
goto fail; goto fail;
blur_node = create_blur_nodes (self, node, paint_opacity);
switch (self->mode) switch (self->mode)
{ {
case SHELL_BLUR_MODE_ACTOR: case SHELL_BLUR_MODE_ACTOR:
paint_opacity = clutter_actor_get_paint_opacity (self->actor);
blur_node = create_blur_nodes (self, node, paint_opacity);
paint_actor_offscreen (self, blur_node, flags); paint_actor_offscreen (self, blur_node, flags);
break; break;
case SHELL_BLUR_MODE_BACKGROUND: case SHELL_BLUR_MODE_BACKGROUND:
blur_node = create_blur_nodes (self, node, 255);
paint_background (self, blur_node, paint_context, &source_actor_box); paint_background (self, blur_node, paint_context, &source_actor_box);
break; break;
} }
@ -861,7 +645,7 @@ shell_blur_effect_paint_node (ClutterEffect *effect,
else else
{ {
/* Use the cached pipeline if no repaint is needed */ /* Use the cached pipeline if no repaint is needed */
add_blurred_pipeline (self, node); add_blurred_pipeline (self, node, paint_opacity);
} }
/* Background blur needs to paint the actor after painting the blurred /* Background blur needs to paint the actor after painting the blurred
@ -895,14 +679,10 @@ shell_blur_effect_finalize (GObject *object)
clear_framebuffer_data (&self->actor_fb); clear_framebuffer_data (&self->actor_fb);
clear_framebuffer_data (&self->background_fb); clear_framebuffer_data (&self->background_fb);
clear_framebuffer_data (&self->brightness_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->actor_fb.pipeline, cogl_object_unref);
g_clear_pointer (&self->background_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->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); G_OBJECT_CLASS (shell_blur_effect_parent_class)->finalize (object);
} }
@ -1013,9 +793,6 @@ shell_blur_effect_init (ShellBlurEffect *self)
self->brightness_fb.pipeline = create_brightness_pipeline (); self->brightness_fb.pipeline = create_brightness_pipeline ();
self->brightness_uniform = self->brightness_uniform =
cogl_pipeline_get_uniform_location (self->brightness_fb.pipeline, "brightness"); cogl_pipeline_get_uniform_location (self->brightness_fb.pipeline, "brightness");
setup_blur (&self->blur[VERTICAL], VERTICAL);
setup_blur (&self->blur[HORIZONTAL], HORIZONTAL);
} }
ShellBlurEffect * ShellBlurEffect *