diff --git a/src/backend/backend.c b/src/backend/backend.c index db0bb2c9..1a107a2b 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -206,21 +206,9 @@ 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}); + if (!w->mask_image && (w->bounding_shaped || w->corner_radius != 0)) { + win_bind_mask(ps->backend_data, w); } // The clip region for the current window, in global/target coordinates @@ -455,8 +443,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 +475,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/backend/backend.h b/src/backend/backend.h index b11ca65b..191e8146 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,31 @@ struct backend_operations { void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned); - /// Create a shadow image based on the parameters + /// 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. 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. 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. The shadow produced + /// shoule be consistent with `render_shadow`. + /// + /// 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..d6fcce21 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,34 @@ default_backend_render_shadow(backend_t *backend_data, int width, int height, 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 = + (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..c72a1686 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -62,9 +62,18 @@ 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, + struct backend_shadow_context *sctx, struct color color); + +/// Implement `render_shadow` with `shadow_from_mask`. void * -default_backend_render_shadow(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a); +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); + +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/blur.c b/src/backend/gl/blur.c new file mode 100644 index 00000000..b73aeeeb --- /dev/null +++ b/src/backend/gl/blur.c @@ -0,0 +1,900 @@ +#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(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 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 = 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]; + 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, default_mask); + + 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, target_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(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; + 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 = source_texture; + tex_width = source_size.width; + tex_height = source_size.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, default_mask); + + 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_DRAW_FRAMEBUFFER, target_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_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 (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 = 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; + 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); + 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->y1}, + extent_height, 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(opacity, bctx, extent_resized, mask, mask_dst, + vao, vao_nelems, source_texture, source_size, + target_fbo, default_mask); + } else { + 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); + 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); + glBindVertexArray(0); + glDeleteVertexArrays(2, vao); + glUseProgram(0); + + free(indices); + free(coord); + free(indices_resized); + free(coord_resized); + + gl_check_err(); + 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); + } + + 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..e7416083 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); @@ -387,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 = @@ -518,17 +485,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 -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 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 @@ -605,395 +571,14 @@ 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); 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 +633,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 +760,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); @@ -1957,6 +884,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) { @@ -1995,6 +933,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; } @@ -2226,6 +1165,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 = 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; + + // 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) { @@ -2233,7 +1329,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 8066c102..2c288529 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); @@ -52,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; @@ -97,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; @@ -117,9 +124,14 @@ 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 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); 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); @@ -149,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); @@ -258,3 +278,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[], shadow_colorization_frag[]; diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index e0cc368f..109bec94 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -536,7 +536,10 @@ struct backend_operations glx_ops = { .is_image_transparent = default_is_image_transparent, .present = glx_present, .buffer_age = glx_buffer_age, - .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 new file mode 100644 index 00000000..4a18e628 --- /dev/null +++ b/src/backend/gl/shaders.c @@ -0,0 +1,187 @@ +#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; + } +); +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 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 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..a96e9f30 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,14 +126,14 @@ 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); } - 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); } 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..d97597e3 100644 --- a/src/win.c +++ b/src/win.c @@ -345,14 +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 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); + 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); @@ -366,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)); @@ -384,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); @@ -441,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); @@ -457,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); @@ -467,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); } @@ -502,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); @@ -534,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 @@ -556,7 +578,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); } } @@ -616,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)) { @@ -738,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 @@ -749,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)) { @@ -770,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 @@ -796,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) @@ -832,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; } @@ -876,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); @@ -898,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 || @@ -911,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); @@ -920,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; } @@ -1056,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); } @@ -1069,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); } @@ -1089,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; } } @@ -1169,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); @@ -1208,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); @@ -1437,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); } @@ -1478,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, @@ -1559,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; } @@ -1572,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; @@ -1649,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); @@ -1686,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; @@ -1734,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) { @@ -1882,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); @@ -1923,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); @@ -1981,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; @@ -2081,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); } @@ -2108,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; } @@ -2145,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; @@ -2231,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); @@ -2310,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); @@ -2334,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)); } } @@ -2396,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); } @@ -2428,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; @@ -2464,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 @@ -2601,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) { @@ -2777,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 ad5fe2fc..8b79cb14 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 @@ -287,9 +289,10 @@ 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 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.