From 4b0ff37b36c58455a47a9044bd2b7e7c2cc3bc73 Mon Sep 17 00:00:00 2001 From: Bernd Busse Date: Sat, 6 Jun 2020 21:27:25 +0200 Subject: [PATCH 1/3] backend: gl_common: Use texture2D with filtering and clamping in blur Create texture with GL_LINEAR filtering and GL_CLAMP_TO_EDGE wrapping. Change `texelFetch()`-call in fragement shader to `texture2D()` to be taken into account. This requires supplying the size of a pixel in normalized texture coordinates via an additional uniform. Fixes darkening at the screen edges with larger blur radii caused by sampling coordinates being out of texture bounds. This is undefined behaviour unless the context has set the flag *GL_ARB_robust_buffer_access_behaviour*, in which case "zero"-pixels are returned (i.e. black). Current behaviour seems to depend on the driver. --- src/backend/gl/gl_common.c | 35 ++++++++++++++++++++++++++--------- src/backend/gl/gl_common.h | 4 +++- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 61dc7ca..4aaaabe 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -629,20 +629,27 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu // 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]); @@ -976,19 +983,20 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg // clang-format off static const char *FRAG_SHADER_BLUR = GLSL(330, %s\n // other extension pragmas - uniform sampler2D tex_scr; + uniform sampler2D tex_src; + uniform vec2 pixel_norm; uniform float opacity; in vec2 texcoord; out vec4 out_color; void main() { + vec2 uv = texcoord * pixel_norm; vec4 sum = vec4(0.0, 0.0, 0.0, 0.0); %s //body of the convolution out_color = sum / float(%.7g) * opacity; } ); static const char *FRAG_SHADER_BLUR_ADD = QUOTE( - sum += float(%.7g) * - texelFetch(tex_scr, ivec2(texcoord + vec2(%d, %d)), 0); + sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%d, %d)); ); // clang-format on @@ -1042,6 +1050,8 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg glBindFragDataLocation(pass->prog, 0, "out_color"); // Get uniform addresses + pass->unifm_pixel_norm = + glGetUniformLocationChecked(pass->prog, "pixel_norm"); pass->unifm_opacity = glGetUniformLocationChecked(pass->prog, "opacity"); pass->orig_loc = glGetUniformLocationChecked(pass->prog, "orig"); pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); @@ -1061,6 +1071,7 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg // the single pass case auto pass = &ctx->blur_shader[1]; pass->prog = gl_create_program_from_str(vertex_shader, dummy_frag); + pass->unifm_pixel_norm = -1; pass->unifm_opacity = -1; pass->orig_loc = glGetUniformLocationChecked(pass->prog, "orig"); pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); @@ -1079,11 +1090,15 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg // 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_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + 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_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + 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) { @@ -1196,8 +1211,10 @@ bool gl_init(struct gl_data *gd, session_t *ps) { } glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + 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, 0); // Set projection matrix to gl viewport dimensions so we can use screen diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index c27c530..125ce5a 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -32,6 +32,7 @@ typedef struct { // Program and uniforms for blur shader typedef struct { GLuint prog; + GLint unifm_pixel_norm; GLint unifm_opacity; GLint orig_loc; GLint texorig_loc; @@ -168,7 +169,8 @@ static inline void gl_check_err_(const char *func, int line) { } static inline void gl_clear_err(void) { - while (glGetError() != GL_NO_ERROR); + while (glGetError() != GL_NO_ERROR) + ; } #define gl_check_err() gl_check_err_(__func__, __LINE__) From 88b16384870d6d31df693d9bf6acea8efc074836 Mon Sep 17 00:00:00 2001 From: Bernd Busse Date: Sun, 7 Jun 2020 12:41:32 +0200 Subject: [PATCH 2/3] backend: gl_common: Use linear interpolation on GPU for blur kernels. Make use of hardware linear interpolation in a GPU to sample 2 pixels with a single texture access inside the blur shaders by sampling between both pixels based on their relative weight. This is significantly easier for a single dimension as 2D bilinear filtering would raise additional constraints on the kernels (not single zero-entries, no zero-diagonals, ...) which require additional checks with limited improvements. Therfore, only use interpolation along the larger dimension should be a sufficient improvement. Using this will effectively half the number of texture accesses and additions needed for a kernel. E.g. a 1D-pass of the gaussian blur with radius 15 will only need 16 samples instead of 31. --- src/backend/gl/gl_common.c | 67 +++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 4aaaabe..48cd410 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -996,7 +996,7 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg } ); static const char *FRAG_SHADER_BLUR_ADD = QUOTE( - sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%d, %d)); + sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%.7g, %.7g)); ); // clang-format on @@ -1008,23 +1008,66 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg // Build shader int width = kern->w, height = kern->h; int nele = width * height; + // '%.7g' is at most 14 characters, inserted 3 times size_t body_len = (strlen(shader_add) + 42) * (uint)nele; char *shader_body = ccalloc(body_len, char); char *pc = shader_body; + // Make use of the linear interpolation hardware by sampling 2 pixels with + // one texture access by sampling between both pixels based on their + // relative weight. Easiest done in a single dimension as 2D bilinear + // filtering would raise additional constraints on the kernels. Therefore + // only use interpolation along the larger dimension. double sum = 0.0; - for (int j = 0; j < height; ++j) { - for (int k = 0; k < width; ++k) { - double val; - val = kern->data[j * width + k]; - if (val == 0) { - continue; + if (width > height) { + // use interpolation in x dimension (width) + for (int j = 0; j < height; ++j) { + for (int k = 0; k < width; k += 2) { + double val1, val2; + val1 = kern->data[j * width + k]; + val2 = (k + 1 < width) + ? kern->data[j * width + k + 1] + : 0; + + double combined_weight = val1 + val2; + if (combined_weight == 0) { + continue; + } + sum += combined_weight; + + double offset_x = + k + (val2 / combined_weight) - (width / 2); + double offset_y = j - (height / 2); + pc += snprintf( + pc, body_len - (ulong)(pc - shader_body), + shader_add, combined_weight, offset_x, offset_y); + assert(pc < shader_body + body_len); + } + } + } else { + // use interpolation in y dimension (height) + for (int j = 0; j < height; j += 2) { + for (int k = 0; k < width; ++k) { + double val1, val2; + val1 = kern->data[j * width + k]; + val2 = (j + 1 < height) + ? kern->data[(j + 1) * width + k] + : 0; + + double combined_weight = val1 + val2; + if (combined_weight == 0) { + continue; + } + sum += combined_weight; + + double offset_x = k - (width / 2); + double offset_y = + j + (val2 / combined_weight) - (height / 2); + pc += snprintf( + pc, body_len - (ulong)(pc - shader_body), + shader_add, combined_weight, offset_x, offset_y); + assert(pc < shader_body + body_len); } - sum += val; - pc += snprintf(pc, body_len - (ulong)(pc - shader_body), - FRAG_SHADER_BLUR_ADD, val, k - width / 2, - j - height / 2); - assert(pc < shader_body + body_len); } } From 7cf13906aedfdf28e6bcd96b8a21142df38c0806 Mon Sep 17 00:00:00 2001 From: Bernd Busse Date: Sun, 7 Jun 2020 15:35:45 +0200 Subject: [PATCH 3/3] backend: xrender: use correct REPEAT mode when creating blur textures Create pictures used for bluring with REPEAT attribute set to PAD (same logic as GL_CLAMP_TO_EDGE in OpenGL). Fixes darkening at the screen edges with larger blur radii caused by sampling out of texture bounds. Related: 4b0ff37b36c58455a47a9044bd2b7e7c2cc3bc73 --- src/backend/xrender/xrender.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index a44dbc1..0b966e5 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -14,15 +14,15 @@ #include "backend/backend.h" #include "backend/backend_common.h" #include "common.h" -#include "picom.h" #include "config.h" #include "kernel.h" #include "log.h" +#include "picom.h" #include "region.h" +#include "types.h" #include "utils.h" #include "win.h" #include "x.h" -#include "types.h" typedef struct _xrender_data { backend_t base; @@ -157,11 +157,13 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, // Create a buffer for storing blurred picture, make it just big enough // for the blur region + const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT; + const xcb_render_create_picture_value_list_t pic_attrs = {.repeat = XCB_RENDER_REPEAT_PAD}; xcb_render_picture_t tmp_picture[2] = { - x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, - height_resized, xd->default_visual, 0, NULL), - x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, - height_resized, xd->default_visual, 0, NULL)}; + x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized, + xd->default_visual, pic_attrs_mask, &pic_attrs), + x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized, + xd->default_visual, pic_attrs_mask, &pic_attrs)}; if (!tmp_picture[0] || !tmp_picture[1]) { log_error("Failed to build intermediate Picture."); @@ -611,8 +613,11 @@ backend_t *backend_xrender_init(session_t *ps) { xd->back_pixmap[i] = x_create_pixmap(ps->c, pictfmt->depth, ps->root, to_u16_checked(ps->root_width), to_u16_checked(ps->root_height)); + const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT; + const xcb_render_create_picture_value_list_t pic_attrs = { + .repeat = XCB_RENDER_REPEAT_PAD}; xd->back[i] = x_create_picture_with_pictfmt_and_pixmap( - ps->c, pictfmt, xd->back_pixmap[i], 0, NULL); + ps->c, pictfmt, xd->back_pixmap[i], pic_attrs_mask, &pic_attrs); xd->buffer_age[i] = -1; if (xd->back_pixmap[i] == XCB_NONE || xd->back[i] == XCB_NONE) { log_error("Cannot create pixmap for rendering");