Merge pull request #382 from tryone144/feature/dual_kawase

dual-kawase blur algorithm for new OpenGL backend
This commit is contained in:
yshui
2020-08-31 14:54:38 +01:00
committed by GitHub
13 changed files with 656 additions and 152 deletions

View File

@@ -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.

View File

@@ -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.

View File

@@ -5,8 +5,8 @@
#include <stdbool.h>
#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 ===========

View File

@@ -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;

View File

@@ -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);

View File

@@ -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.
/// Turns out calling glTexImage to resize is expensive, so we avoid that.
GLuint blur_texture[2];
/// Temporary fbo used for blurring
GLuint blur_fbo;
/// Temporary textures used for blurring
GLuint *blur_textures;
int blur_texture_count;
/// Temporary fbos used for blurring
GLuint *blur_fbos;
int blur_fbo_count;
int texture_width, texture_height;
/// 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.
struct texture_size {
int width;
int height;
} * texture_sizes;
/// 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_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;
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;
if (gd->width + bctx->resize_width * 2 != bctx->texture_width ||
gd->height + bctx->resize_height * 2 != bctx->texture_height) {
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) {
auto bctx = (struct gl_blur_context *)ctx;
auto gd = (struct gl_data *)base;
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;
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);
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_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(&reg_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(&reg_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 @@ end:
glDeleteBuffers(4, bo);
glBindVertexArray(0);
glDeleteVertexArrays(2, vao);
glUseProgram(0);
free(indices);
free(coord);
@@ -702,20 +871,20 @@ end:
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 @@ out:
free(kernels);
}
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;
}
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) {
ctx = NULL;
}
// 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;
}

View File

@@ -36,6 +36,7 @@ typedef struct {
GLint unifm_opacity;
GLint orig_loc;
GLint texorig_loc;
GLint scale_loc;
} gl_blur_shader_t;
typedef struct {

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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

View File

@@ -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.");
}

View File

@@ -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;
}