From e2d990dc3df2e67013270b81feb45bcf53d968f8 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 25 Aug 2022 13:31:54 +0100 Subject: [PATCH 01/11] backend: gl: split code into multiple files gl_common.c is getting too big. Signed-off-by: Yuxuan Shui --- src/backend/gl/blur.c | 892 +++++++++++++++++++++++++++++ src/backend/gl/gl_common.c | 1080 +----------------------------------- src/backend/gl/gl_common.h | 14 + src/backend/gl/shaders.c | 177 ++++++ src/backend/meson.build | 2 +- 5 files changed, 1086 insertions(+), 1079 deletions(-) create mode 100644 src/backend/gl/blur.c create mode 100644 src/backend/gl/shaders.c diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c new file mode 100644 index 00000000..0407501c --- /dev/null +++ b/src/backend/gl/blur.c @@ -0,0 +1,892 @@ +#include +#include + +#include +#include + +#include "gl_common.h" + +struct gl_blur_context { + enum blur_method method; + gl_blur_shader_t *blur_shader; + + /// 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. + 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; + + int npasses; +}; + +/** + * Blur contents in a particular region. + */ +bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, + struct backend_image *mask, coord_t mask_dst, const GLuint vao[2], + const int vao_nelems[2]) { + auto bctx = (struct gl_blur_context *)ctx; + auto gd = (struct gl_data *)base; + + int 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 = extent->x1, texorig_y = dst_y_fb_coord; + GLint tex_width, tex_height; + GLuint src_texture; + + if (i == 0) { + src_texture = gd->back_texture; + tex_width = gd->width; + tex_height = gd->height; + } else { + src_texture = bctx->blur_textures[curr]; + auto src_size = bctx->texture_sizes[curr]; + tex_width = src_size.width; + tex_height = src_size.height; + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, src_texture); + glUseProgram(p->prog); + glUniform2f(p->uniform_pixel_norm, 1.0F / (GLfloat)tex_width, + 1.0F / (GLfloat)tex_height); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, gd->default_mask_texture); + + glUniform1i(p->uniform_mask_tex, 1); + glUniform2f(p->uniform_mask_offset, 0.0F, 0.0F); + glUniform1i(p->uniform_mask_inverted, 0); + glUniform1f(p->uniform_mask_corner_radius, 0.0F); + + // The number of indices in the selected vertex array + GLsizei nelems; + + 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]); + nelems = vao_nelems[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 (!gl_check_fb_complete(GL_FRAMEBUFFER)) { + return false; + } + + glUniform1f(p->uniform_opacity, 1.0F); + } else { + // last pass, draw directly into the back buffer, with origin + // regions. And apply mask if requested + if (mask) { + auto inner = (struct gl_texture *)mask->inner; + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, inner->texture); + glUniform1i(p->uniform_mask_inverted, mask->color_inverted); + glUniform1f(p->uniform_mask_corner_radius, + (float)mask->corner_radius); + glUniform2f( + p->uniform_mask_offset, (float)(mask_dst.x), + (float)(bctx->fb_height - mask_dst.y - inner->height)); + } + glBindVertexArray(vao[0]); + nelems = vao_nelems[0]; + glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); + + glUniform1f(p->uniform_opacity, (float)opacity); + } + + glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); + glDrawElements(GL_TRIANGLES, nelems, 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, + struct backend_image *mask, coord_t mask_dst, + const GLuint vao[2], const int vao_nelems[2]) { + auto bctx = (struct gl_blur_context *)ctx; + auto gd = (struct gl_data *)base; + + int 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); + + glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); + + 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; + + if (i == 0) { + // first pass: copy from back buffer + src_texture = gd->back_texture; + tex_width = gd->width; + tex_height = gd->height; + } 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; + } + + assert(src_texture); + assert(bctx->blur_fbos[i]); + + glBindTexture(GL_TEXTURE_2D, src_texture); + glBindVertexArray(vao[1]); + auto nelems = vao_nelems[1]; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + + glUniform1f(down_pass->scale_loc, (GLfloat)scale_factor); + + glUniform2f(down_pass->uniform_pixel_norm, 1.0F / (GLfloat)tex_width, + 1.0F / (GLfloat)tex_height); + + glDrawElements(GL_TRIANGLES, nelems, 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); + + glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); + + 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; + + // The number of indices in the selected vertex array + GLsizei nelems; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, src_texture); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, gd->default_mask_texture); + + glUniform1i(up_pass->uniform_mask_tex, 1); + glUniform2f(up_pass->uniform_mask_offset, 0.0F, 0.0F); + glUniform1i(up_pass->uniform_mask_inverted, 0); + glUniform1f(up_pass->uniform_mask_corner_radius, 0.0F); + if (i > 0) { + assert(bctx->blur_fbos[i - 1]); + + // not last pass, draw into next framebuffer + glBindVertexArray(vao[1]); + nelems = vao_nelems[1]; + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + + glUniform1f(up_pass->uniform_opacity, (GLfloat)1); + } else { + // last pass, draw directly into the back buffer + if (mask) { + auto inner = (struct gl_texture *)mask->inner; + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, inner->texture); + glUniform1i(up_pass->uniform_mask_inverted, + mask->color_inverted); + glUniform1f(up_pass->uniform_mask_corner_radius, + (float)mask->corner_radius); + glUniform2f( + up_pass->uniform_mask_offset, (float)(mask_dst.x), + (float)(bctx->fb_height - mask_dst.y - inner->height)); + } + glBindVertexArray(vao[0]); + nelems = vao_nelems[0]; + glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); + + glUniform1f(up_pass->uniform_opacity, (GLfloat)opacity); + } + + glUniform1f(up_pass->scale_loc, (GLfloat)scale_factor); + glUniform2f(up_pass->uniform_pixel_norm, 1.0F / (GLfloat)tex_width, + 1.0F / (GLfloat)tex_height); + + glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); + } + + return true; +} + +bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst, + 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->fb_width || gd->height != bctx->fb_height) { + // Resize the temporary textures used for blur in case the root + // size changed + bctx->fb_width = gd->width; + bctx->fb_height = gd->height; + + 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 (!gl_check_fb_complete(GL_FRAMEBUFFER)) { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return false; + } + } + } + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + } + + // Remainder: regions are in Xorg coordinates + auto reg_blur_resized = + resize_region(reg_blur, bctx->resize_width, bctx->resize_height); + 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; + if (width == 0 || height == 0) { + return true; + } + + int nrects, nrects_resized; + const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects), + *rects_resized = + pixman_region32_rectangles(®_blur_resized, &nrects_resized); + if (!nrects || !nrects_resized) { + return true; + } + + auto coord = ccalloc(nrects * 16, GLint); + auto indices = ccalloc(nrects * 6, GLuint); + x_rect_to_coords(nrects, rects, + (coord_t){.x = extent_resized->x1, .y = extent_resized->y2}, + 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, + (coord_t){.x = extent_resized->x1, .y = extent_resized->y2}, + bctx->fb_height, bctx->fb_height, false, coord_resized, + indices_resized); + pixman_region32_fini(®_blur_resized); + + GLuint vao[2]; + glGenVertexArrays(2, vao); + GLuint bo[4]; + glGenBuffers(4, bo); + + glBindVertexArray(vao[0]); + glBindBuffer(GL_ARRAY_BUFFER, bo[0]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, + indices, GL_STATIC_DRAW); + glEnableVertexAttribArray(vert_coord_loc); + glEnableVertexAttribArray(vert_in_texcoord_loc); + glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); + glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, + sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); + + glBindVertexArray(vao[1]); + glBindBuffer(GL_ARRAY_BUFFER, bo[2]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16, + coord_resized, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + (long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized, + GL_STATIC_DRAW); + glEnableVertexAttribArray(vert_coord_loc); + glEnableVertexAttribArray(vert_in_texcoord_loc); + glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); + glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, + sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); + + int vao_nelems[2] = {nrects * 6, nrects_resized * 6}; + + if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { + ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, mask, + mask_dst, vao, vao_nelems); + } else { + ret = gl_kernel_blur(base, opacity, ctx, extent_resized, mask, mask_dst, + vao, vao_nelems); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDeleteBuffers(4, bo); + glBindVertexArray(0); + glDeleteVertexArrays(2, vao); + glUseProgram(0); + + free(indices); + free(coord); + free(indices_resized); + free(coord_resized); + + gl_check_err(); + return ret; +} + +static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { + if (shader->prog) { + glDeleteProgram(shader->prog); + } + + shader->prog = 0; +} + +void gl_destroy_blur_context(backend_t *base attr_unused, void *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); + + 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(); +} + +/** + * Initialize GL blur filters. + */ +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; + + int nkernels; + ctx->method = BLUR_METHOD_KERNEL; + if (method == BLUR_METHOD_KERNEL) { + nkernels = ((struct kernel_blur_args *)args)->kernel_count; + kernels = ((struct kernel_blur_args *)args)->kernels; + } else { + kernels = generate_blur_kernel(method, args, &nkernels); + } + + if (!nkernels) { + ctx->method = BLUR_METHOD_NONE; + return true; + } + + // 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); + + 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"); + + // clang-format off + static const char *FRAG_SHADER_BLUR = GLSL(330, + %s\n // other extension pragmas + uniform sampler2D tex_src; + uniform vec2 pixel_norm; + uniform float opacity; + in vec2 texcoord; + out vec4 out_color; + float mask_factor(); + 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 * mask_factor(); + } + ); + static const char *FRAG_SHADER_BLUR_ADD = QUOTE( + sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%.7g, %.7g)); + ); + // clang-format on + + const char *shader_add = FRAG_SHADER_BLUR_ADD; + char *extension = strdup(""); + + for (int i = 0; i < nkernels; i++) { + auto kern = kernels[i]; + // 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; + 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); + } + } + } + + auto pass = ctx->blur_shader + i; + size_t shader_len = strlen(FRAG_SHADER_BLUR) + strlen(extension) + + strlen(shader_body) + 10 /* sum */ + + 1 /* null terminator */; + char *shader_str = ccalloc(shader_len, char); + auto real_shader_len = snprintf(shader_str, shader_len, FRAG_SHADER_BLUR, + extension, shader_body, sum); + CHECK(real_shader_len >= 0); + CHECK((size_t)real_shader_len < shader_len); + free(shader_body); + + // Build program + pass->prog = gl_create_program_from_strv( + (const char *[]){vertex_shader, NULL}, + (const char *[]){shader_str, masking_glsl, NULL}); + free(shader_str); + if (!pass->prog) { + log_error("Failed to create GLSL program."); + success = false; + goto out; + } + glBindFragDataLocation(pass->prog, 0, "out_color"); + + // Get uniform addresses + bind_uniform(pass, pixel_norm); + bind_uniform(pass, opacity); + + bind_uniform(pass, mask_tex); + bind_uniform(pass, mask_offset); + bind_uniform(pass, mask_inverted); + bind_uniform(pass, mask_corner_radius); + log_info("Uniform locations: %d %d %d %d %d", pass->uniform_mask_tex, + pass->uniform_mask_offset, pass->uniform_mask_inverted, + pass->uniform_mask_corner_radius, pass->uniform_opacity); + pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); + + // Setup projection matrix + glUseProgram(pass->prog); + int pml = glGetUniformLocationChecked(pass->prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection); + glUseProgram(0); + + ctx->resize_width += kern->w / 2; + ctx->resize_height += kern->h / 2; + } + + if (nkernels == 1) { + // Generate an extra null pass so we don't need special code path for + // the single pass case + auto pass = &ctx->blur_shader[1]; + pass->prog = gl_create_program_from_strv( + (const char *[]){vertex_shader, NULL}, + (const char *[]){copy_with_mask_frag, masking_glsl, NULL}); + pass->uniform_pixel_norm = -1; + pass->uniform_opacity = -1; + pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); + bind_uniform(pass, mask_tex); + bind_uniform(pass, mask_offset); + bind_uniform(pass, mask_inverted); + bind_uniform(pass, mask_corner_radius); + + // Setup projection matrix + glUseProgram(pass->prog); + int pml = glGetUniformLocationChecked(pass->prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection); + glUseProgram(0); + + ctx->npasses = 2; + } else { + ctx->npasses = nkernels; + } + + success = true; +out: + if (method != BLUR_METHOD_KERNEL) { + // We generated the blur kernels, so we need to free them + for (int i = 0; i < nkernels; i++) { + free(kernels[i]); + } + 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 + bind_uniform(down_pass, pixel_norm); + 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; + float mask_factor(); + 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 * mask_factor(); + } + ); + // 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_strv( + (const char *[]){vertex_shader, NULL}, + (const char *[]){shader_str, masking_glsl, NULL}); + 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 + bind_uniform(up_pass, pixel_norm); + bind_uniform(up_pass, opacity); + + bind_uniform(up_pass, mask_tex); + bind_uniform(up_pass, mask_offset); + bind_uniform(up_pass, mask_inverted); + bind_uniform(up_pass, mask_corner_radius); + + 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) { + auto ctx = (struct gl_blur_context *)blur_context; + *width = ctx->resize_width; + *height = ctx->resize_height; +} diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 07c75be5..d7840d86 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -2,7 +2,6 @@ // Copyright (c) Yuxuan Shui #include #include -#include #include #include #include @@ -23,41 +22,6 @@ #include "backend/backend_common.h" #include "backend/gl/gl_common.h" -#define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__ -#define QUOTE(...) #__VA_ARGS__ - -static const GLuint vert_coord_loc = 0; -static const GLuint vert_in_texcoord_loc = 1; - -struct gl_blur_context { - enum blur_method method; - gl_blur_shader_t *blur_shader; - - /// 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. - 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; - - int npasses; -}; - GLuint gl_create_shader(GLenum shader_type, const char *shader_str) { log_trace("===\n%s\n===", shader_str); @@ -522,9 +486,8 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe /// @param[in] root_height height of the back buffer /// @param[in] y_inverted whether the texture is y inverted /// @param[out] coord, indices output -static void -x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, int texture_height, - int root_height, bool y_inverted, GLint *coord, GLuint *indices) { +void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, int texture_height, + int root_height, bool y_inverted, GLint *coord, GLuint *indices) { image_dst.y = root_height - image_dst.y; if (y_inverted) { image_dst.y -= texture_height; @@ -613,387 +576,6 @@ void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask free(coord); } -/** - * Blur contents in a particular region. - */ -bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, - struct backend_image *mask, coord_t mask_dst, const GLuint vao[2], - const int vao_nelems[2]) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - - int 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 = extent->x1, texorig_y = dst_y_fb_coord; - GLint tex_width, tex_height; - GLuint src_texture; - - if (i == 0) { - src_texture = gd->back_texture; - tex_width = gd->width; - tex_height = gd->height; - } else { - src_texture = bctx->blur_textures[curr]; - auto src_size = bctx->texture_sizes[curr]; - tex_width = src_size.width; - tex_height = src_size.height; - } - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, src_texture); - glUseProgram(p->prog); - glUniform2f(p->uniform_pixel_norm, 1.0F / (GLfloat)tex_width, - 1.0F / (GLfloat)tex_height); - - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, gd->default_mask_texture); - - glUniform1i(p->uniform_mask_tex, 1); - glUniform2f(p->uniform_mask_offset, 0.0F, 0.0F); - glUniform1i(p->uniform_mask_inverted, 0); - glUniform1f(p->uniform_mask_corner_radius, 0.0F); - - // The number of indices in the selected vertex array - GLsizei nelems; - - 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]); - nelems = vao_nelems[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 (!gl_check_fb_complete(GL_FRAMEBUFFER)) { - return false; - } - - glUniform1f(p->uniform_opacity, 1.0F); - } else { - // last pass, draw directly into the back buffer, with origin - // regions. And apply mask if requested - if (mask) { - auto inner = (struct gl_texture *)mask->inner; - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, inner->texture); - glUniform1i(p->uniform_mask_inverted, mask->color_inverted); - glUniform1f(p->uniform_mask_corner_radius, - (float)mask->corner_radius); - glUniform2f( - p->uniform_mask_offset, (float)(mask_dst.x), - (float)(bctx->fb_height - mask_dst.y - inner->height)); - } - glBindVertexArray(vao[0]); - nelems = vao_nelems[0]; - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); - - glUniform1f(p->uniform_opacity, (float)opacity); - } - - glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); - glDrawElements(GL_TRIANGLES, nelems, 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, - struct backend_image *mask, coord_t mask_dst, - const GLuint vao[2], const int vao_nelems[2]) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - - int 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); - - glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); - - 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; - - if (i == 0) { - // first pass: copy from back buffer - src_texture = gd->back_texture; - tex_width = gd->width; - tex_height = gd->height; - } 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; - } - - assert(src_texture); - assert(bctx->blur_fbos[i]); - - glBindTexture(GL_TEXTURE_2D, src_texture); - glBindVertexArray(vao[1]); - auto nelems = vao_nelems[1]; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - glUniform1f(down_pass->scale_loc, (GLfloat)scale_factor); - - glUniform2f(down_pass->uniform_pixel_norm, 1.0F / (GLfloat)tex_width, - 1.0F / (GLfloat)tex_height); - - glDrawElements(GL_TRIANGLES, nelems, 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); - - glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); - - 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; - - // The number of indices in the selected vertex array - GLsizei nelems; - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, src_texture); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, gd->default_mask_texture); - - glUniform1i(up_pass->uniform_mask_tex, 1); - glUniform2f(up_pass->uniform_mask_offset, 0.0F, 0.0F); - glUniform1i(up_pass->uniform_mask_inverted, 0); - glUniform1f(up_pass->uniform_mask_corner_radius, 0.0F); - if (i > 0) { - assert(bctx->blur_fbos[i - 1]); - - // not last pass, draw into next framebuffer - glBindVertexArray(vao[1]); - nelems = vao_nelems[1]; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - glUniform1f(up_pass->uniform_opacity, (GLfloat)1); - } else { - // last pass, draw directly into the back buffer - if (mask) { - auto inner = (struct gl_texture *)mask->inner; - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, inner->texture); - glUniform1i(up_pass->uniform_mask_inverted, - mask->color_inverted); - glUniform1f(up_pass->uniform_mask_corner_radius, - (float)mask->corner_radius); - glUniform2f( - up_pass->uniform_mask_offset, (float)(mask_dst.x), - (float)(bctx->fb_height - mask_dst.y - inner->height)); - } - glBindVertexArray(vao[0]); - nelems = vao_nelems[0]; - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); - - glUniform1f(up_pass->uniform_opacity, (GLfloat)opacity); - } - - glUniform1f(up_pass->scale_loc, (GLfloat)scale_factor); - glUniform2f(up_pass->uniform_pixel_norm, 1.0F / (GLfloat)tex_width, - 1.0F / (GLfloat)tex_height); - - glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); - } - - return true; -} - -bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst, - 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->fb_width || gd->height != bctx->fb_height) { - // Resize the temporary textures used for blur in case the root - // size changed - bctx->fb_width = gd->width; - bctx->fb_height = gd->height; - - 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 (!gl_check_fb_complete(GL_FRAMEBUFFER)) { - glBindFramebuffer(GL_FRAMEBUFFER, 0); - return false; - } - } - } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - } - - // Remainder: regions are in Xorg coordinates - auto reg_blur_resized = - resize_region(reg_blur, bctx->resize_width, bctx->resize_height); - 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; - if (width == 0 || height == 0) { - return true; - } - - int nrects, nrects_resized; - const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects), - *rects_resized = - pixman_region32_rectangles(®_blur_resized, &nrects_resized); - if (!nrects || !nrects_resized) { - return true; - } - - auto coord = ccalloc(nrects * 16, GLint); - auto indices = ccalloc(nrects * 6, GLuint); - x_rect_to_coords(nrects, rects, - (coord_t){.x = extent_resized->x1, .y = extent_resized->y2}, - 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, - (coord_t){.x = extent_resized->x1, .y = extent_resized->y2}, - bctx->fb_height, bctx->fb_height, false, coord_resized, - indices_resized); - pixman_region32_fini(®_blur_resized); - - GLuint vao[2]; - glGenVertexArrays(2, vao); - GLuint bo[4]; - glGenBuffers(4, bo); - - glBindVertexArray(vao[0]); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, - indices, GL_STATIC_DRAW); - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - glBindVertexArray(vao[1]); - glBindBuffer(GL_ARRAY_BUFFER, bo[2]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16, - coord_resized, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, - (long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized, - GL_STATIC_DRAW); - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - int vao_nelems[2] = {nrects * 6, nrects_resized * 6}; - - if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { - ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, mask, - mask_dst, vao, vao_nelems); - } else { - ret = gl_kernel_blur(base, opacity, ctx, extent_resized, mask, mask_dst, - vao, vao_nelems); - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(4, bo); - glBindVertexArray(0); - glDeleteVertexArrays(2, vao); - glUseProgram(0); - - free(indices); - free(coord); - free(indices_resized); - 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 texorig; - layout(location = 0) in vec2 coord; - layout(location = 1) in vec2 in_texcoord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(coord, 0, scale); - texcoord = in_texcoord + texorig; - } -); -// clang-format on - /** * Load a GLSL main program from shader strings. */ @@ -1048,60 +630,6 @@ void gl_resize(struct gl_data *gd, int width, int height) { gl_check_err(); } -// clang-format off -static const char dummy_frag[] = GLSL(330, - uniform sampler2D tex; - in vec2 texcoord; - void main() { - gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0); - } -); - -static const char copy_with_mask_frag[] = GLSL(330, - uniform sampler2D tex; - in vec2 texcoord; - float mask_factor(); - void main() { - gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0) * mask_factor(); - } -); - -static const char fill_frag[] = GLSL(330, - uniform vec4 color; - void main() { - gl_FragColor = color; - } -); - -static const char fill_vert[] = GLSL(330, - layout(location = 0) in vec2 in_coord; - uniform mat4 projection; - void main() { - gl_Position = projection * vec4(in_coord, 0, 1); - } -); - -static const char interpolating_frag[] = GLSL(330, - uniform sampler2D tex; - in vec2 texcoord; - void main() { - gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1); - } -); - -static const char interpolating_vert[] = GLSL(330, - uniform mat4 projection; - uniform vec2 texsize; - layout(location = 0) in vec2 in_coord; - layout(location = 1) in vec2 in_texcoord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(in_coord, 0, 1); - texcoord = in_texcoord / texsize; - } -); -// clang-format on - /// Fill a given region in bound framebuffer. /// @param[in] y_inverted whether the y coordinates in `clip` should be inverted static void _gl_fill(backend_t *base, struct color c, const region_t *clip, GLuint target, @@ -1229,610 +757,6 @@ void gl_release_image(backend_t *base, void *image_data) { free(wd); } -static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { - if (shader->prog) { - glDeleteProgram(shader->prog); - } - - shader->prog = 0; -} - -void gl_destroy_blur_context(backend_t *base attr_unused, void *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); - - 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(); -} - -const char *masking_glsl; -/** - * Initialize GL blur filters. - */ -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; - - int nkernels; - ctx->method = BLUR_METHOD_KERNEL; - if (method == BLUR_METHOD_KERNEL) { - nkernels = ((struct kernel_blur_args *)args)->kernel_count; - kernels = ((struct kernel_blur_args *)args)->kernels; - } else { - kernels = generate_blur_kernel(method, args, &nkernels); - } - - if (!nkernels) { - ctx->method = BLUR_METHOD_NONE; - return true; - } - - // 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); - - 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"); - - // clang-format off - static const char *FRAG_SHADER_BLUR = GLSL(330, - %s\n // other extension pragmas - uniform sampler2D tex_src; - uniform vec2 pixel_norm; - uniform float opacity; - in vec2 texcoord; - out vec4 out_color; - float mask_factor(); - 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 * mask_factor(); - } - ); - static const char *FRAG_SHADER_BLUR_ADD = QUOTE( - sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%.7g, %.7g)); - ); - // clang-format on - - const char *shader_add = FRAG_SHADER_BLUR_ADD; - char *extension = strdup(""); - - for (int i = 0; i < nkernels; i++) { - auto kern = kernels[i]; - // 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; - 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); - } - } - } - - auto pass = ctx->blur_shader + i; - size_t shader_len = strlen(FRAG_SHADER_BLUR) + strlen(extension) + - strlen(shader_body) + 10 /* sum */ + - 1 /* null terminator */; - char *shader_str = ccalloc(shader_len, char); - auto real_shader_len = snprintf(shader_str, shader_len, FRAG_SHADER_BLUR, - extension, shader_body, sum); - CHECK(real_shader_len >= 0); - CHECK((size_t)real_shader_len < shader_len); - free(shader_body); - - // Build program - pass->prog = gl_create_program_from_strv( - (const char *[]){vertex_shader, NULL}, - (const char *[]){shader_str, masking_glsl, NULL}); - free(shader_str); - if (!pass->prog) { - log_error("Failed to create GLSL program."); - success = false; - goto out; - } - glBindFragDataLocation(pass->prog, 0, "out_color"); - - // Get uniform addresses - bind_uniform(pass, pixel_norm); - bind_uniform(pass, opacity); - - bind_uniform(pass, mask_tex); - bind_uniform(pass, mask_offset); - bind_uniform(pass, mask_inverted); - bind_uniform(pass, mask_corner_radius); - log_info("Uniform locations: %d %d %d %d %d", pass->uniform_mask_tex, - pass->uniform_mask_offset, pass->uniform_mask_inverted, - pass->uniform_mask_corner_radius, pass->uniform_opacity); - pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); - - // Setup projection matrix - glUseProgram(pass->prog); - int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - - ctx->resize_width += kern->w / 2; - ctx->resize_height += kern->h / 2; - } - - if (nkernels == 1) { - // Generate an extra null pass so we don't need special code path for - // the single pass case - auto pass = &ctx->blur_shader[1]; - pass->prog = gl_create_program_from_strv( - (const char *[]){vertex_shader, NULL}, - (const char *[]){copy_with_mask_frag, masking_glsl, NULL}); - pass->uniform_pixel_norm = -1; - pass->uniform_opacity = -1; - pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); - bind_uniform(pass, mask_tex); - bind_uniform(pass, mask_offset); - bind_uniform(pass, mask_inverted); - bind_uniform(pass, mask_corner_radius); - - // Setup projection matrix - glUseProgram(pass->prog); - int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - - ctx->npasses = 2; - } else { - ctx->npasses = nkernels; - } - - success = true; -out: - if (method != BLUR_METHOD_KERNEL) { - // We generated the blur kernels, so we need to free them - for (int i = 0; i < nkernels; i++) { - free(kernels[i]); - } - 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 - bind_uniform(down_pass, pixel_norm); - 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; - float mask_factor(); - 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 * mask_factor(); - } - ); - // 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_strv( - (const char *[]){vertex_shader, NULL}, - (const char *[]){shader_str, masking_glsl, NULL}); - 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 - bind_uniform(up_pass, pixel_norm); - bind_uniform(up_pass, opacity); - - bind_uniform(up_pass, mask_tex); - bind_uniform(up_pass, mask_offset); - bind_uniform(up_pass, mask_inverted); - bind_uniform(up_pass, mask_corner_radius); - - 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) { - auto ctx = (struct gl_blur_context *)blur_context; - *width = ctx->resize_width; - *height = ctx->resize_height; -} - -// clang-format off -const char *masking_glsl = GLSL(330, - uniform sampler2D mask_tex; - uniform vec2 mask_offset; - uniform float mask_corner_radius; - uniform bool mask_inverted; - in vec2 texcoord; - float mask_rectangle_sdf(vec2 point, vec2 half_size) { - vec2 d = abs(point) - half_size; - return length(max(d, 0.0)); - } - float mask_factor() { - vec2 mask_size = textureSize(mask_tex, 0); - vec2 maskcoord = texcoord - mask_offset; - vec4 mask = texture2D(mask_tex, maskcoord / mask_size); - if (mask_corner_radius != 0) { - vec2 inner_size = mask_size - vec2(mask_corner_radius) * 2.0f; - float dist = mask_rectangle_sdf(maskcoord - mask_size / 2.0f, - inner_size / 2.0f) - mask_corner_radius; - if (dist > 0.0f) { - mask.r = (1.0f - clamp(dist, 0.0f, 1.0f)); - } - } - if (mask_inverted) { - mask.rgb = 1.0 - mask.rgb; - } - return mask.r; - } -); -const char *win_shader_glsl = GLSL(330, - uniform float opacity; - uniform float dim; - uniform float corner_radius; - uniform float border_width; - uniform bool invert_color; - in vec2 texcoord; - uniform sampler2D tex; - uniform sampler2D brightness; - uniform float max_brightness; - // Signed distance field for rectangle center at (0, 0), with size of - // half_size * 2 - float rectangle_sdf(vec2 point, vec2 half_size) { - vec2 d = abs(point) - half_size; - return length(max(d, 0.0)); - } - - vec4 default_post_processing(vec4 c) { - vec4 border_color = texture(tex, vec2(0.0, 0.5)); - if (invert_color) { - c = vec4(c.aaa - c.rgb, c.a); - border_color = vec4(border_color.aaa - border_color.rgb, border_color.a); - } - c = vec4(c.rgb * (1.0 - dim), c.a) * opacity; - border_color = vec4(border_color.rgb * (1.0 - dim), border_color.a) * opacity; - - vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb; - // Ref: https://en.wikipedia.org/wiki/Relative_luminance - float brightness = rgb_brightness.r * 0.21 + - rgb_brightness.g * 0.72 + - rgb_brightness.b * 0.07; - if (brightness > max_brightness) { - c.rgb = c.rgb * (max_brightness / brightness); - border_color.rgb = border_color.rgb * (max_brightness / brightness); - } - - // Rim color is the color of the outer rim of the window, if there is no - // border, it's the color of the window itself, otherwise it's the border. - // Using mix() to avoid a branch here. - vec4 rim_color = mix(c, border_color, clamp(border_width, 0.0f, 1.0f)); - - vec2 outer_size = vec2(textureSize(tex, 0)); - vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f; - float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f, - inner_size / 2.0f) - corner_radius; - if (rect_distance > 0.0f) { - c = (1.0f - clamp(rect_distance, 0.0f, 1.0f)) * rim_color; - } else { - float factor = clamp(rect_distance + border_width, 0.0f, 1.0f); - c = (1.0f - factor) * c + factor * border_color; - } - - return c; - } - - vec4 window_shader(); - float mask_factor(); - - void main() { - gl_FragColor = window_shader() * mask_factor(); - } -); - -const char *win_shader_default = GLSL(330, - in vec2 texcoord; - uniform sampler2D tex; - vec4 default_post_processing(vec4 c); - vec4 window_shader() { - vec4 c = texelFetch(tex, ivec2(texcoord), 0); - return default_post_processing(c); - } -); - -const char *present_vertex_shader = GLSL(330, - uniform mat4 projection; - layout(location = 0) in vec2 coord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(coord, 0, 1); - texcoord = coord; - } -); -// clang-format on - void *gl_create_window_shader(backend_t *backend_data attr_unused, const char *source) { auto win_shader = (gl_win_shader_t *)ccalloc(1, gl_win_shader_t); diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 8066c102..5638be3c 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -117,9 +117,13 @@ typedef struct session session_t; #define GL_PROG_MAIN_INIT \ { .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, } +void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, int texture_height, + int root_height, bool y_inverted, GLint *coord, GLuint *indices); + GLuint gl_create_shader(GLenum shader_type, const char *shader_str); GLuint gl_create_program(const GLuint *const shaders, int nshaders); GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str); +GLuint gl_create_program_from_strv(const char **vert_shaders, const char **frag_shaders); void *gl_create_window_shader(backend_t *backend_data, const char *source); void gl_destroy_window_shader(backend_t *backend_data, void *shader); uint64_t gl_get_shader_attributes(backend_t *backend_data, void *shader); @@ -258,3 +262,13 @@ static inline bool gl_has_extension(const char *ext) { log_info("Missing GL extension %s.", ext); return false; } + +static const GLuint vert_coord_loc = 0; +static const GLuint vert_in_texcoord_loc = 1; + +#define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__ +#define QUOTE(...) #__VA_ARGS__ + +extern const char vertex_shader[], copy_with_mask_frag[], masking_glsl[], dummy_frag[], + fill_frag[], fill_vert[], interpolating_frag[], interpolating_vert[], + win_shader_glsl[], win_shader_default[], present_vertex_shader[]; diff --git a/src/backend/gl/shaders.c b/src/backend/gl/shaders.c new file mode 100644 index 00000000..7439cd33 --- /dev/null +++ b/src/backend/gl/shaders.c @@ -0,0 +1,177 @@ +#include "gl_common.h" + +// clang-format off +const char dummy_frag[] = GLSL(330, + uniform sampler2D tex; + in vec2 texcoord; + void main() { + gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0); + } +); + +const char copy_with_mask_frag[] = GLSL(330, + uniform sampler2D tex; + in vec2 texcoord; + float mask_factor(); + void main() { + gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0) * mask_factor(); + } +); + +const char fill_frag[] = GLSL(330, + uniform vec4 color; + void main() { + gl_FragColor = color; + } +); + +const char fill_vert[] = GLSL(330, + layout(location = 0) in vec2 in_coord; + uniform mat4 projection; + void main() { + gl_Position = projection * vec4(in_coord, 0, 1); + } +); + +const char interpolating_frag[] = GLSL(330, + uniform sampler2D tex; + in vec2 texcoord; + void main() { + gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1); + } +); + +const char interpolating_vert[] = GLSL(330, + uniform mat4 projection; + uniform vec2 texsize; + layout(location = 0) in vec2 in_coord; + layout(location = 1) in vec2 in_texcoord; + out vec2 texcoord; + void main() { + gl_Position = projection * vec4(in_coord, 0, 1); + texcoord = in_texcoord / texsize; + } +); +const char masking_glsl[] = GLSL(330, + uniform sampler2D mask_tex; + uniform vec2 mask_offset; + uniform float mask_corner_radius; + uniform bool mask_inverted; + in vec2 texcoord; + float mask_rectangle_sdf(vec2 point, vec2 half_size) { + vec2 d = abs(point) - half_size; + return length(max(d, 0.0)); + } + float mask_factor() { + vec2 mask_size = textureSize(mask_tex, 0); + vec2 maskcoord = texcoord - mask_offset; + vec4 mask = texture2D(mask_tex, maskcoord / mask_size); + if (mask_corner_radius != 0) { + vec2 inner_size = mask_size - vec2(mask_corner_radius) * 2.0f; + float dist = mask_rectangle_sdf(maskcoord - mask_size / 2.0f, + inner_size / 2.0f) - mask_corner_radius; + if (dist > 0.0f) { + mask.r = (1.0f - clamp(dist, 0.0f, 1.0f)); + } + } + if (mask_inverted) { + mask.rgb = 1.0 - mask.rgb; + } + return mask.r; + } +); +const char win_shader_glsl[] = GLSL(330, + uniform float opacity; + uniform float dim; + uniform float corner_radius; + uniform float border_width; + uniform bool invert_color; + in vec2 texcoord; + uniform sampler2D tex; + uniform sampler2D brightness; + uniform float max_brightness; + // Signed distance field for rectangle center at (0, 0), with size of + // half_size * 2 + float rectangle_sdf(vec2 point, vec2 half_size) { + vec2 d = abs(point) - half_size; + return length(max(d, 0.0)); + } + + vec4 default_post_processing(vec4 c) { + vec4 border_color = texture(tex, vec2(0.0, 0.5)); + if (invert_color) { + c = vec4(c.aaa - c.rgb, c.a); + border_color = vec4(border_color.aaa - border_color.rgb, border_color.a); + } + c = vec4(c.rgb * (1.0 - dim), c.a) * opacity; + border_color = vec4(border_color.rgb * (1.0 - dim), border_color.a) * opacity; + + vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb; + // Ref: https://en.wikipedia.org/wiki/Relative_luminance + float brightness = rgb_brightness.r * 0.21 + + rgb_brightness.g * 0.72 + + rgb_brightness.b * 0.07; + if (brightness > max_brightness) { + c.rgb = c.rgb * (max_brightness / brightness); + border_color.rgb = border_color.rgb * (max_brightness / brightness); + } + + // Rim color is the color of the outer rim of the window, if there is no + // border, it's the color of the window itself, otherwise it's the border. + // Using mix() to avoid a branch here. + vec4 rim_color = mix(c, border_color, clamp(border_width, 0.0f, 1.0f)); + + vec2 outer_size = vec2(textureSize(tex, 0)); + vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f; + float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f, + inner_size / 2.0f) - corner_radius; + if (rect_distance > 0.0f) { + c = (1.0f - clamp(rect_distance, 0.0f, 1.0f)) * rim_color; + } else { + float factor = clamp(rect_distance + border_width, 0.0f, 1.0f); + c = (1.0f - factor) * c + factor * border_color; + } + + return c; + } + + vec4 window_shader(); + float mask_factor(); + + void main() { + gl_FragColor = window_shader() * mask_factor(); + } +); + +const char win_shader_default[] = GLSL(330, + in vec2 texcoord; + uniform sampler2D tex; + vec4 default_post_processing(vec4 c); + vec4 window_shader() { + vec4 c = texelFetch(tex, ivec2(texcoord), 0); + return default_post_processing(c); + } +); + +const char present_vertex_shader[] = GLSL(330, + uniform mat4 projection; + layout(location = 0) in vec2 coord; + out vec2 texcoord; + void main() { + gl_Position = projection * vec4(coord, 0, 1); + texcoord = coord; + } +); +const char vertex_shader[] = GLSL(330, + uniform mat4 projection; + uniform float scale = 1.0; + 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, 0, scale); + texcoord = in_texcoord + texorig; + } +); +// clang-format on diff --git a/src/backend/meson.build b/src/backend/meson.build index b8f0ad9a..511394b8 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -3,5 +3,5 @@ srcs += [ files('backend_common.c', 'xrender/xrender.c', 'dummy/dummy.c', 'backe # enable opengl if get_option('opengl') - srcs += [ files('gl/gl_common.c', 'gl/glx.c') ] + srcs += [ files('gl/gl_common.c', 'gl/glx.c', 'gl/blur.c', 'gl/shaders.c') ] endif From e942f253f7fd7eaa5d3348c99b0539643d2c03a6 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 25 Aug 2022 13:34:21 +0100 Subject: [PATCH 02/11] backend: gl: make blur take a source texture and a target fbo Instead of always using the back texture/fbo. Also use the size of the source texture, instead of hard coded back buffer size. Signed-off-by: Yuxuan Shui --- src/backend/gl/blur.c | 75 +++++++++++++++++++++----------------- src/backend/gl/gl_common.h | 1 + 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c index 0407501c..c2de2e08 100644 --- a/src/backend/gl/blur.c +++ b/src/backend/gl/blur.c @@ -38,12 +38,10 @@ struct gl_blur_context { /** * Blur contents in a particular region. */ -bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, +bool gl_kernel_blur(double opacity, struct gl_blur_context *bctx, const rect_t *extent, struct backend_image *mask, coord_t mask_dst, const GLuint vao[2], - const int vao_nelems[2]) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - + const int vao_nelems[2], GLuint source_texture, + geometry_t source_size, GLuint target_fbo, GLuint default_mask) { int dst_y_fb_coord = bctx->fb_height - extent->y2; int curr = 0; @@ -59,9 +57,9 @@ bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *ex GLuint src_texture; if (i == 0) { - src_texture = gd->back_texture; - tex_width = gd->width; - tex_height = gd->height; + src_texture = source_texture; + tex_width = source_size.width; + tex_height = source_size.height; } else { src_texture = bctx->blur_textures[curr]; auto src_size = bctx->texture_sizes[curr]; @@ -76,7 +74,7 @@ bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *ex 1.0F / (GLfloat)tex_height); glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, gd->default_mask_texture); + glBindTexture(GL_TEXTURE_2D, default_mask); glUniform1i(p->uniform_mask_tex, 1); glUniform2f(p->uniform_mask_offset, 0.0F, 0.0F); @@ -119,7 +117,7 @@ bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *ex } glBindVertexArray(vao[0]); nelems = vao_nelems[0]; - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, target_fbo); glUniform1f(p->uniform_opacity, (float)opacity); } @@ -136,12 +134,10 @@ bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *ex return true; } -bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, - struct backend_image *mask, coord_t mask_dst, - const GLuint vao[2], const int vao_nelems[2]) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - +bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rect_t *extent, + struct backend_image *mask, coord_t mask_dst, const GLuint vao[2], + const int vao_nelems[2], GLuint source_texture, + geometry_t source_size, GLuint target_fbo, GLuint default_mask) { int dst_y_fb_coord = bctx->fb_height - extent->y2; int iterations = bctx->blur_texture_count; @@ -163,9 +159,9 @@ bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_ if (i == 0) { // first pass: copy from back buffer - src_texture = gd->back_texture; - tex_width = gd->width; - tex_height = gd->height; + src_texture = source_texture; + tex_width = source_size.width; + tex_height = source_size.height; } else { // copy from previous pass src_texture = bctx->blur_textures[i - 1]; @@ -216,7 +212,7 @@ bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_ glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, src_texture); glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, gd->default_mask_texture); + glBindTexture(GL_TEXTURE_2D, default_mask); glUniform1i(up_pass->uniform_mask_tex, 1); glUniform2f(up_pass->uniform_mask_offset, 0.0F, 0.0F); @@ -248,7 +244,7 @@ bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_ } glBindVertexArray(vao[0]); nelems = vao_nelems[0]; - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target_fbo); glUniform1f(up_pass->uniform_opacity, (GLfloat)opacity); } @@ -263,18 +259,17 @@ bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_ return true; } -bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst, - 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 gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, + coord_t mask_dst, const region_t *reg_blur, + const region_t *reg_visible attr_unused, GLuint source_texture, + geometry_t source_size, GLuint target_fbo, GLuint default_mask) { bool ret = false; - if (gd->width != bctx->fb_width || gd->height != bctx->fb_height) { + if (source_size.width != bctx->fb_width || source_size.height != bctx->fb_height) { // Resize the temporary textures used for blur in case the root // size changed - bctx->fb_width = gd->width; - bctx->fb_height = gd->height; + bctx->fb_width = source_size.width; + bctx->fb_height = source_size.height; for (int i = 0; i < bctx->blur_texture_count; ++i) { auto tex_size = bctx->texture_sizes + i; @@ -329,7 +324,7 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mas auto indices = ccalloc(nrects * 6, GLuint); x_rect_to_coords(nrects, rects, (coord_t){.x = extent_resized->x1, .y = extent_resized->y2}, - bctx->fb_height, gd->height, false, coord, indices); + bctx->fb_height, source_size.height, false, coord, indices); auto coord_resized = ccalloc(nrects_resized * 16, GLint); auto indices_resized = ccalloc(nrects_resized * 6, GLuint); @@ -373,11 +368,13 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mas int vao_nelems[2] = {nrects * 6, nrects_resized * 6}; if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { - ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, mask, - mask_dst, vao, vao_nelems); + ret = gl_dual_kawase_blur(opacity, bctx, extent_resized, mask, mask_dst, + vao, vao_nelems, source_texture, source_size, + target_fbo, default_mask); } else { - ret = gl_kernel_blur(base, opacity, ctx, extent_resized, mask, mask_dst, - vao, vao_nelems); + ret = gl_kernel_blur(opacity, bctx, extent_resized, mask, mask_dst, vao, + vao_nelems, source_texture, source_size, target_fbo, + default_mask); } glBindFramebuffer(GL_FRAMEBUFFER, 0); @@ -401,6 +398,16 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mas return ret; } +bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst, + const region_t *reg_blur, const region_t *reg_visible attr_unused) { + auto gd = (struct gl_data *)base; + auto bctx = (struct gl_blur_context *)ctx; + return gl_blur_impl(opacity, bctx, mask, mask_dst, reg_blur, reg_visible, + gd->back_texture, + (geometry_t){.width = gd->width, .height = gd->height}, + gd->back_fbo, gd->default_mask_texture); +} + static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { if (shader->prog) { glDeleteProgram(shader->prog); diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 5638be3c..f716093b 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -12,6 +12,7 @@ #define CASESTRRET(s) \ case s: return #s +struct gl_blur_context; static inline GLint glGetUniformLocationChecked(GLuint p, const char *name) { auto ret = glGetUniformLocation(p, name); From 84407099a99d44d0fc40ffa5525b9bf7bde37cfa Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 25 Aug 2022 05:21:19 +0100 Subject: [PATCH 03/11] backend: give backends more flexibility regarding shadow creation Signed-off-by: Yuxuan Shui --- src/backend/backend.h | 18 +++++++++++++++++- src/backend/backend_common.c | 27 ++++++++++++++++++++------- src/backend/backend_common.h | 10 +++++++--- src/backend/dummy/dummy.c | 2 ++ src/backend/gl/glx.c | 2 ++ src/backend/xrender/xrender.c | 2 ++ src/common.h | 4 ++-- src/kernel.c | 17 +++++++++++------ src/kernel.h | 2 +- src/picom.c | 29 +++++++++++++++++++++++++---- src/render.c | 8 +++++--- src/win.c | 7 +++---- src/win.h | 4 +++- 13 files changed, 100 insertions(+), 32 deletions(-) diff --git a/src/backend/backend.h b/src/backend/backend.h index b11ca65b..e0a51ca3 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -16,6 +16,8 @@ typedef struct session session_t; struct managed_win; +struct backend_shadow_context; + struct ev_loop; struct backend_operations; @@ -213,10 +215,24 @@ struct backend_operations { void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned); + struct backend_shadow_context *(*create_shadow_context)(backend_t *backend_data, + double radius); + void (*destroy_shadow_context)(backend_t *backend_data, + struct backend_shadow_context *ctx); + /// Create a shadow image based on the parameters /// Default implementation: default_backend_render_shadow + /// + /// Required. void *(*render_shadow)(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a); + struct backend_shadow_context *ctx, struct color color); + + /// Create a shadow by blurring a mask. `size` is the size of the blur. The + /// backend can use whichever blur method is the fastest. + /// + /// Optional. + void *(*shadow_from_mask)(backend_t *backend_data, void *mask, + struct backend_shadow_context *ctx, struct color color); /// Create a mask image from region `reg`. This region can be used to create /// shadow, or used as a mask for composing. When used as a mask, it should mask diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index 165a9dd4..eba5cb47 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -291,16 +291,16 @@ shadow_picture_err: return false; } -void * -default_backend_render_shadow(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a) { - xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root, - true, 1, r, g, b), +void *default_backend_render_shadow(backend_t *backend_data, int width, int height, + struct backend_shadow_context *sctx, struct color color) { + const conv *kernel = (void *)sctx; + xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root, true, + 1, color.red, color.green, color.blue), shadow = XCB_NONE; xcb_render_picture_t pict = XCB_NONE; - if (!build_shadow(backend_data->c, backend_data->root, a, width, height, kernel, - shadow_pixel, &shadow, &pict)) { + if (!build_shadow(backend_data->c, backend_data->root, color.alpha, width, height, + kernel, shadow_pixel, &shadow, &pict)) { return NULL; } @@ -311,6 +311,19 @@ default_backend_render_shadow(backend_t *backend_data, int width, int height, return ret; } +struct backend_shadow_context * +default_create_shadow_context(backend_t *backend_data attr_unused, double radius) { + auto ret = + (struct backend_shadow_context *)gaussian_kernel_autodetect_deviation(radius); + sum_kernel_preprocess((conv *)ret); + return ret; +} + +void default_destroy_shadow_context(backend_t *backend_data attr_unused, + struct backend_shadow_context *sctx) { + free_conv((conv *)sctx); +} + static struct conv **generate_box_blur_kernel(struct box_blur_args *args, int *kernel_count) { int r = args->size * 2 + 1; assert(r > 0); diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h index cf77ce8b..60286d1d 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -62,9 +62,13 @@ bool default_is_win_transparent(void *, win *, void *); /// caveat as `default_is_win_transparent` applies. bool default_is_frame_transparent(void *, win *, void *); -void * -default_backend_render_shadow(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a); +void *default_backend_render_shadow(backend_t *backend_data, int width, int height, + struct backend_shadow_context *sctx, struct color color); +struct backend_shadow_context * +default_create_shadow_context(backend_t *backend_data, double radius); + +void default_destroy_shadow_context(backend_t *backend_data, + struct backend_shadow_context *sctx); void init_backend_base(struct backend_base *base, session_t *ps); diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index c7e0e2cf..7e06face 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -175,6 +175,8 @@ struct backend_operations dummy_ops = { .fill = dummy_fill, .blur = dummy_blur, .bind_pixmap = dummy_bind_pixmap, + .create_shadow_context = default_create_shadow_context, + .destroy_shadow_context = default_destroy_shadow_context, .render_shadow = default_backend_render_shadow, .make_mask = dummy_make_mask, .release_image = dummy_release_image, diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index e0cc368f..26aaa49b 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -536,6 +536,8 @@ struct backend_operations glx_ops = { .is_image_transparent = default_is_image_transparent, .present = glx_present, .buffer_age = glx_buffer_age, + .create_shadow_context = default_create_shadow_context, + .destroy_shadow_context = default_destroy_shadow_context, .render_shadow = default_backend_render_shadow, .make_mask = gl_make_mask, .fill = gl_fill, diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 08e804f1..2b7f8e17 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -968,6 +968,8 @@ struct backend_operations xrender_ops = { .fill = fill, .bind_pixmap = bind_pixmap, .release_image = release_image, + .create_shadow_context = default_create_shadow_context, + .destroy_shadow_context = default_destroy_shadow_context, .render_shadow = default_backend_render_shadow, .make_mask = make_mask, //.prepare_win = prepare_win, diff --git a/src/common.h b/src/common.h index a68565b1..c06a30c2 100644 --- a/src/common.h +++ b/src/common.h @@ -300,8 +300,8 @@ typedef struct session { xcb_render_picture_t cshadow_picture; /// 1x1 white Picture. xcb_render_picture_t white_picture; - /// Gaussian map of shadow. - struct conv *gaussian_map; + /// Backend shadow context. + struct backend_shadow_context *shadow_context; // for shadow precomputation /// A region in which shadow is not painted on. region_t shadow_exclude_reg; diff --git a/src/kernel.c b/src/kernel.c index 51510452..547e25bc 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -90,15 +90,20 @@ conv *gaussian_kernel(double r, int size) { /// Estimate the element of the sum of the first row in a gaussian kernel with standard /// deviation `r` and size `size`, static inline double estimate_first_row_sum(double size, double r) { + // `factor` is integral of gaussian from -size to size double factor = erf(size / r / sqrt(2)); + // `a` is gaussian at (size, 0) double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r; + // The sum of the whole kernel is normalized to 1, i.e. each element is divided by + // factor sqaured. So the sum of the first row is a * factor / factor^2 = a / + // factor return a / factor; } -/// Pick a suitable gaussian kernel radius for a given kernel size. The returned radius -/// is the maximum possible radius (<= size*2) that satisfies no sum of the rows in -/// the kernel are less than `row_limit` (up to certain precision). -static inline double gaussian_kernel_std_for_size(int size, double row_limit) { +/// Pick a suitable gaussian kernel standard deviation for a given kernel size. The +/// returned radius is the maximum possible radius (<= size*2) that satisfies no sum of +/// the rows in the kernel are less than `row_limit` (up to certain precision). +static inline double gaussian_kernel_std_for_size(double size, double row_limit) { assert(size > 0); if (row_limit >= 1.0 / 2.0 / size) { return size * 2; @@ -121,9 +126,9 @@ static inline double gaussian_kernel_std_for_size(int size, double row_limit) { /// transparent, so the transition from shadow to the background is smooth. /// /// @param[in] shadow_radius the radius of the shadow -conv *gaussian_kernel_autodetect_deviation(int shadow_radius) { +conv *gaussian_kernel_autodetect_deviation(double shadow_radius) { assert(shadow_radius >= 0); - int size = shadow_radius * 2 + 1; + int size = (int)(shadow_radius * 2 + 1); if (shadow_radius == 0) { return gaussian_kernel(0, size); diff --git a/src/kernel.h b/src/kernel.h index 251d127d..85997de3 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -27,7 +27,7 @@ conv *gaussian_kernel(double r, int size); /// transparent. /// /// @param[in] shadow_radius the radius of the shadow -conv *gaussian_kernel_autodetect_deviation(int shadow_radius); +conv *gaussian_kernel_autodetect_deviation(double shadow_radius); /// preprocess kernels to make shadow generation faster /// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive diff --git a/src/picom.c b/src/picom.c index ce4f0d22..e1c1f1e7 100644 --- a/src/picom.c +++ b/src/picom.c @@ -451,6 +451,11 @@ static void destroy_backend(session_t *ps) { ps->backend_data, ps->backend_blur_context); ps->backend_blur_context = NULL; } + if (ps->shadow_context) { + ps->backend_data->ops->destroy_shadow_context(ps->backend_data, + ps->shadow_context); + ps->shadow_context = NULL; + } ps->backend_data->ops->deinit(ps->backend_data); ps->backend_data = NULL; } @@ -504,6 +509,12 @@ static bool initialize_backend(session_t *ps) { return false; } ps->backend_data->ops = backend_list[ps->o.backend]; + ps->shadow_context = ps->backend_data->ops->create_shadow_context( + ps->backend_data, ps->o.shadow_radius); + if (!ps->shadow_context) { + log_fatal("Failed to initialize shadow context, aborting..."); + goto err; + } if (!initialize_blur(ps)) { log_fatal("Failed to prepare for background blur, aborting..."); @@ -553,6 +564,11 @@ static bool initialize_backend(session_t *ps) { // The old backends binds pixmap lazily, nothing to do here return true; err: + if (ps->shadow_context) { + ps->backend_data->ops->destroy_shadow_context(ps->backend_data, + ps->shadow_context); + ps->shadow_context = NULL; + } ps->backend_data->ops->deinit(ps->backend_data); ps->backend_data = NULL; quit(ps); @@ -1675,7 +1691,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .black_picture = XCB_NONE, .cshadow_picture = XCB_NONE, .white_picture = XCB_NONE, - .gaussian_map = NULL, + .shadow_context = NULL, #ifdef CONFIG_VSYNC_DRM .drm_fd = -1, @@ -1929,8 +1945,11 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } } - ps->gaussian_map = gaussian_kernel_autodetect_deviation(ps->o.shadow_radius); - sum_kernel_preprocess(ps->gaussian_map); + if (ps->o.legacy_backends) { + ps->shadow_context = + (void *)gaussian_kernel_autodetect_deviation(ps->o.shadow_radius); + sum_kernel_preprocess((conv *)ps->shadow_context); + } rebuild_shadow_exclude_reg(ps); @@ -2408,7 +2427,9 @@ static void session_destroy(session_t *ps) { // Flush all events x_sync(ps->c); ev_io_stop(ps->loop, &ps->xiow); - free_conv(ps->gaussian_map); + if (ps->o.legacy_backends) { + free_conv((conv *)ps->shadow_context); + } destroy_atoms(ps->atoms); #ifdef DEBUG_XRC diff --git a/src/render.c b/src/render.c index ac9b40ec..db627de3 100644 --- a/src/render.c +++ b/src/render.c @@ -649,8 +649,9 @@ static bool get_root_tile(session_t *ps) { static void paint_root(session_t *ps, const region_t *reg_paint) { // If there is no root tile pixmap, try getting one. // If that fails, give up. - if (!ps->root_tile_paint.pixmap && !get_root_tile(ps)) + if (!ps->root_tile_paint.pixmap && !get_root_tile(ps)) { return; + } paint_region(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint, ps->root_tile_paint.pict); @@ -669,7 +670,7 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit xcb_render_picture_t shadow_picture = XCB_NONE, shadow_picture_argb = XCB_NONE; xcb_gcontext_t gc = XCB_NONE; - shadow_image = make_shadow(ps->c, ps->gaussian_map, opacity, width, height); + shadow_image = make_shadow(ps->c, (conv *)ps->shadow_context, opacity, width, height); if (!shadow_image) { log_error("failed to make shadow"); return XCB_NONE; @@ -689,8 +690,9 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit ps->c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL); shadow_picture_argb = x_create_picture_with_standard_and_pixmap( ps->c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL); - if (!shadow_picture || !shadow_picture_argb) + if (!shadow_picture || !shadow_picture_argb) { goto shadow_picture_err; + } gc = x_new_id(ps->c); xcb_create_gc(ps->c, gc, shadow_pixmap, 0, NULL); diff --git a/src/win.c b/src/win.c index d579c69c..3f376a77 100644 --- a/src/win.c +++ b/src/win.c @@ -346,11 +346,10 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w } bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c, - struct conv *kernel) { + struct backend_shadow_context *sctx) { assert(!w->shadow_image); assert(w->shadow); - w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, kernel, c.red, - c.green, c.blue, c.alpha); + w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, sctx, c); if (!w->shadow_image) { log_error("Failed to bind shadow image, shadow will be disabled for " "%#010x (%s)", @@ -556,7 +555,7 @@ void win_process_image_flags(session_t *ps, struct managed_win *w) { .green = ps->o.shadow_green, .blue = ps->o.shadow_blue, .alpha = ps->o.shadow_opacity}, - ps->gaussian_map); + ps->shadow_context); } } diff --git a/src/win.h b/src/win.h index ad5fe2fc..fb5137c0 100644 --- a/src/win.h +++ b/src/win.h @@ -7,6 +7,8 @@ #include #include +#include + #include "uthash_extra.h" // FIXME shouldn't need this @@ -289,7 +291,7 @@ void win_process_update_flags(session_t *ps, struct managed_win *w); void win_process_image_flags(session_t *ps, struct managed_win *w); /// Bind a shadow to the window, with color `c` and shadow kernel `kernel` bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c, - struct conv *kernel); + struct backend_shadow_context *kernel); /// Start the unmap of a window. We cannot unmap immediately since we might need to fade /// the window out. From 94e3d4d483bf050c2ebee4ee67f02eaffd1c53f3 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 25 Aug 2022 05:29:26 +0100 Subject: [PATCH 04/11] kernel: be more conservative when estimating deviation Using integral to estimate the sum of the kernel will overestimate a little bit. Signed-off-by: Yuxuan Shui --- src/kernel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kernel.c b/src/kernel.c index 547e25bc..a96e9f30 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -133,7 +133,7 @@ conv *gaussian_kernel_autodetect_deviation(double shadow_radius) { if (shadow_radius == 0) { return gaussian_kernel(0, size); } - double std = gaussian_kernel_std_for_size(shadow_radius, 1.0 / 256.0); + double std = gaussian_kernel_std_for_size(shadow_radius, 0.5 / 256.0); return gaussian_kernel(std, size); } From de209fd52c6e06c327ca78921386df375b17bbd5 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 25 Aug 2022 17:49:37 +0100 Subject: [PATCH 05/11] backend: gl: fix x_rect_to_coords when y_inverted is false Since image_dst is in X coordinates, after flipping Y, we need to subtract the height of the drawing area, to make it the bottom right corner for OpenGL. However, this breaks blur. Because we assumed the drawing area is the same size as the texture, which is not the case for blur. So add the height of the drawing area as another parameter. Signed-off-by: Yuxuan Shui --- src/backend/gl/blur.c | 13 +++++++------ src/backend/gl/gl_common.c | 17 ++++++++--------- src/backend/gl/gl_common.h | 5 +++-- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c index c2de2e08..271a17ab 100644 --- a/src/backend/gl/blur.c +++ b/src/backend/gl/blur.c @@ -322,16 +322,17 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, auto coord = ccalloc(nrects * 16, GLint); auto indices = ccalloc(nrects * 6, GLuint); - x_rect_to_coords(nrects, rects, - (coord_t){.x = extent_resized->x1, .y = extent_resized->y2}, - bctx->fb_height, source_size.height, false, coord, indices); + auto extent_height = extent_resized->y2 - extent_resized->y1; + x_rect_to_coords( + nrects, rects, (coord_t){.x = extent_resized->x1, .y = extent_resized->y1}, + extent_height, bctx->fb_height, source_size.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, - (coord_t){.x = extent_resized->x1, .y = extent_resized->y2}, - bctx->fb_height, bctx->fb_height, false, coord_resized, - indices_resized); + (coord_t){.x = extent_resized->x1, .y = extent_resized->y1}, + extent_height, bctx->fb_height, bctx->fb_height, false, + coord_resized, indices_resized); pixman_region32_fini(®_blur_resized); GLuint vao[2]; diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index d7840d86..3e7c5c8a 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -482,16 +482,16 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe /// @param[in] nrects, rects rectangles /// @param[in] image_dst origin of the OpenGL texture, affect the calculated texture /// coordinates +/// @param[in] extend_height height of the drawing extent /// @param[in] texture_height height of the OpenGL texture /// @param[in] root_height height of the back buffer /// @param[in] y_inverted whether the texture is y inverted /// @param[out] coord, indices output -void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, int texture_height, - int root_height, bool y_inverted, GLint *coord, GLuint *indices) { +void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, + int extent_height, int texture_height, int root_height, + bool y_inverted, GLint *coord, GLuint *indices) { image_dst.y = root_height - image_dst.y; - if (y_inverted) { - image_dst.y -= texture_height; - } + image_dst.y -= extent_height; for (int i = 0; i < nrects; i++) { // Y-flip. Note after this, crect.y1 > crect.y2 @@ -568,8 +568,8 @@ void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask auto coord = ccalloc(nrects * 16, GLint); auto indices = ccalloc(nrects * 6, GLuint); coord_t mask_offset = {.x = mask_dst.x - image_dst.x, .y = mask_dst.y - image_dst.y}; - x_rect_to_coords(nrects, rects, image_dst, inner->height, gd->height, - inner->y_inverted, coord, indices); + x_rect_to_coords(nrects, rects, image_dst, inner->height, inner->height, + gd->height, inner->y_inverted, coord, indices); _gl_compose(base, img, gd->back_fbo, mask, mask_offset, coord, indices, nrects); free(indices); @@ -1157,7 +1157,6 @@ enum device_status gl_device_status(backend_t *base) { } if (glGetGraphicsResetStatusARB() == GL_NO_ERROR) { return DEVICE_STATUS_NORMAL; - } else { - return DEVICE_STATUS_RESETTING; } + return DEVICE_STATUS_RESETTING; } diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index f716093b..743d8b51 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -118,8 +118,9 @@ typedef struct session session_t; #define GL_PROG_MAIN_INIT \ { .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, } -void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, int texture_height, - int root_height, bool y_inverted, GLint *coord, GLuint *indices); +void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, + int extent_height, int texture_height, int root_height, + bool y_inverted, GLint *coord, GLuint *indices); GLuint gl_create_shader(GLenum shader_type, const char *shader_str); GLuint gl_create_program(const GLuint *const shaders, int nshaders); From a9ec614286e3ca4c54df10b0573c8c1fb3ab7b87 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 25 Aug 2022 17:53:31 +0100 Subject: [PATCH 06/11] backend: add shadow_from_mask based implementation of render_shadow If the backend implements shadow_from_mask then it doesn't need to implement render_shadow. Signed-off-by: Yuxuan Shui --- src/backend/backend.h | 11 +++++++++-- src/backend/backend_common.c | 15 +++++++++++++++ src/backend/backend_common.h | 5 +++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/backend/backend.h b/src/backend/backend.h index e0a51ca3..191e8146 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -215,12 +215,18 @@ struct backend_operations { void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned); + /// Create a shadow context for rendering shadows with radius `radius`. + /// Default implementation: default_backend_create_shadow_context struct backend_shadow_context *(*create_shadow_context)(backend_t *backend_data, double radius); + /// Destroy a shadow context + /// Default implementation: default_backend_destroy_shadow_context void (*destroy_shadow_context)(backend_t *backend_data, struct backend_shadow_context *ctx); - /// Create a shadow image based on the parameters + /// Create a shadow image based on the parameters. Resulting image should have a + /// size of `width + radisu * 2` x `height + radius * 2`. Radius is set when the + /// shadow context is created. /// Default implementation: default_backend_render_shadow /// /// Required. @@ -228,7 +234,8 @@ struct backend_operations { struct backend_shadow_context *ctx, struct color color); /// Create a shadow by blurring a mask. `size` is the size of the blur. The - /// backend can use whichever blur method is the fastest. + /// backend can use whichever blur method is the fastest. The shadow produced + /// shoule be consistent with `render_shadow`. /// /// Optional. void *(*shadow_from_mask)(backend_t *backend_data, void *mask, diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index eba5cb47..d6fcce21 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -311,6 +311,21 @@ void *default_backend_render_shadow(backend_t *backend_data, int width, int heig return ret; } +/// Implement render_shadow with shadow_from_mask +void * +backend_render_shadow_from_mask(backend_t *backend_data, int width, int height, + struct backend_shadow_context *sctx, struct color color) { + region_t reg; + pixman_region32_init_rect(®, 0, 0, (unsigned int)width, (unsigned int)height); + void *mask = backend_data->ops->make_mask( + backend_data, (geometry_t){.width = width, .height = height}, ®); + pixman_region32_fini(®); + + void *shadow = backend_data->ops->shadow_from_mask(backend_data, mask, sctx, color); + backend_data->ops->release_image(backend_data, mask); + return shadow; +} + struct backend_shadow_context * default_create_shadow_context(backend_t *backend_data attr_unused, double radius) { auto ret = diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h index 60286d1d..c72a1686 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -64,6 +64,11 @@ bool default_is_frame_transparent(void *, win *, void *); void *default_backend_render_shadow(backend_t *backend_data, int width, int height, struct backend_shadow_context *sctx, struct color color); + +/// Implement `render_shadow` with `shadow_from_mask`. +void * +backend_render_shadow_from_mask(backend_t *backend_data, int width, int height, + struct backend_shadow_context *sctx, struct color color); struct backend_shadow_context * default_create_shadow_context(backend_t *backend_data, double radius); From 9ac046c2ba5ffe1c19018af403f60356679d59a5 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 25 Aug 2022 17:59:19 +0100 Subject: [PATCH 07/11] backend, win: create shadows with shadow_from_mask Do this for shaped, and rounded windows. Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 18 +-- src/win.c | 317 ++++++++++++++++++++++++------------------ src/win.h | 1 + 3 files changed, 191 insertions(+), 145 deletions(-) diff --git a/src/backend/backend.c b/src/backend/backend.c index db0bb2c9..7d40e1cb 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -206,21 +206,11 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { auto reg_bound = win_get_bounding_shape_global_by_val(w); auto reg_bound_no_corner = win_get_bounding_shape_global_without_corners_by_val(w); - region_t reg_bound_local; - pixman_region32_init(®_bound_local); - pixman_region32_copy(®_bound_local, ®_bound); - pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); if (!w->mask_image) { // TODO(yshui) only allocate a mask if the window is shaped or has // rounded corners. - w->mask_image = ps->backend_data->ops->make_mask( - ps->backend_data, - (geometry_t){.width = w->g.width, .height = w->g.height}, - ®_bound_local); - ps->backend_data->ops->set_image_property( - ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS, w->mask_image, - (double[]){w->corner_radius}); + win_bind_mask(ps->backend_data, w); } // The clip region for the current window, in global/target coordinates @@ -455,8 +445,12 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // reg_visible as a hint. Since window image data outside of the // damage region won't be painted onto target region_t reg_visible_local; + region_t reg_bound_local; { // The bounding shape, in window local coordinates + pixman_region32_init(®_bound_local); + pixman_region32_copy(®_bound_local, ®_bound); + pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); pixman_region32_init(®_visible_local); pixman_region32_intersect(®_visible_local, @@ -483,10 +477,10 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ®_paint_in_bound, ®_visible); ps->backend_data->ops->release_image(ps->backend_data, new_img); pixman_region32_fini(®_visible_local); + pixman_region32_fini(®_bound_local); } skip: pixman_region32_fini(®_bound); - pixman_region32_fini(®_bound_local); pixman_region32_fini(®_bound_no_corner); pixman_region32_fini(®_paint_in_bound); } diff --git a/src/win.c b/src/win.c index 3f376a77..d97597e3 100644 --- a/src/win.c +++ b/src/win.c @@ -345,13 +345,33 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w return true; } +bool win_bind_mask(struct backend_base *b, struct managed_win *w) { + auto reg_bound_local = win_get_bounding_shape_global_by_val(w); + pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); + w->mask_image = b->ops->make_mask( + b, (geometry_t){.width = w->g.width, .height = w->g.height}, ®_bound_local); + if (!w->mask_image) { + return false; + } + b->ops->set_image_property(b, IMAGE_PROPERTY_CORNER_RADIUS, w->mask_image, + (double[]){w->corner_radius}); + return true; +} + bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c, struct backend_shadow_context *sctx) { assert(!w->shadow_image); assert(w->shadow); - w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, sctx, c); + if ((w->corner_radius == 0 && w->bounding_shaped == false) || + b->ops->shadow_from_mask == NULL) { + w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, sctx, c); + } else { + win_bind_mask(b, w); + w->shadow_image = b->ops->shadow_from_mask(b, w->mask_image, sctx, c); + } if (!w->shadow_image) { - log_error("Failed to bind shadow image, shadow will be disabled for " + log_error("Failed to bind shadow image, shadow will be disabled " + "for " "%#010x (%s)", w->base.id, w->name); win_set_flags(w, WIN_FLAGS_SHADOW_NONE); @@ -365,10 +385,9 @@ bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color } void win_release_images(struct backend_base *backend, struct managed_win *w) { - // We don't want to decide what we should do if the image we want to release is - // stale (do we clear the stale flags or not?) - // But if we are not releasing any images anyway, we don't care about the stale - // flags. + // We don't want to decide what we should do if the image we want to + // release is stale (do we clear the stale flags or not?) But if we are + // not releasing any images anyway, we don't care about the stale flags. if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) { assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)); @@ -383,13 +402,15 @@ void win_release_images(struct backend_base *backend, struct managed_win *w) { win_release_mask(backend, w); } -/// Returns true if the `prop` property is stale, as well as clears the stale flag. +/// Returns true if the `prop` property is stale, as well as clears the stale +/// flag. static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop); -/// Returns true if any of the properties are stale, as well as clear all the stale flags. +/// Returns true if any of the properties are stale, as well as clear all the +/// stale flags. static void win_clear_all_properties_stale(struct managed_win *w); -/// Fetch new window properties from the X server, and run appropriate updates. Might set -/// WIN_FLAGS_FACTOR_CHANGED +/// Fetch new window properties from the X server, and run appropriate updates. +/// Might set WIN_FLAGS_FACTOR_CHANGED static void win_update_properties(session_t *ps, struct managed_win *w) { if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_WINDOW_TYPE)) { win_update_wintype(ps, w); @@ -440,8 +461,8 @@ static void win_update_properties(session_t *ps, struct managed_win *w) { /// Handle non-image flags. This phase might set IMAGES_STALE flags void win_process_update_flags(session_t *ps, struct managed_win *w) { - // Whether the window was visible before we process the mapped flag. i.e. is the - // window just mapped. + // Whether the window was visible before we process the mapped flag. i.e. + // is the window just mapped. bool was_visible = win_is_real_visible(w); log_trace("Processing flags for window %#010x (%s), was visible: %d", w->base.id, w->name, was_visible); @@ -456,8 +477,8 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { return; } - // Check client first, because later property updates need accurate client window - // information + // Check client first, because later property updates need accurate client + // window information if (win_check_flags_all(w, WIN_FLAGS_CLIENT_STALE)) { win_recheck_client(ps, w); win_clear_flags(w, WIN_FLAGS_CLIENT_STALE); @@ -466,15 +487,15 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { bool damaged = false; if (win_check_flags_any(w, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) { if (was_visible) { - // Mark the old extents of this window as damaged. The new extents - // will be marked damaged below, after the window extents are - // updated. + // Mark the old extents of this window as damaged. The new + // extents will be marked damaged below, after the window + // extents are updated. // - // If the window is just mapped, we don't need to mark the old - // extent as damaged. (It's possible that the window was in fading - // and is interrupted by being mapped. In that case, the fading - // window will be added to damage by map_win_start, so we don't - // need to do it here) + // If the window is just mapped, we don't need to mark the + // old extent as damaged. (It's possible that the window + // was in fading and is interrupted by being mapped. In + // that case, the fading window will be added to damage by + // map_win_start, so we don't need to do it here) add_damage_from_win(ps, w); } @@ -501,7 +522,8 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { win_clear_flags(w, WIN_FLAGS_PROPERTY_STALE); } - // Factor change flags could be set by previous stages, so must be handled last + // Factor change flags could be set by previous stages, so must be handled + // last if (win_check_flags_all(w, WIN_FLAGS_FACTOR_CHANGED)) { win_on_factor_change(ps, w); win_clear_flags(w, WIN_FLAGS_FACTOR_CHANGED); @@ -533,9 +555,10 @@ void win_process_image_flags(session_t *ps, struct managed_win *w) { } if (win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)) { - // Check to make sure the window is still mapped, otherwise we - // won't be able to rebind pixmap after releasing it, yet we might - // still need the pixmap for rendering. + // Check to make sure the window is still mapped, + // otherwise we won't be able to rebind pixmap after + // releasing it, yet we might still need the pixmap for + // rendering. assert(w->state != WSTATE_UNMAPPING && w->state != WSTATE_DESTROYING); if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) { // Must release images first, otherwise breaks @@ -615,7 +638,8 @@ int win_update_name(session_t *ps, struct managed_win *w) { } if (!(wid_get_text_prop(ps, w->client_win, ps->atoms->a_NET_WM_NAME, &strlst, &nstr))) { - log_debug("(%#010x): _NET_WM_NAME unset, falling back to WM_NAME.", + log_debug("(%#010x): _NET_WM_NAME unset, falling back to " + "WM_NAME.", w->client_win); if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_NAME, &strlst, &nstr)) { @@ -737,8 +761,8 @@ winmode_t win_calc_mode(const struct managed_win *w) { if (win_has_alpha(w)) { if (w->client_win == XCB_NONE) { - // This is a window not managed by the WM, and it has alpha, - // so it's transparent. No need to check WM frame. + // This is a window not managed by the WM, and it has + // alpha, so it's transparent. No need to check WM frame. return WMODE_TRANS; } // The WM window has alpha @@ -748,12 +772,12 @@ winmode_t win_calc_mode(const struct managed_win *w) { return WMODE_TRANS; } if (win_has_frame(w)) { - // The client window doesn't have alpha, but we have a WM frame - // window, which has alpha. + // The client window doesn't have alpha, but we have a WM + // frame window, which has alpha. return WMODE_FRAME_TRANS; } - // Although the WM window has alpha, the frame window has 0 size, so - // consider the window solid + // Although the WM window has alpha, the frame window has 0 size, + // so consider the window solid } if (w->frame_opacity != 1.0 && win_has_frame(w)) { @@ -769,8 +793,9 @@ winmode_t win_calc_mode(const struct managed_win *w) { * * The priority of opacity settings are: * - * inactive_opacity_override (if set, and unfocused) > _NET_WM_WINDOW_OPACITY (if set) > - * opacity-rules (if matched) > window type default opacity > active/inactive opacity + * inactive_opacity_override (if set, and unfocused) > _NET_WM_WINDOW_OPACITY (if + * set) > opacity-rules (if matched) > window type default opacity > + * active/inactive opacity * * @param ps current session * @param w struct _win object representing the window @@ -795,7 +820,8 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) { } else if (!safe_isnan(ps->o.wintype_option[w->window_type].opacity)) { opacity = ps->o.wintype_option[w->window_type].opacity; } else { - // Respect active_opacity only when the window is physically focused + // Respect active_opacity only when the window is physically + // focused if (win_is_focused_raw(ps, w)) opacity = ps->o.active_opacity; else if (!w->focused) @@ -831,7 +857,8 @@ bool win_should_dim(session_t *ps, const struct managed_win *w) { * Determine if a window should fade on opacity change. */ bool win_should_fade(session_t *ps, const struct managed_win *w) { - // To prevent it from being overwritten by last-paint value if the window is + // To prevent it from being overwritten by last-paint value if the window + // is if (w->fade_force != UNSET) { return w->fade_force; } @@ -875,7 +902,8 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new log_debug("Updating shadow property of window %#010x (%s) to %d", w->base.id, w->name, shadow_new); - // We don't handle property updates of non-visible windows until they are mapped. + // We don't handle property updates of non-visible windows until they are + // mapped. assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && w->state != WSTATE_UNMAPPING); @@ -897,10 +925,10 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new // Note: because the release and creation of the shadow images are // delayed. When multiple shadow changes happen in a row, without - // rendering phase between them, there could be a stale shadow image - // attached to the window even if w->shadow was previously false. And vice - // versa. So we check the STALE flag before asserting the existence of the - // shadow image. + // rendering phase between them, there could be a stale shadow + // image attached to the window even if w->shadow was previously + // false. And vice versa. So we check the STALE flag before + // asserting the existence of the shadow image. if (w->shadow) { // Mark the new extents as damaged if the shadow is added assert(!w->shadow_image || @@ -910,7 +938,8 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new win_extents(w, &extents); add_damage_from_win(ps, w); } else { - // Mark the old extents as damaged if the shadow is removed + // Mark the old extents as damaged if the shadow is + // removed assert(w->shadow_image || win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) || ps->o.legacy_backends); @@ -919,11 +948,12 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new // Delayed update of shadow image // By setting WIN_FLAGS_SHADOW_STALE, we ask win_process_flags to - // re-create or release the shaodw in based on whether w->shadow is set. + // re-create or release the shaodw in based on whether w->shadow + // is set. win_set_flags(w, WIN_FLAGS_SHADOW_STALE); - // Only set pending_updates if we are redirected. Otherwise change of a - // shadow won't have influence on whether we should redirect. + // Only set pending_updates if we are redirected. Otherwise change + // of a shadow won't have influence on whether we should redirect. ps->pending_updates = true; } @@ -1055,8 +1085,9 @@ win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_backgrou w->blur_background = blur_background_new; - // This damage might not be absolutely necessary (e.g. when the window is opaque), - // but blur_background changes should be rare, so this should be fine. + // This damage might not be absolutely necessary (e.g. when the window is + // opaque), but blur_background changes should be rare, so this should be + // fine. add_damage_from_win(ps, w); } @@ -1068,8 +1099,8 @@ win_set_fg_shader(session_t *ps, struct managed_win *w, struct shader_info *shad w->fg_shader = shader_new; - // A different shader might change how the window is drawn, these changes should - // be rare however, so this should be fine. + // A different shader might change how the window is drawn, these changes + // should be rare however, so this should be fine. add_damage_from_win(ps, w); } @@ -1088,7 +1119,8 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w) log_debug("Blur background disabled by wintypes"); blur_background_new = false; } else if (c2_match(ps, w, ps->o.blur_background_blacklist, NULL)) { - log_debug("Blur background disabled by blur-background-exclude"); + log_debug("Blur background disabled by " + "blur-background-exclude"); blur_background_new = false; } } @@ -1168,8 +1200,8 @@ void win_update_opacity_rule(session_t *ps, struct managed_win *w) { */ void win_on_factor_change(session_t *ps, struct managed_win *w) { log_debug("Window %#010x (%s) factor change", w->base.id, w->name); - // Focus needs to be updated first, as other rules might depend on the focused - // state of the window + // Focus needs to be updated first, as other rules might depend on the + // focused state of the window win_update_focused(ps, w); win_determine_shadow(ps, w); @@ -1207,7 +1239,8 @@ void win_on_win_size_change(session_t *ps, struct managed_win *w) { w->shadow_width = w->widthb + ps->o.shadow_radius * 2; w->shadow_height = w->heightb + ps->o.shadow_radius * 2; - // We don't handle property updates of non-visible windows until they are mapped. + // We don't handle property updates of non-visible windows until they are + // mapped. assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && w->state != WSTATE_UNMAPPING); @@ -1436,21 +1469,21 @@ struct win *add_win_top(session_t *ps, xcb_window_t id) { return add_win(ps, id, &ps->window_stack); } -/// Insert a new window above window with id `below`, if there is no window, add to top -/// New window will be in unmapped state +/// Insert a new window above window with id `below`, if there is no window, add +/// to top New window will be in unmapped state struct win *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below) { struct win *w = NULL; HASH_FIND_INT(ps->windows, &below, w); if (!w) { if (!list_is_empty(&ps->window_stack)) { - // `below` window is not found even if the window stack is not - // empty + // `below` window is not found even if the window stack is + // not empty return NULL; } return add_win_top(ps, id); } else { - // we found something from the hash table, so if the stack is empty, - // we are in an inconsistent state. + // we found something from the hash table, so if the stack is + // empty, we are in an inconsistent state. assert(!list_is_empty(&ps->window_stack)); return add_win(ps, id, w->stack_neighbour.prev); } @@ -1477,7 +1510,8 @@ struct win *fill_win(session_t *ps, struct win *w) { .in_openclose = true, // set to false after first map is done, // true here because window is just created .reg_ignore_valid = false, // set to true when damaged - .flags = WIN_FLAGS_IMAGES_NONE, // updated by property/attributes/etc + .flags = WIN_FLAGS_IMAGES_NONE, // updated by + // property/attributes/etc // change .stale_props = NULL, .stale_props_capacity = 0, @@ -1558,8 +1592,9 @@ struct win *fill_win(session_t *ps, struct win *w) { auto duplicated_win = find_managed_win(ps, w->id); if (duplicated_win) { - log_debug("Window %#010x (recorded name: %s) added multiple times", w->id, - duplicated_win->name); + log_debug("Window %#010x (recorded name: %s) added multiple " + "times", + w->id, duplicated_win->name); return &duplicated_win->base; } @@ -1571,14 +1606,15 @@ struct win *fill_win(session_t *ps, struct win *w) { // Failed to get window attributes or geometry probably means // the window is gone already. Unviewable means the window is // already reparented elsewhere. - // BTW, we don't care about Input Only windows, except for stacking - // proposes, so we need to keep track of them still. + // BTW, we don't care about Input Only windows, except for + // stacking proposes, so we need to keep track of them still. free(a); return w; } if (a->_class == XCB_WINDOW_CLASS_INPUT_ONLY) { - // No need to manage this window, but we still keep it on the window stack + // No need to manage this window, but we still keep it on the + // window stack w->managed = false; free(a); return w; @@ -1648,8 +1684,8 @@ struct win *fill_win(session_t *ps, struct win *w) { assert(replaced == w); free(w); - // Set all the stale flags on this new window, so it's properties will get updated - // when it's mapped + // Set all the stale flags on this new window, so it's properties will get + // updated when it's mapped win_set_flags(new, WIN_FLAGS_CLIENT_STALE | WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE | WIN_FLAGS_PROPERTY_STALE | WIN_FLAGS_FACTOR_CHANGED); @@ -1685,8 +1721,8 @@ static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_wind // gets mapped before parent, or when the window is a waypoint clear_cache_win_leaders(ps); - // Update the old and new window group and active_leader if the window - // could affect their state. + // Update the old and new window group and active_leader if the + // window could affect their state. xcb_window_t cache_leader = win_get_leader(ps, w); if (win_is_focused_raw(ps, w) && cache_leader_old != cache_leader) { ps->active_leader = cache_leader; @@ -1733,7 +1769,8 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int if (!(w->cache_leader = w->leader)) w->cache_leader = w->client_win; - // If the leader of this window isn't itself, look for its ancestors + // If the leader of this window isn't itself, look for its + // ancestors if (w->cache_leader && w->cache_leader != w->client_win) { auto wp = find_toplevel(ps, w->cache_leader); if (wp) { @@ -1881,7 +1918,8 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) { w->bounding_shaped = win_bounding_shaped(ps, w->base.id); } - // We don't handle property updates of non-visible windows until they are mapped. + // We don't handle property updates of non-visible windows until they are + // mapped. assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && w->state != WSTATE_UNMAPPING); @@ -1922,8 +1960,8 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) { // We think the top left of the border is the origin pixman_region32_translate(&br, w->g.border_width, w->g.border_width); - // Intersect the bounding region we got with the window rectangle, to - // make sure the bounding region is not bigger than the window + // Intersect the bounding region we got with the window rectangle, + // to make sure the bounding region is not bigger than the window // rectangle pixman_region32_intersect(&w->bounding_shape, &w->bounding_shape, &br); pixman_region32_fini(&br); @@ -1980,7 +2018,8 @@ void win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t for (int i = 0; i < 4; i++) { if (prop.c32[i] > (uint32_t)INT_MAX) { log_warn("Your window manager sets a absurd " - "_NET_FRAME_EXTENTS value (%u), ignoring it.", + "_NET_FRAME_EXTENTS value (%u), " + "ignoring it.", prop.c32[i]); memset(extents, 0, sizeof(extents)); break; @@ -2080,11 +2119,12 @@ static void destroy_win_finish(session_t *ps, struct win *w) { auto mw = (struct managed_win *)w; if (mw->state != WSTATE_UNMAPPED) { - // Only UNMAPPED state has window resources freed, otherwise - // we need to call unmap_win_finish to free them. - // XXX actually we unmap_win_finish only frees the rendering - // resources, we still need to call free_win_res. will fix - // later. + // Only UNMAPPED state has window resources freed, + // otherwise we need to call unmap_win_finish to free + // them. + // XXX actually we unmap_win_finish only frees the + // rendering resources, we still need to call free_win_res. + // will fix later. unmap_win_finish(ps, mw); } @@ -2107,11 +2147,12 @@ static void destroy_win_finish(session_t *ps, struct win *w) { } if (mw == ps->active_win) { - // Usually, the window cannot be the focused at destruction. - // FocusOut should be generated before the window is destroyed. We - // do this check just to be completely sure we don't have dangling - // references. - log_debug("window %#010x (%s) is destroyed while being focused", + // Usually, the window cannot be the focused at + // destruction. FocusOut should be generated before the + // window is destroyed. We do this check just to be + // completely sure we don't have dangling references. + log_debug("window %#010x (%s) is destroyed while being " + "focused", w->id, mw->name); ps->active_win = NULL; } @@ -2144,11 +2185,13 @@ static inline void restack_win(session_t *ps, struct win *w, struct list_node *n } if (mw) { - // This invalidates all reg_ignore below the new stack position of `w` + // This invalidates all reg_ignore below the new stack position of + // `w` mw->reg_ignore_valid = false; rc_region_unref(&mw->reg_ignore); - // This invalidates all reg_ignore below the old stack position of `w` + // This invalidates all reg_ignore below the old stack position of + // `w` auto next_w = win_stack_find_next_managed(ps, &w->stack_neighbour); if (next_w) { next_w->reg_ignore_valid = false; @@ -2230,39 +2273,42 @@ bool destroy_win_start(session_t *ps, struct win *w) { log_debug("Destroying %#010x \"%s\", managed = %d", w->id, (w->managed ? mw->name : NULL), w->managed); - // Delete destroyed window from the hash table, even though the window might still - // be rendered for a while. We need to make sure future window with the same - // window id won't confuse us. Keep the window in the window stack if it's managed - // and mapped, since we might still need to render it (e.g. fading out). Window - // will be removed from the stack when it finishes destroying. + // Delete destroyed window from the hash table, even though the window + // might still be rendered for a while. We need to make sure future window + // with the same window id won't confuse us. Keep the window in the window + // stack if it's managed and mapped, since we might still need to render + // it (e.g. fading out). Window will be removed from the stack when it + // finishes destroying. HASH_DEL(ps->windows, w); if (!w->managed || mw->state == WSTATE_UNMAPPED) { - // Window is already unmapped, or is an unmanged window, just destroy it + // Window is already unmapped, or is an unmanged window, just + // destroy it destroy_win_finish(ps, w); return true; } if (w->managed) { // Clear IMAGES_STALE flags since the window is destroyed: Clear - // PIXMAP_STALE as there is no pixmap available anymore, so STALE doesn't - // make sense. - // XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed window - // doesn't work leading to an inconsistent state where the shadow is - // refreshed but the flags are stuck in STALE. - // Do this before changing the window state to destroying + // PIXMAP_STALE as there is no pixmap available anymore, so STALE + // doesn't make sense. + // XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed + // window doesn't work leading to an inconsistent state where the + // shadow is refreshed but the flags are stuck in STALE. Do this + // before changing the window state to destroying win_clear_flags(mw, WIN_FLAGS_IMAGES_STALE); - // If size/shape/position information is stale, win_process_update_flags - // will update them and add the new window extents to damage. Since the - // window has been destroyed, we cannot get the complete information at - // this point, so we just add what we currently have to the damage. + // If size/shape/position information is stale, + // win_process_update_flags will update them and add the new + // window extents to damage. Since the window has been destroyed, + // we cannot get the complete information at this point, so we + // just add what we currently have to the damage. if (win_check_flags_any(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) { add_damage_from_win(ps, mw); } - // Clear some flags about stale window information. Because now the window - // is destroyed, we can't update them anyway. + // Clear some flags about stale window information. Because now + // the window is destroyed, we can't update them anyway. win_clear_flags(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE | WIN_FLAGS_PROPERTY_STALE | WIN_FLAGS_FACTOR_CHANGED | WIN_FLAGS_CLIENT_STALE); @@ -2309,7 +2355,8 @@ void unmap_win_start(session_t *ps, struct managed_win *w) { // Clear the pending map as this window is now unmapped win_clear_flags(w, WIN_FLAGS_MAPPED); } else { - log_warn("Trying to unmapping an already unmapped window %#010x " + log_warn("Trying to unmapping an already unmapped window " + "%#010x " "\"%s\"", w->base.id, w->name); assert(false); @@ -2333,10 +2380,10 @@ void unmap_win_start(session_t *ps, struct managed_win *w) { #endif if (!ps->redirected || !was_damaged) { - // If we are not redirected, we skip fading because we aren't rendering - // anything anyway. - // If the window wasn't ever damaged, it shouldn't be painted either. But - // a fading out window is always painted, so we have to skip fading here. + // If we are not redirected, we skip fading because we aren't + // rendering anything anyway. If the window wasn't ever damaged, + // it shouldn't be painted either. But a fading out window is + // always painted, so we have to skip fading here. CHECK(!win_skip_fading(ps, w)); } } @@ -2395,14 +2442,16 @@ void win_update_screen(int nscreens, region_t *screens, struct managed_win *w) { if (e->x1 <= w->g.x && e->y1 <= w->g.y && e->x2 >= w->g.x + w->widthb && e->y2 >= w->g.y + w->heightb) { w->xinerama_scr = i; - log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen %d " + log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen " + "%d " "(%dx%d+%dx%d)", w->base.id, w->name, w->g.x, w->g.y, w->widthb, w->heightb, i, e->x1, e->y1, e->x2 - e->x1, e->y2 - e->y1); return; } } - log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any screen", + log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any " + "screen", w->base.id, w->name, w->g.x, w->g.y, w->g.width, w->g.height); } @@ -2427,23 +2476,24 @@ void map_win_start(session_t *ps, struct managed_win *w) { if (w->state == WSTATE_UNMAPPING) { CHECK(!win_skip_fading(ps, w)); - // We skipped the unmapping process, the window was rendered, now it is - // not anymore. So we need to mark the then unmapping window as damaged. + // We skipped the unmapping process, the window was rendered, now + // it is not anymore. So we need to mark the then unmapping window + // as damaged. // - // Solves problem when, for example, a window is unmapped then mapped in a - // different location + // Solves problem when, for example, a window is unmapped then + // mapped in a different location add_damage_from_win(ps, w); assert(w); } assert(w->state == WSTATE_UNMAPPED); - // Rant: window size could change after we queried its geometry here and before - // we get its pixmap. Later, when we get back to the event processing loop, we - // will get the notification about size change from Xserver and try to refresh the - // pixmap, while the pixmap is actually already up-to-date (i.e. the notification - // is stale). There is basically no real way to prevent this, aside from grabbing - // the server. + // Rant: window size could change after we queried its geometry here and + // before we get its pixmap. Later, when we get back to the event + // processing loop, we will get the notification about size change from + // Xserver and try to refresh the pixmap, while the pixmap is actually + // already up-to-date (i.e. the notification is stale). There is basically + // no real way to prevent this, aside from grabbing the server. // XXX Can we assume map_state is always viewable? w->a.map_state = XCB_MAP_STATE_VIEWABLE; @@ -2463,9 +2513,9 @@ void map_win_start(session_t *ps, struct managed_win *w) { w->opacity, w->opacity_target); // Cannot set w->ever_damaged = false here, since window mapping could be - // delayed, so a damage event might have already arrived before this function - // is called. But this should be unnecessary in the first place, since - // ever_damaged is set to false in unmap_win_finish anyway. + // delayed, so a damage event might have already arrived before this + // function is called. But this should be unnecessary in the first place, + // since ever_damaged is set to false in unmap_win_finish anyway. // Sets the WIN_FLAGS_IMAGES_STALE flag so later in the critical section // the window's image will be bound @@ -2600,17 +2650,17 @@ struct managed_win *find_toplevel(session_t *ps, xcb_window_t id) { * @return struct _win object of the found window, NULL if not found */ struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wid) { - // TODO(yshui) this should probably be an "update tree", then find_toplevel. - // current approach is a bit more "racy", as the server state might be ahead of - // our state + // TODO(yshui) this should probably be an "update tree", then + // find_toplevel. current approach is a bit more "racy", as the server + // state might be ahead of our state struct win *w = NULL; // We traverse through its ancestors to find out the frame - // Using find_win here because if we found a unmanaged window we know about, we - // can stop early. + // Using find_win here because if we found a unmanaged window we know + // about, we can stop early. while (wid && wid != ps->root && !(w = find_win(ps, wid))) { - // xcb_query_tree probably fails if you run picom when X is somehow - // initializing (like add it in .xinitrc). In this case + // xcb_query_tree probably fails if you run picom when X is + // somehow initializing (like add it in .xinitrc). In this case // just leave it alone. auto reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, wid), NULL); if (reply == NULL) { @@ -2776,7 +2826,8 @@ bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win * } /** - * Check if a window is focused, without using any focus rules or forced focus settings + * Check if a window is focused, without using any focus rules or forced focus + * settings */ bool win_is_focused_raw(const session_t *ps, const struct managed_win *w) { return w->a.map_state == XCB_MAP_STATE_VIEWABLE && ps->active_win == w; diff --git a/src/win.h b/src/win.h index fb5137c0..8b79cb14 100644 --- a/src/win.h +++ b/src/win.h @@ -289,6 +289,7 @@ struct managed_win { /// section void win_process_update_flags(session_t *ps, struct managed_win *w); void win_process_image_flags(session_t *ps, struct managed_win *w); +bool win_bind_mask(struct backend_base *b, struct managed_win *w); /// Bind a shadow to the window, with color `c` and shadow kernel `kernel` bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c, struct backend_shadow_context *kernel); From a29caeaf3d5d8cf7ada1b7c076bff91c830c11dd Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 25 Aug 2022 18:00:25 +0100 Subject: [PATCH 08/11] ackend: gl: implement shadow_from_mask Signed-off-by: Yuxuan Shui --- src/backend/gl/blur.c | 4 +- src/backend/gl/gl_common.c | 169 +++++++++++++++++++++++++++++++++++++ src/backend/gl/gl_common.h | 18 +++- src/backend/gl/glx.c | 7 +- src/backend/gl/shaders.c | 10 +++ 5 files changed, 201 insertions(+), 7 deletions(-) diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c index 271a17ab..b73aeeeb 100644 --- a/src/backend/gl/blur.c +++ b/src/backend/gl/blur.c @@ -379,10 +379,10 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, } glBindFramebuffer(GL_FRAMEBUFFER, 0); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDeleteBuffers(4, bo); diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 3e7c5c8a..ac9de95d 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -881,6 +881,17 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); + gd->shadow_shader.prog = + gl_create_program_from_str(present_vertex_shader, shadow_colorization_frag); + gd->shadow_shader.uniform_color = + glGetUniformLocationChecked(gd->shadow_shader.prog, "color"); + pml = glGetUniformLocationChecked(gd->shadow_shader.prog, "projection"); + glUseProgram(gd->shadow_shader.prog); + glUniform1i(glGetUniformLocationChecked(gd->shadow_shader.prog, "tex"), 0); + glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + glUseProgram(0); + glBindFragDataLocation(gd->shadow_shader.prog, 0, "out_color"); + gd->brightness_shader.prog = gl_create_program_from_str(interpolating_vert, interpolating_frag); if (!gd->brightness_shader.prog) { @@ -919,6 +930,7 @@ bool gl_init(struct gl_data *gd, session_t *ps) { gd->is_nvidia = false; } gd->has_robustness = gl_has_extension("GL_ARB_robustness"); + gl_check_err(); return true; } @@ -1150,6 +1162,163 @@ bool gl_set_image_property(backend_t *backend_data, enum image_properties prop, return true; } +struct gl_shadow_context { + double radius; + void *blur_context; +}; + +struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius) { + auto ctx = ccalloc(1, struct gl_shadow_context); + ctx->radius = radius; + + struct dual_kawase_blur_args args = { + .size = (int)radius, + .strength = 0, + }; + ctx->blur_context = gl_create_blur_context(base, BLUR_METHOD_DUAL_KAWASE, &args); + return (struct backend_shadow_context *)ctx; +} + +void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx) { + auto ctx_ = (struct gl_shadow_context *)ctx; + gl_destroy_blur_context(base, (struct backend_blur_context *)ctx_->blur_context); + free(ctx_); +} + +void *gl_shadow_from_mask(backend_t *base, void *mask, + struct backend_shadow_context *sctx, struct color color) { + log_debug("Create shadow from mask"); + auto gd = (struct gl_data *)base; + auto img = (struct backend_image *)mask; + auto inner = (struct gl_texture *)img->inner; + auto gsctx = (struct gl_shadow_context *)sctx; + int radius = (int)gsctx->radius; + + auto new_inner = ccalloc(1, struct gl_texture); + new_inner->width = inner->width + radius * 2; + new_inner->height = inner->height + radius * 2; + new_inner->texture = gl_new_texture(GL_TEXTURE_2D); + new_inner->has_alpha = inner->has_alpha; + new_inner->y_inverted = false; + auto new_img = default_new_backend_image(new_inner->width, new_inner->height); + new_img->inner = (struct backend_image_inner_base *)new_inner; + new_img->inner->refcount = 1; + + // Render the mask to a texture, so inversion and corner radius can be + // applied. + auto source_texture = gl_new_texture(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, source_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, new_inner->height, 0, + GL_RED, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + source_texture, 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + if (img->color_inverted) { + // If the mask is inverted, clear the source_texture to white, so the + // "outside" of the mask would be correct + glClearColor(1, 1, 1, 1); + } else { + glClearColor(0, 0, 0, 1); + } + glClear(GL_COLOR_BUFFER_BIT); + { + // clang-format off + // interleaved vertex coordinates and texture coordinates + GLint coords[] = {radius , radius , 0 , 0, + radius + inner->width, radius , inner->width, 0, + radius + inner->width, radius + inner->height, inner->width, inner->height, + radius , radius + inner->height, 0 , inner->height,}; + // clang-format on + GLuint indices[] = {0, 1, 2, 2, 3, 0}; + _gl_compose(base, mask, fbo, NULL, (coord_t){0}, coords, indices, 1); + } + + gl_check_err(); + + glActiveTexture(GL_TEXTURE0); + auto tmp_texture = gl_new_texture(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, tmp_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, new_inner->height, 0, + GL_RED, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + tmp_texture, 0); + + region_t reg_blur; + pixman_region32_init_rect(®_blur, 0, 0, (unsigned int)new_inner->width, + (unsigned int)new_inner->height); + // gl_blur expects reg_blur to be in X coordinate system (i.e. y flipped), but we + // are covering the whole texture so we don't need to worry about that. + gl_blur_impl(1.0, gsctx->blur_context, NULL, (coord_t){0}, ®_blur, NULL, + source_texture, + (geometry_t){.width = new_inner->width, .height = new_inner->height}, + fbo, gd->default_mask_texture); + pixman_region32_fini(®_blur); + + // Colorize the shadow with color. + log_debug("Colorize shadow"); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, new_inner->texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, new_inner->width, new_inner->height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + new_inner->texture, 0); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + + glBindTexture(GL_TEXTURE_2D, tmp_texture); + glUseProgram(gd->shadow_shader.prog); + glUniform4f(gd->shadow_shader.uniform_color, (GLfloat)color.red, + (GLfloat)color.green, (GLfloat)color.blue, (GLfloat)color.alpha); + + // clang-format off + GLuint indices[] = {0, 1, 2, 2, 3, 0}; + GLint coord[] = {0 , 0 , + new_inner->width , 0 , + new_inner->width , new_inner->height, + 0 , new_inner->height,}; + // clang-format on + + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + GLuint bo[2]; + glGenBuffers(2, bo); + glBindBuffer(GL_ARRAY_BUFFER, bo[0]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 8, coord, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, + GL_STATIC_DRAW); + + glEnableVertexAttribArray(vert_coord_loc); + glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL); + + glDisableVertexAttribArray(vert_coord_loc); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDeleteBuffers(2, bo); + + glDeleteTextures(1, (GLuint[]){source_texture, tmp_texture}); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + gl_check_err(); + return new_img; +} + enum device_status gl_device_status(backend_t *base) { auto gd = (struct gl_data *)base; if (!gd->has_robustness) { diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 743d8b51..2c288529 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -53,6 +53,11 @@ typedef struct { GLuint prog; } gl_brightness_shader_t; +typedef struct { + GLuint prog; + GLint uniform_color; +} gl_shadow_shader_t; + // Program and uniforms for blur shader typedef struct { GLuint prog; @@ -98,6 +103,7 @@ struct gl_data { gl_win_shader_t *default_shader; gl_brightness_shader_t brightness_shader; gl_fill_shader_t fill_shader; + gl_shadow_shader_t shadow_shader; GLuint back_texture, back_fbo; GLuint present_prog; @@ -155,8 +161,16 @@ void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visi bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible); +bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, + coord_t mask_dst, const region_t *reg_blur, + const region_t *reg_visible attr_unused, GLuint source_texture, + geometry_t source_size, GLuint target_fbo, GLuint default_mask); void *gl_create_blur_context(backend_t *base, enum blur_method, void *args); void gl_destroy_blur_context(backend_t *base, void *ctx); +struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius); +void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx); +void *gl_shadow_from_mask(backend_t *base, void *mask, + struct backend_shadow_context *sctx, struct color color); void gl_get_blur_size(void *blur_context, int *width, int *height); void gl_fill(backend_t *base, struct color, const region_t *clip); @@ -272,5 +286,5 @@ static const GLuint vert_in_texcoord_loc = 1; #define QUOTE(...) #__VA_ARGS__ extern const char vertex_shader[], copy_with_mask_frag[], masking_glsl[], dummy_frag[], - fill_frag[], fill_vert[], interpolating_frag[], interpolating_vert[], - win_shader_glsl[], win_shader_default[], present_vertex_shader[]; + fill_frag[], fill_vert[], interpolating_frag[], interpolating_vert[], win_shader_glsl[], + win_shader_default[], present_vertex_shader[], shadow_colorization_frag[]; diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index 26aaa49b..109bec94 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -536,9 +536,10 @@ struct backend_operations glx_ops = { .is_image_transparent = default_is_image_transparent, .present = glx_present, .buffer_age = glx_buffer_age, - .create_shadow_context = default_create_shadow_context, - .destroy_shadow_context = default_destroy_shadow_context, - .render_shadow = default_backend_render_shadow, + .create_shadow_context = gl_create_shadow_context, + .destroy_shadow_context = gl_destroy_shadow_context, + .render_shadow = backend_render_shadow_from_mask, + .shadow_from_mask = gl_shadow_from_mask, .make_mask = gl_make_mask, .fill = gl_fill, .create_blur_context = gl_create_blur_context, diff --git a/src/backend/gl/shaders.c b/src/backend/gl/shaders.c index 7439cd33..23866894 100644 --- a/src/backend/gl/shaders.c +++ b/src/backend/gl/shaders.c @@ -174,4 +174,14 @@ const char vertex_shader[] = GLSL(330, texcoord = in_texcoord + texorig; } ); +const char shadow_colorization_frag[] = GLSL(330, + uniform vec4 color; + uniform sampler2D tex; + in vec2 texcoord; + out vec4 out_color; + void main() { + vec4 c = texelFetch(tex, ivec2(texcoord), 0); + out_color = c.r * color; + } +); // clang-format on From 8c14d5354cf4d044f7061ecfd9d29a4c13fe2c69 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 26 Aug 2022 05:28:19 +0100 Subject: [PATCH 09/11] backend: gl: fix mask being inverted Signed-off-by: Yuxuan Shui --- src/backend/gl/gl_common.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index ac9de95d..e7416083 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -351,6 +351,9 @@ static GLuint gl_average_texture_color(backend_t *base, struct backend_image *im static void _gl_compose(backend_t *base, struct backend_image *img, GLuint target, struct backend_image *mask, coord_t mask_offset, GLint *coord, GLuint *indices, int nrects) { + // FIXME(yshui) breaks when `mask` and `img` doesn't have the same y_inverted + // value. but we don't ever hit this problem because all of our + // images and masks are y_inverted. auto gd = (struct gl_data *)base; auto inner = (struct gl_texture *)img->inner; auto mask_texture = @@ -1199,7 +1202,7 @@ void *gl_shadow_from_mask(backend_t *base, void *mask, new_inner->height = inner->height + radius * 2; new_inner->texture = gl_new_texture(GL_TEXTURE_2D); new_inner->has_alpha = inner->has_alpha; - new_inner->y_inverted = false; + new_inner->y_inverted = true; auto new_img = default_new_backend_image(new_inner->width, new_inner->height); new_img->inner = (struct backend_image_inner_base *)new_inner; new_img->inner->refcount = 1; From 5d2f8d7456d85df6c5ae0f36184337003566b371 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 26 Aug 2022 05:28:57 +0100 Subject: [PATCH 10/11] backend: gl: fix visible seam in shadow at edge of windows Signed-off-by: Yuxuan Shui --- src/backend/gl/shaders.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/gl/shaders.c b/src/backend/gl/shaders.c index 23866894..4a18e628 100644 --- a/src/backend/gl/shaders.c +++ b/src/backend/gl/shaders.c @@ -71,7 +71,7 @@ const char masking_glsl[] = GLSL(330, float dist = mask_rectangle_sdf(maskcoord - mask_size / 2.0f, inner_size / 2.0f) - mask_corner_radius; if (dist > 0.0f) { - mask.r = (1.0f - clamp(dist, 0.0f, 1.0f)); + mask.r *= (1.0f - clamp(dist, 0.0f, 1.0f)); } } if (mask_inverted) { From 483aa4347cd2961355f5fdb7f325b7526dc26e7f Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 26 Aug 2022 10:41:13 +0100 Subject: [PATCH 11/11] backend: allocate mask only when necessary Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/backend/backend.c b/src/backend/backend.c index 7d40e1cb..1a107a2b 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -207,9 +207,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { auto reg_bound_no_corner = win_get_bounding_shape_global_without_corners_by_val(w); - if (!w->mask_image) { - // TODO(yshui) only allocate a mask if the window is shaped or has - // rounded corners. + if (!w->mask_image && (w->bounding_shaped || w->corner_radius != 0)) { win_bind_mask(ps->backend_data, w); }