diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index a7a3d44a51..232bd5bd7f 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -166,7 +166,7 @@ OPTIONS *--detect-client-leader*:: Use 'WM_CLIENT_LEADER' to group windows, and consider windows in the same group focused at the same time. 'WM_TRANSIENT_FOR' has higher priority if *--detect-transient* is enabled, too. -*--blur-method*, *--blur-size*, *--blur-deviation*:: +*--blur-method*, *--blur-size*, *--blur-deviation*, *--blur-strength*:: Parameters for background blurring, see the *BLUR* section for more information. *--blur-background*:: @@ -397,8 +397,8 @@ Available options of the 'blur' section are: :: *method*::: A string. Controls the blur method. Corresponds to the *--blur-method* command line option. Available choices are: - 'none' to disable blurring; 'gaussian' for gaussian blur; 'box' for box blur; 'kernel' for convolution blur with a custom kernel. - Note: 'gaussian' and 'box' blur methods are only supported by the experimental backends. + 'none' to disable blurring; 'gaussian' for gaussian blur; 'box' for box blur; 'kernel' for convolution blur with a custom kernel; 'dual_kawase' for dual-filter kawase blur. + Note: 'gaussian', 'box' and 'dual_kawase' blur methods are only supported by the experimental backends. (default: none) *size*::: @@ -407,6 +407,9 @@ Available options of the 'blur' section are: :: *deviation*::: A floating point number. The standard deviation for the 'gaussian' blur method. Corresponds to the *--blur-deviation* command line option (default: 0.84089642). + *strength*::: + An integer in the range 0-20. The strength of the 'dual_kawase' blur method. Corresponds to the *--blur-strength* command line option. If set to zero, the value requested by *--blur-size* is approximated (default: 5). + *kernel*::: A string. The kernel to use for the 'kernel' blur method, specified in the same format as the *--blur-kerns* option. Corresponds to the *--blur-kerns* command line option. diff --git a/picom.sample.conf b/picom.sample.conf index defdbeee39..201fa5f69a 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -143,6 +143,8 @@ focus-exclude = [ "class_g = 'Cairo-clock'" ]; # blur-size = 12 # # blur-deviation = false +# +# blur-strength = 5 # Blur background of semi-transparent / ARGB windows. # Bad in performance, with driver-dependent behavior. diff --git a/src/backend/backend.h b/src/backend/backend.h index f5d10ad809..c530a1e900 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -5,8 +5,8 @@ #include -#include "config.h" #include "compiler.h" +#include "config.h" #include "driver.h" #include "kernel.h" #include "region.h" @@ -65,6 +65,11 @@ struct kernel_blur_args { int kernel_count; }; +struct dual_kawase_blur_args { + int size; + int strength; +}; + struct backend_operations { // =========== Initialization =========== diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index f6ce580188..ad4aabfc8b 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -363,6 +363,69 @@ struct conv **generate_blur_kernel(enum blur_method method, void *args, int *ker return NULL; } +/// Generate kernel parameters for dual-kawase blur method. Falls back on approximating +/// standard gauss radius if strength is zero or below. +struct dual_kawase_params *generate_dual_kawase_params(void *args) { + struct dual_kawase_blur_args *blur_args = args; + static const struct { + int iterations; /// Number of down- and upsample iterations + float offset; /// Sample offset in half-pixels + int min_radius; /// Approximate gauss-blur with at least this + /// radius and std-deviation + } strength_levels[20] = { + {.iterations = 1, .offset = 1.25f, .min_radius = 1}, // LVL 1 + {.iterations = 1, .offset = 2.25f, .min_radius = 6}, // LVL 2 + {.iterations = 2, .offset = 2.00f, .min_radius = 11}, // LVL 3 + {.iterations = 2, .offset = 3.00f, .min_radius = 17}, // LVL 4 + {.iterations = 2, .offset = 4.25f, .min_radius = 24}, // LVL 5 + {.iterations = 3, .offset = 2.50f, .min_radius = 32}, // LVL 6 + {.iterations = 3, .offset = 3.25f, .min_radius = 40}, // LVL 7 + {.iterations = 3, .offset = 4.25f, .min_radius = 51}, // LVL 8 + {.iterations = 3, .offset = 5.50f, .min_radius = 67}, // LVL 9 + {.iterations = 4, .offset = 3.25f, .min_radius = 83}, // LVL 10 + {.iterations = 4, .offset = 4.00f, .min_radius = 101}, // LVL 11 + {.iterations = 4, .offset = 5.00f, .min_radius = 123}, // LVL 12 + {.iterations = 4, .offset = 6.00f, .min_radius = 148}, // LVL 13 + {.iterations = 4, .offset = 7.25f, .min_radius = 178}, // LVL 14 + {.iterations = 4, .offset = 8.25f, .min_radius = 208}, // LVL 15 + {.iterations = 5, .offset = 4.50f, .min_radius = 236}, // LVL 16 + {.iterations = 5, .offset = 5.25f, .min_radius = 269}, // LVL 17 + {.iterations = 5, .offset = 6.25f, .min_radius = 309}, // LVL 18 + {.iterations = 5, .offset = 7.25f, .min_radius = 357}, // LVL 19 + {.iterations = 5, .offset = 8.50f, .min_radius = 417}, // LVL 20 + }; + + auto params = ccalloc(1, struct dual_kawase_params); + params->iterations = 0; + params->offset = 1.0f; + + if (blur_args->strength <= 0 && blur_args->size) { + // find highest level that approximates blur-strength with the selected + // gaussian blur-radius + int lvl = 1; + while (strength_levels[lvl - 1].min_radius < blur_args->size && lvl < 20) { + ++lvl; + } + blur_args->strength = lvl; + } + if (blur_args->strength <= 0) { + // default value + blur_args->strength = 5; + } + + assert(blur_args->strength > 0 && blur_args->strength <= 20); + params->iterations = strength_levels[blur_args->strength - 1].iterations; + params->offset = strength_levels[blur_args->strength - 1].offset; + + // Expand sample area to cover the smallest texture / highest selected iteration: + // - Smallest texture dimensions are halved `iterations`-times + // - Upsample needs pixels two-times `offset` away from the border + // - Plus one for interpolation differences + params->expand = (1 << params->iterations) * 2 * (int)ceil(params->offset) + 1; + + return params; +} + void init_backend_base(struct backend_base *base, session_t *ps) { base->c = ps->c; base->loop = ps->loop; diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h index 28048b1cdb..938b02efe2 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -16,6 +16,15 @@ typedef struct conv conv; typedef struct backend_base backend_t; struct backend_operations; +struct dual_kawase_params { + /// Number of downsample passes + int iterations; + /// Pixel offset for down- and upsample + float offset; + /// Save area around blur target (@ref resize_width, @ref resize_height) + int expand; +}; + bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width, int height, const conv *kernel, xcb_render_picture_t shadow_pixel, xcb_pixmap_t *pixmap, xcb_render_picture_t *pict); @@ -41,3 +50,4 @@ default_backend_render_shadow(backend_t *backend_data, int width, int height, void init_backend_base(struct backend_base *base, session_t *ps); struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count); +struct dual_kawase_params *generate_dual_kawase_params(void *args); diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 1e5eafbaa4..d0d199121f 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -32,14 +32,24 @@ struct gl_blur_context { enum blur_method method; gl_blur_shader_t *blur_shader; - /// Temporary textures used for blurring. They are always the same size as the - /// target, so they are always big enough without resizing. + /// Temporary textures used for blurring + GLuint *blur_textures; + int blur_texture_count; + /// Temporary fbos used for blurring + GLuint *blur_fbos; + int blur_fbo_count; + + /// Cached dimensions of each blur_texture. They are the same size as the target, + /// so they are always big enough without resizing. /// Turns out calling glTexImage to resize is expensive, so we avoid that. - GLuint blur_texture[2]; - /// Temporary fbo used for blurring - GLuint blur_fbo; + struct texture_size { + int width; + int height; + } * texture_sizes; - int texture_width, texture_height; + /// Cached dimensions of the offscreen framebuffer. It's the same size as the + /// target but is expanded in either direction by resize_width / resize_height. + int fb_width, fb_height; /// How much do we need to resize the damaged region for blurring. int resize_width, resize_height; @@ -536,24 +546,247 @@ void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, /** * Blur contents in a particular region. */ +bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, + const int nrects, const GLuint vao[2]) { + auto bctx = (struct gl_blur_context *)ctx; + auto gd = (struct gl_data *)base; + + int dst_y_screen_coord = gd->height - extent->y2, + dst_y_fb_coord = bctx->fb_height - extent->y2; + + int curr = 0; + for (int i = 0; i < bctx->npasses; ++i) { + const gl_blur_shader_t *p = &bctx->blur_shader[i]; + assert(p->prog); + + assert(bctx->blur_textures[curr]); + + // The origin to use when sampling from the source texture + GLint texorig_x, texorig_y; + GLint tex_width, tex_height; + GLuint src_texture; + + if (i == 0) { + texorig_x = extent->x1; + texorig_y = dst_y_screen_coord; + src_texture = gd->back_texture; + tex_width = gd->width; + tex_height = gd->height; + } else { + texorig_x = extent->x1 + bctx->resize_width; + texorig_y = dst_y_fb_coord - bctx->resize_height; + src_texture = bctx->blur_textures[curr]; + auto src_size = bctx->texture_sizes[curr]; + tex_width = src_size.width; + tex_height = src_size.height; + } + + glBindTexture(GL_TEXTURE_2D, src_texture); + glUseProgram(p->prog); + glUniform2f(p->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, + 1.0f / (GLfloat)tex_height); + if (i < bctx->npasses - 1) { + assert(bctx->blur_fbos[0]); + assert(bctx->blur_textures[!curr]); + + // not last pass, draw into framebuffer, with resized regions + glBindVertexArray(vao[1]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[0]); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, bctx->blur_textures[!curr], 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + log_error("Framebuffer attachment failed."); + return false; + } + + glUniform1f(p->unifm_opacity, 1.0); + glUniform2f(p->orig_loc, (GLfloat)bctx->resize_width, + -(GLfloat)bctx->resize_height); + } else { + // last pass, draw directly into the back buffer, with origin + // regions + glBindVertexArray(vao[0]); + glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); + + glUniform1f(p->unifm_opacity, (float)opacity); + glUniform2f(p->orig_loc, 0, 0); + } + + glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); + glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); + + // XXX use multiple draw calls is probably going to be slow than + // just simply blur the whole area. + + curr = !curr; + } + + return true; +} + +bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, + const int nrects, const GLuint vao[2]) { + auto bctx = (struct gl_blur_context *)ctx; + auto gd = (struct gl_data *)base; + + int dst_y_screen_coord = gd->height - extent->y2, + dst_y_fb_coord = bctx->fb_height - extent->y2; + + int iterations = bctx->blur_texture_count; + int scale_factor = 1; + + // Kawase downsample pass + const gl_blur_shader_t *down_pass = &bctx->blur_shader[0]; + assert(down_pass->prog); + glUseProgram(down_pass->prog); + + // Downsample always renders with resize offset + glUniform2f(down_pass->orig_loc, (GLfloat)bctx->resize_width, + -(GLfloat)bctx->resize_height); + + for (int i = 0; i < iterations; ++i) { + // Scale output width / height by half in each iteration + scale_factor <<= 1; + + GLuint src_texture; + int tex_width, tex_height; + int texorig_x, texorig_y; + + if (i == 0) { + // first pass: copy from back buffer + src_texture = gd->back_texture; + tex_width = gd->width; + tex_height = gd->height; + + texorig_x = extent->x1; + texorig_y = dst_y_screen_coord; + } else { + // copy from previous pass + src_texture = bctx->blur_textures[i - 1]; + auto src_size = bctx->texture_sizes[i - 1]; + tex_width = src_size.width; + tex_height = src_size.height; + + texorig_x = extent->x1 + bctx->resize_width; + texorig_y = dst_y_fb_coord - bctx->resize_height; + } + + assert(src_texture); + assert(bctx->blur_fbos[i]); + + glBindTexture(GL_TEXTURE_2D, src_texture); + glBindVertexArray(vao[1]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + + glUniform2f(down_pass->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); + glUniform1f(down_pass->scale_loc, (GLfloat)scale_factor); + + glUniform2f(down_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, + 1.0f / (GLfloat)tex_height); + + glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); + } + + // Kawase upsample pass + const gl_blur_shader_t *up_pass = &bctx->blur_shader[1]; + assert(up_pass->prog); + glUseProgram(up_pass->prog); + + // Upsample always samples from textures with resize offset + glUniform2f(up_pass->texorig_loc, (GLfloat)(extent->x1 + bctx->resize_width), + (GLfloat)(dst_y_fb_coord - bctx->resize_height)); + + for (int i = iterations - 1; i >= 0; --i) { + // Scale output width / height back by two in each iteration + scale_factor >>= 1; + + const GLuint src_texture = bctx->blur_textures[i]; + assert(src_texture); + + // Calculate normalized half-width/-height of a src pixel + auto src_size = bctx->texture_sizes[i]; + int tex_width = src_size.width; + int tex_height = src_size.height; + + glBindTexture(GL_TEXTURE_2D, src_texture); + if (i > 0) { + assert(bctx->blur_fbos[i - 1]); + + // not last pass, draw into next framebuffer + glBindVertexArray(vao[1]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + + glUniform2f(up_pass->orig_loc, (GLfloat)bctx->resize_width, + -(GLfloat)bctx->resize_height); + glUniform1f(up_pass->unifm_opacity, (GLfloat)1); + } else { + // last pass, draw directly into the back buffer + glBindVertexArray(vao[0]); + glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); + + glUniform2f(up_pass->orig_loc, (GLfloat)0, (GLfloat)0); + glUniform1f(up_pass->unifm_opacity, (GLfloat)opacity); + } + + glUniform1f(up_pass->scale_loc, (GLfloat)scale_factor); + glUniform2f(up_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, + 1.0f / (GLfloat)tex_height); + + glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); + } + + return true; +} + bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blur, const region_t *reg_visible attr_unused) { - struct gl_blur_context *bctx = ctx; + auto bctx = (struct gl_blur_context *)ctx; auto gd = (struct gl_data *)base; - if (gd->width + bctx->resize_width * 2 != bctx->texture_width || - gd->height + bctx->resize_height * 2 != bctx->texture_height) { + bool ret = false; + + if (gd->width + bctx->resize_width * 2 != bctx->fb_width || + gd->height + bctx->resize_height * 2 != bctx->fb_height) { // Resize the temporary textures used for blur in case the root // size changed - bctx->texture_width = gd->width + bctx->resize_width * 2; - bctx->texture_height = gd->height + bctx->resize_height * 2; + bctx->fb_width = gd->width + bctx->resize_width * 2; + bctx->fb_height = gd->height + bctx->resize_height * 2; + + for (int i = 0; i < bctx->blur_texture_count; ++i) { + auto tex_size = bctx->texture_sizes + i; + if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { + // Use smaller textures for each iteration (quarter of the + // previous texture) + tex_size->width = 1 + ((bctx->fb_width - 1) >> (i + 1)); + tex_size->height = 1 + ((bctx->fb_height - 1) >> (i + 1)); + } else { + tex_size->width = bctx->fb_width; + tex_size->height = bctx->fb_height; + } - glBindTexture(GL_TEXTURE_2D, bctx->blur_texture[0]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, bctx->texture_width, - bctx->texture_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, bctx->blur_texture[1]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, bctx->texture_width, - bctx->texture_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width, + tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + + if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { + // Attach texture to FBO target + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + bctx->blur_textures[i], 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != + GL_FRAMEBUFFER_COMPLETE) { + log_error("Framebuffer attachment failed."); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return false; + } + } + } + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } // Remainder: regions are in Xorg coordinates @@ -562,13 +795,10 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu const rect_t *extent = pixman_region32_extents((region_t *)reg_blur), *extent_resized = pixman_region32_extents(®_blur_resized); int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1; - int dst_y_resized_screen_coord = gd->height - extent_resized->y2, - dst_y_resized_fb_coord = bctx->texture_height - extent_resized->y2; if (width == 0 || height == 0) { return true; } - bool ret = false; int nrects, nrects_resized; const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects), *rects_resized = @@ -580,13 +810,13 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu auto coord = ccalloc(nrects * 16, GLint); auto indices = ccalloc(nrects * 6, GLuint); x_rect_to_coords(nrects, rects, extent_resized->x1, extent_resized->y2, - bctx->texture_height, gd->height, false, coord, indices); + bctx->fb_height, gd->height, false, coord, indices); auto coord_resized = ccalloc(nrects_resized * 16, GLint); auto indices_resized = ccalloc(nrects_resized * 6, GLuint); x_rect_to_coords(nrects_resized, rects_resized, extent_resized->x1, - extent_resized->y2, bctx->texture_height, bctx->texture_height, - false, coord_resized, indices_resized); + extent_resized->y2, bctx->fb_height, bctx->fb_height, false, + coord_resized, indices_resized); pixman_region32_fini(®_blur_resized); GLuint vao[2]; @@ -620,74 +850,12 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - int curr = 0; - for (int i = 0; i < bctx->npasses; ++i) { - const gl_blur_shader_t *p = &bctx->blur_shader[i]; - assert(p->prog); - - assert(bctx->blur_texture[curr]); - - // The origin to use when sampling from the source texture - GLint texorig_x, texorig_y; - GLint tex_width, tex_height; - GLuint src_texture; - - if (i == 0) { - texorig_x = extent_resized->x1; - texorig_y = dst_y_resized_screen_coord; - tex_width = gd->width; - tex_height = gd->height; - src_texture = gd->back_texture; - } else { - texorig_x = 0; - texorig_y = 0; - tex_width = bctx->texture_width; - tex_height = bctx->texture_height; - src_texture = bctx->blur_texture[curr]; - } - - glBindTexture(GL_TEXTURE_2D, src_texture); - glUseProgram(p->prog); - glUniform2f(p->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, - 1.0f / (GLfloat)tex_height); - if (i < bctx->npasses - 1) { - // not last pass, draw into framebuffer, with resized regions - glBindVertexArray(vao[1]); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbo); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, bctx->blur_texture[!curr], 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - log_error("Framebuffer attachment failed."); - goto end; - } - glUniform1f(p->unifm_opacity, 1.0); - // For other than last pass, we are drawing to a texture, we - // translate the render origin so we don't need a big texture - glUniform2f(p->orig_loc, -(GLfloat)extent_resized->x1, - -(GLfloat)dst_y_resized_fb_coord); - } else { - // last pass, draw directly into the back buffer, with origin - // regions - glBindVertexArray(vao[0]); - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); - glUniform1f(p->unifm_opacity, (float)opacity); - glUniform2f(p->orig_loc, 0, 0); - } - - glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); - glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); - - // XXX use multiple draw calls is probably going to be slow than - // just simply blur the whole area. - - curr = !curr; + if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { + ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, nrects, vao); + } else { + ret = gl_kernel_blur(base, opacity, ctx, extent_resized, nrects, vao); } - ret = true; - -end: glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); @@ -695,6 +863,7 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu glDeleteBuffers(4, bo); glBindVertexArray(0); glDeleteVertexArrays(2, vao); + glUseProgram(0); free(indices); free(coord); @@ -702,20 +871,20 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu free(coord_resized); gl_check_err(); - return ret; } // clang-format off const char *vertex_shader = GLSL(330, uniform mat4 projection; + uniform float scale = 1.0; uniform vec2 orig; uniform vec2 texorig; layout(location = 0) in vec2 coord; layout(location = 1) in vec2 in_texcoord; out vec2 texcoord; void main() { - gl_Position = projection * vec4(coord + orig, 0, 1); + gl_Position = projection * vec4(coord + orig, 0, scale); texcoord = in_texcoord + texorig; } ); @@ -918,17 +1087,28 @@ static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { } void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) { - struct gl_blur_context *bctx = ctx; + auto bctx = (struct gl_blur_context *)ctx; // Free GLSL shaders/programs for (int i = 0; i < bctx->npasses; ++i) { gl_free_blur_shader(&bctx->blur_shader[i]); } free(bctx->blur_shader); - glDeleteTextures(bctx->npasses > 1 ? 2 : 1, bctx->blur_texture); - if (bctx->npasses > 1) { - glDeleteFramebuffers(1, &bctx->blur_fbo); + if (bctx->blur_texture_count && bctx->blur_textures) { + glDeleteTextures(bctx->blur_texture_count, bctx->blur_textures); + free(bctx->blur_textures); + } + if (bctx->blur_texture_count && bctx->texture_sizes) { + free(bctx->texture_sizes); } + if (bctx->blur_fbo_count && bctx->blur_fbos) { + glDeleteFramebuffers(bctx->blur_fbo_count, bctx->blur_fbos); + free(bctx->blur_fbos); + } + + bctx->blur_texture_count = 0; + bctx->blur_fbo_count = 0; + free(bctx); gl_check_err(); @@ -937,17 +1117,12 @@ void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) { /** * Initialize GL blur filters. */ -void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) { - bool success = true; - auto gd = (struct gl_data *)base; +bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection, + enum blur_method method, void *args) { + bool success = false; + auto ctx = (struct gl_blur_context *)blur_context; struct conv **kernels; - auto ctx = ccalloc(1, struct gl_blur_context); - - if (!method || method >= BLUR_METHOD_INVALID) { - ctx->method = BLUR_METHOD_NONE; - return ctx; - } int nkernels; ctx->method = BLUR_METHOD_KERNEL; @@ -960,18 +1135,12 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg if (!nkernels) { ctx->method = BLUR_METHOD_NONE; - return ctx; + return true; } - // Set projection matrix to gl viewport dimensions so we can use screen - // coordinates for all vertices - // Note: OpenGL matrices are column major - GLint viewport_dimensions[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); - GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0}, - {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0}, - {0, 0, 0, 0}, - {-1, -1, 0, 1}}; + // Specify required textures and FBOs + ctx->blur_texture_count = 2; + ctx->blur_fbo_count = 1; ctx->blur_shader = ccalloc(max2(2, nkernels), gl_blur_shader_t); @@ -1102,7 +1271,7 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg // Setup projection matrix glUseProgram(pass->prog); int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + glUniformMatrix4fv(pml, 1, false, projection); glUseProgram(0); ctx->resize_width += kern->w / 2; @@ -1122,7 +1291,7 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg // Setup projection matrix glUseProgram(pass->prog); int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + glUniformMatrix4fv(pml, 1, false, projection); glUseProgram(0); ctx->npasses = 2; @@ -1130,26 +1299,7 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg ctx->npasses = nkernels; } - // Texture size will be defined by gl_blur - glGenTextures(2, ctx->blur_texture); - glBindTexture(GL_TEXTURE_2D, ctx->blur_texture[0]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindTexture(GL_TEXTURE_2D, ctx->blur_texture[1]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - // Generate FBO and textures when needed - glGenFramebuffers(1, &ctx->blur_fbo); - if (!ctx->blur_fbo) { - log_error("Failed to generate framebuffer object for blur"); - success = false; - goto out; - } - + success = true; out: if (method != BLUR_METHOD_KERNEL) { // We generated the blur kernels, so we need to free them @@ -1159,22 +1309,244 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg free(kernels); } + free(extension); + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + + return success; +} + +bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection, + enum blur_method method, void *args) { + bool success = false; + auto ctx = (struct gl_blur_context *)blur_context; + + ctx->method = method; + + auto blur_params = generate_dual_kawase_params(args); + + // Specify required textures and FBOs + ctx->blur_texture_count = blur_params->iterations; + ctx->blur_fbo_count = blur_params->iterations; + + ctx->resize_width += blur_params->expand; + ctx->resize_height += blur_params->expand; + + ctx->npasses = 2; + ctx->blur_shader = ccalloc(ctx->npasses, gl_blur_shader_t); + + char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); + // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane + // Thanks to hiciu for reporting. + setlocale(LC_NUMERIC, "C"); + + // Dual-kawase downsample shader / program + auto down_pass = ctx->blur_shader; + { + // clang-format off + static const char *FRAG_SHADER_DOWN = GLSL(330, + uniform sampler2D tex_src; + uniform float scale = 1.0; + uniform vec2 pixel_norm; + in vec2 texcoord; + out vec4 out_color; + void main() { + vec2 offset = %.7g * pixel_norm; + vec2 uv = texcoord * pixel_norm * (2.0 / scale); + vec4 sum = texture2D(tex_src, uv) * 4.0; + sum += texture2D(tex_src, uv - vec2(0.5, 0.5) * offset); + sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset); + sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset); + sum += texture2D(tex_src, uv - vec2(0.5, -0.5) * offset); + out_color = sum / 8.0; + } + ); + // clang-format on + + // Build shader + size_t shader_len = + strlen(FRAG_SHADER_DOWN) + 10 /* offset */ + 1 /* null terminator */; + char *shader_str = ccalloc(shader_len, char); + auto real_shader_len = + snprintf(shader_str, shader_len, FRAG_SHADER_DOWN, blur_params->offset); + CHECK(real_shader_len >= 0); + CHECK((size_t)real_shader_len < shader_len); + + // Build program + down_pass->prog = gl_create_program_from_str(vertex_shader, shader_str); + free(shader_str); + if (!down_pass->prog) { + log_error("Failed to create GLSL program."); + success = false; + goto out; + } + glBindFragDataLocation(down_pass->prog, 0, "out_color"); + + // Get uniform addresses + down_pass->unifm_pixel_norm = + glGetUniformLocationChecked(down_pass->prog, "pixel_norm"); + down_pass->orig_loc = + glGetUniformLocationChecked(down_pass->prog, "orig"); + down_pass->texorig_loc = + glGetUniformLocationChecked(down_pass->prog, "texorig"); + down_pass->scale_loc = + glGetUniformLocationChecked(down_pass->prog, "scale"); + + // Setup projection matrix + glUseProgram(down_pass->prog); + int pml = glGetUniformLocationChecked(down_pass->prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection); + glUseProgram(0); + } + + // Dual-kawase upsample shader / program + auto up_pass = ctx->blur_shader + 1; + { + // clang-format off + static const char *FRAG_SHADER_UP = GLSL(330, + uniform sampler2D tex_src; + uniform float scale = 1.0; + uniform vec2 pixel_norm; + uniform float opacity; + in vec2 texcoord; + out vec4 out_color; + void main() { + vec2 offset = %.7g * pixel_norm; + vec2 uv = texcoord * pixel_norm / (2 * scale); + vec4 sum = texture2D(tex_src, uv + vec2(-1.0, 0.0) * offset); + sum += texture2D(tex_src, uv + vec2(-0.5, 0.5) * offset) * 2.0; + sum += texture2D(tex_src, uv + vec2(0.0, 1.0) * offset); + sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset) * 2.0; + sum += texture2D(tex_src, uv + vec2(1.0, 0.0) * offset); + sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset) * 2.0; + sum += texture2D(tex_src, uv + vec2(0.0, -1.0) * offset); + sum += texture2D(tex_src, uv + vec2(-0.5, -0.5) * offset) * 2.0; + out_color = sum / 12.0 * opacity; + } + ); + // clang-format on + + // Build shader + size_t shader_len = + strlen(FRAG_SHADER_UP) + 10 /* offset */ + 1 /* null terminator */; + char *shader_str = ccalloc(shader_len, char); + auto real_shader_len = + snprintf(shader_str, shader_len, FRAG_SHADER_UP, blur_params->offset); + CHECK(real_shader_len >= 0); + CHECK((size_t)real_shader_len < shader_len); + + // Build program + up_pass->prog = gl_create_program_from_str(vertex_shader, shader_str); + free(shader_str); + if (!up_pass->prog) { + log_error("Failed to create GLSL program."); + success = false; + goto out; + } + glBindFragDataLocation(up_pass->prog, 0, "out_color"); + + // Get uniform addresses + up_pass->unifm_pixel_norm = + glGetUniformLocationChecked(up_pass->prog, "pixel_norm"); + up_pass->unifm_opacity = + glGetUniformLocationChecked(up_pass->prog, "opacity"); + up_pass->orig_loc = glGetUniformLocationChecked(up_pass->prog, "orig"); + up_pass->texorig_loc = + glGetUniformLocationChecked(up_pass->prog, "texorig"); + up_pass->scale_loc = glGetUniformLocationChecked(up_pass->prog, "scale"); + + // Setup projection matrix + glUseProgram(up_pass->prog); + int pml = glGetUniformLocationChecked(up_pass->prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection); + glUseProgram(0); + } + + success = true; +out: + free(blur_params); + if (!success) { - gl_destroy_blur_context(&gd->base, ctx); ctx = NULL; } - free(extension); // Restore LC_NUMERIC setlocale(LC_NUMERIC, lc_numeric_old); free(lc_numeric_old); + return success; +} + +void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) { + bool success; + auto gd = (struct gl_data *)base; + + auto ctx = ccalloc(1, struct gl_blur_context); + + if (!method || method >= BLUR_METHOD_INVALID) { + ctx->method = BLUR_METHOD_NONE; + return ctx; + } + + // Set projection matrix to gl viewport dimensions so we can use screen + // coordinates for all vertices + // Note: OpenGL matrices are column major + GLint viewport_dimensions[2]; + glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); + GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0}, + {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0}, + {0, 0, 0, 0}, + {-1, -1, 0, 1}}; + + if (method == BLUR_METHOD_DUAL_KAWASE) { + success = gl_create_dual_kawase_blur_context(ctx, projection_matrix[0], + method, args); + } else { + success = + gl_create_kernel_blur_context(ctx, projection_matrix[0], method, args); + } + if (!success || ctx->method == BLUR_METHOD_NONE) { + goto out; + } + + // Texture size will be defined by gl_blur + ctx->blur_textures = ccalloc(ctx->blur_texture_count, GLuint); + ctx->texture_sizes = ccalloc(ctx->blur_texture_count, struct texture_size); + glGenTextures(ctx->blur_texture_count, ctx->blur_textures); + + for (int i = 0; i < ctx->blur_texture_count; ++i) { + glBindTexture(GL_TEXTURE_2D, ctx->blur_textures[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + // Generate FBO and textures when needed + ctx->blur_fbos = ccalloc(ctx->blur_fbo_count, GLuint); + glGenFramebuffers(ctx->blur_fbo_count, ctx->blur_fbos); + + for (int i = 0; i < ctx->blur_fbo_count; ++i) { + if (!ctx->blur_fbos[i]) { + log_error("Failed to generate framebuffer objects for blur"); + success = false; + goto out; + } + } + +out: + if (!success) { + gl_destroy_blur_context(&gd->base, ctx); + ctx = NULL; + } + gl_check_err(); return ctx; } void gl_get_blur_size(void *blur_context, int *width, int *height) { - struct gl_blur_context *ctx = blur_context; + auto ctx = (struct gl_blur_context *)blur_context; *width = ctx->resize_width; *height = ctx->resize_height; } diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 125ce5a2c0..5ed956ae12 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -36,6 +36,7 @@ typedef struct { GLint unifm_opacity; GLint orig_loc; GLint texorig_loc; + GLint scale_loc; } gl_blur_shader_t; typedef struct { diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index fcc76b0ca9..f9f4775b36 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -507,6 +507,12 @@ void *create_blur_context(backend_t *base attr_unused, enum blur_method method, ret->method = BLUR_METHOD_NONE; return ret; } + if (method == BLUR_METHOD_DUAL_KAWASE) { + log_warn("Blur method 'dual_kawase' is not compatible with the 'xrender' " + "backend."); + ret->method = BLUR_METHOD_NONE; + return ret; + } ret->method = BLUR_METHOD_KERNEL; struct conv **kernels; diff --git a/src/config.c b/src/config.c index 5d93f32769..c8690b7c3b 100644 --- a/src/config.c +++ b/src/config.c @@ -88,6 +88,13 @@ enum blur_method parse_blur_method(const char *src) { return BLUR_METHOD_BOX; } else if (strcmp(src, "gaussian") == 0) { return BLUR_METHOD_GAUSSIAN; + } else if (strcmp(src, "dual_kawase") == 0) { + return BLUR_METHOD_DUAL_KAWASE; + } else if (strcmp(src, "kawase") == 0) { + log_warn("Blur method 'kawase' has been renamed to 'dual_kawase'. " + "Interpreted as 'dual_kawase', but this will stop working " + "soon."); + return BLUR_METHOD_DUAL_KAWASE; } else if (strcmp(src, "none") == 0) { return BLUR_METHOD_NONE; } @@ -542,6 +549,7 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .blur_method = BLUR_METHOD_NONE, .blur_radius = 3, .blur_deviation = 0.84089642, + .blur_strength = 5, .blur_background_frame = false, .blur_background_fixed = false, .blur_background_blacklist = NULL, diff --git a/src/config.h b/src/config.h index cd32add2ff..267239a568 100644 --- a/src/config.h +++ b/src/config.h @@ -23,8 +23,8 @@ #include "kernel.h" #include "log.h" #include "region.h" -#include "win_defs.h" #include "types.h" +#include "win_defs.h" typedef struct session session_t; @@ -60,6 +60,7 @@ enum blur_method { BLUR_METHOD_KERNEL, BLUR_METHOD_BOX, BLUR_METHOD_GAUSSIAN, + BLUR_METHOD_DUAL_KAWASE, BLUR_METHOD_INVALID, }; @@ -189,6 +190,8 @@ typedef struct options { int blur_radius; // Standard deviation for the gaussian blur double blur_deviation; + // Strength of the dual_kawase blur + int blur_strength; /// Whether to blur background when the window frame is not opaque. /// Implies blur_background. bool blur_background_frame; diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 16e965f763..9de0479078 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -530,6 +530,8 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad config_lookup_int(&cfg, "blur-size", &opt->blur_radius); // --blur-deviation config_lookup_float(&cfg, "blur-deviation", &opt->blur_deviation); + // --blur-strength + config_lookup_int(&cfg, "blur-strength", &opt->blur_strength); // --blur-background if (config_lookup_bool(&cfg, "blur-background", &ival) && ival) { if (opt->blur_method == BLUR_METHOD_NONE) { @@ -640,6 +642,7 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad } config_setting_lookup_float(blur_cfg, "deviation", &opt->blur_deviation); + config_setting_lookup_int(blur_cfg, "strength", &opt->blur_strength); } // Wintype settings diff --git a/src/options.c b/src/options.c index 44c30812a9..953d4644b3 100644 --- a/src/options.c +++ b/src/options.c @@ -212,6 +212,9 @@ static void usage(const char *argv0, int ret) { "--blur-deviation\n" " The standard deviation for the 'gaussian' blur method.\n" "\n" + "--blur-strength\n" + " The strength level of the 'dual_kawase' blur method.\n" + "\n" "--blur-background\n" " Blur background of semi-transparent / ARGB windows. Bad in\n" " performance. The switch name may change without prior\n" @@ -435,7 +438,8 @@ static const struct option longopts[] = { {"blur-method", required_argument, NULL, 328}, {"blur-size", required_argument, NULL, 329}, {"blur-deviation", required_argument, NULL, 330}, - {"shadow-color", required_argument, NULL, 331}, + {"blur-strength", required_argument, NULL, 331}, + {"shadow-color", required_argument, NULL, 332}, {"experimental-backends", no_argument, NULL, 733}, {"monitor-repaint", no_argument, NULL, 800}, {"diagnostics", no_argument, NULL, 801}, @@ -605,7 +609,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, case 256: // --config break; - case 331:; + case 332:; // --shadow-color struct color rgb; rgb = hex_to_rgb(optarg); @@ -844,6 +848,10 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // --blur-deviation opt->blur_deviation = atof(optarg); break; + case 331: + // --blur-strength + opt->blur_strength = atoi(optarg); + break; P_CASEBOOL(733, experimental_backends); P_CASEBOOL(800, monitor_repaint); @@ -933,6 +941,20 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, CHECK(opt->blur_kernel_count); } + // Sanitize parameters for dual-filter kawase blur + if (opt->blur_method == BLUR_METHOD_DUAL_KAWASE) { + if (opt->blur_strength <= 0 && opt->blur_radius > 500) { + log_warn("Blur radius >500 not supported by dual_kawase method, " + "capping to 500."); + opt->blur_radius = 500; + } + if (opt->blur_strength > 20) { + log_warn("Blur strength >20 not supported by dual_kawase method, " + "capping to 20."); + opt->blur_strength = 20; + } + } + if (opt->resize_damage < 0) { log_warn("Negative --resize-damage will not work correctly."); } diff --git a/src/picom.c b/src/picom.c index b4602e1f60..d2649021fe 100644 --- a/src/picom.c +++ b/src/picom.c @@ -431,6 +431,7 @@ static bool initialize_blur(session_t *ps) { struct kernel_blur_args kargs; struct gaussian_blur_args gargs; struct box_blur_args bargs; + struct dual_kawase_blur_args dkargs; void *args = NULL; switch (ps->o.blur_method) { @@ -448,6 +449,11 @@ static bool initialize_blur(session_t *ps) { gargs.deviation = ps->o.blur_deviation; args = (void *)&gargs; break; + case BLUR_METHOD_DUAL_KAWASE: + dkargs.size = ps->o.blur_radius; + dkargs.strength = ps->o.blur_strength; + args = (void *)&dkargs; + break; default: return true; }