diff --git a/src/backend/backend.c b/src/backend/backend.c index 796e7be..db0bb2c 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -246,6 +246,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { * is transparent or not. for now we will just rely on the force_win_blend * option */ auto real_win_mode = w->mode; + coord_t window_coord = {.x = w->g.x, .y = w->g.y}; if (w->blur_background && (ps->o.force_win_blend || real_win_mode == WMODE_TRANS || @@ -289,7 +290,8 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // We need to blur the bounding shape of the window // (reg_paint_in_bound = reg_bound \cap reg_paint) ps->backend_data->ops->blur( - ps->backend_data, blur_opacity, ps->backend_blur_context, + ps->backend_data, blur_opacity, + ps->backend_blur_context, w->mask_image, window_coord, ®_paint_in_bound, ®_visible); } else { // Window itself is solid, we only need to blur the frame @@ -308,14 +310,13 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { pixman_region32_intersect(®_blur, ®_blur, ®_visible); } - ps->backend_data->ops->blur(ps->backend_data, blur_opacity, - ps->backend_blur_context, - ®_blur, ®_visible); + ps->backend_data->ops->blur( + ps->backend_data, blur_opacity, ps->backend_blur_context, + w->mask_image, window_coord, ®_blur, ®_visible); pixman_region32_fini(®_blur); } } - coord_t window_coord = {.x = w->g.x, .y = w->g.y}; // Draw shadow on target if (w->shadow) { assert(!(w->flags & WIN_FLAGS_SHADOW_NONE)); diff --git a/src/backend/backend.h b/src/backend/backend.h index 027a4e2..b11ca65 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -185,9 +185,12 @@ struct backend_operations { void (*fill)(backend_t *backend_data, struct color, const region_t *clip); /// Blur a given region of the rendering buffer. - bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, - const region_t *reg_blur, const region_t *reg_visible) - attr_nonnull(1, 3, 4, 5); + /// + /// The blur is limited by `mask`. `mask_dst` specifies the top left corner of the + /// mask is. + bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, void *mask, + coord_t mask_dst, const region_t *reg_blur, + const region_t *reg_visible) attr_nonnull(1, 3, 4, 6, 7); /// Update part of the back buffer with the rendering buffer, then present the /// back buffer onto the target window (if not back buffered, update part of the diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 6064791..c7e0e2c 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -75,7 +75,8 @@ void dummy_fill(struct backend_base *backend_data attr_unused, struct color c at } bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused, - void *blur_ctx attr_unused, const region_t *reg_blur attr_unused, + void *blur_ctx attr_unused, void *mask attr_unused, + coord_t mask_dst attr_unused, const region_t *reg_blur attr_unused, const region_t *reg_visible attr_unused) { return true; } diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 0cd70c2..07c75be 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -494,6 +494,11 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe glDeleteVertexArrays(1, &vao); // Cleanup + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDrawBuffer(GL_BACK); @@ -511,7 +516,7 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe /// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates /// @param[in] nrects, rects rectangles -/// @param[in] dst_x, dst_y origin of the OpenGL texture, affect the calculated texture +/// @param[in] image_dst origin of the OpenGL texture, affect the calculated texture /// coordinates /// @param[in] texture_height height of the OpenGL texture /// @param[in] root_height height of the back buffer @@ -612,7 +617,8 @@ void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask * Blur contents in a particular region. */ bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, - const GLuint vao[2], const int vao_nelems[2]) { + 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; @@ -641,11 +647,20 @@ bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *ex 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; @@ -668,7 +683,18 @@ bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *ex glUniform1f(p->uniform_opacity, 1.0F); } else { // last pass, draw directly into the back buffer, with origin - // regions + // 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); @@ -689,6 +715,7 @@ bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *ex } 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; @@ -764,7 +791,15 @@ bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_ // 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]); @@ -777,6 +812,18 @@ bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_ 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); @@ -794,8 +841,8 @@ bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_ return true; } -bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blur, - const region_t *reg_visible attr_unused) { +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; @@ -904,12 +951,17 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu 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, vao, vao_nelems); + 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, vao, vao_nelems); + 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); @@ -1005,6 +1057,15 @@ static const char dummy_frag[] = GLSL(330, } ); +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() { @@ -1204,6 +1265,7 @@ void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) { gl_check_err(); } +const char *masking_glsl; /** * Initialize GL blur filters. */ @@ -1247,11 +1309,12 @@ bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection, 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; + out_color = sum / float(%.7g) * opacity * mask_factor(); } ); static const char *FRAG_SHADER_BLUR_ADD = QUOTE( @@ -1342,7 +1405,9 @@ bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection, free(shader_body); // Build program - pass->prog = gl_create_program_from_str(vertex_shader, shader_str); + 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."); @@ -1354,6 +1419,14 @@ bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection, // 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 @@ -1370,10 +1443,16 @@ bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection, // 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_str(vertex_shader, dummy_frag); + 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); @@ -1495,6 +1574,7 @@ bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection, 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); @@ -1506,7 +1586,7 @@ bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection, 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; + out_color = sum / 12.0 * opacity * mask_factor(); } ); // clang-format on @@ -1521,7 +1601,9 @@ bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection, CHECK((size_t)real_shader_len < shader_len); // Build program - up_pass->prog = gl_create_program_from_str(vertex_shader, shader_str); + 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."); @@ -1533,6 +1615,12 @@ bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection, // 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"); @@ -1633,6 +1721,34 @@ void gl_get_blur_size(void *blur_context, int *width, int *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; @@ -1643,11 +1759,6 @@ const char *win_shader_glsl = GLSL(330, uniform sampler2D tex; uniform sampler2D brightness; uniform float max_brightness; - - uniform sampler2D mask_tex; - uniform vec2 mask_offset; - uniform float mask_corner_radius; - uniform bool mask_inverted; // Signed distance field for rectangle center at (0, 0), with size of // half_size * 2 float rectangle_sdf(vec2 point, vec2 half_size) { @@ -1694,23 +1805,10 @@ const char *win_shader_glsl = GLSL(330, } vec4 window_shader(); + float mask_factor(); void main() { - 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 = 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; - } - gl_FragColor = window_shader() * mask.r; + gl_FragColor = window_shader() * mask_factor(); } ); @@ -1739,7 +1837,7 @@ void *gl_create_window_shader(backend_t *backend_data attr_unused, const char *s auto win_shader = (gl_win_shader_t *)ccalloc(1, gl_win_shader_t); const char *vert_shaders[2] = {vertex_shader, NULL}; - const char *frag_shaders[3] = {win_shader_glsl, source, NULL}; + const char *frag_shaders[4] = {win_shader_glsl, masking_glsl, source, NULL}; if (!gl_win_shader_from_stringv(vert_shaders, frag_shaders, win_shader)) { free(win_shader); diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index ffd2639..8066c10 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -59,6 +59,11 @@ typedef struct { GLint uniform_opacity; GLint texorig_loc; GLint scale_loc; + + GLint uniform_mask_tex; + GLint uniform_mask_offset; + GLint uniform_mask_corner_radius; + GLint uniform_mask_inverted; } gl_blur_shader_t; typedef struct { @@ -142,8 +147,8 @@ void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg); void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visible); -bool gl_blur(backend_t *base, double opacity, void *, const region_t *reg_blur, - const region_t *reg_visible); +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); void *gl_create_blur_context(backend_t *base, enum blur_method, void *args); void gl_destroy_blur_context(backend_t *base, void *ctx); void gl_get_blur_size(void *blur_context, int *width, int *height); diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index f74b08a..08e804f 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -373,8 +373,8 @@ static void fill(backend_t *base, struct color c, const region_t *clip) { .height = to_u16_checked(extent->y2 - extent->y1)}}); } -static bool blur(backend_t *backend_data, double opacity, void *ctx_, - const region_t *reg_blur, const region_t *reg_visible) { +static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask, + coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible) { struct _xrender_blur_context *bctx = ctx_; if (bctx->method == BLUR_METHOD_NONE) { return true; @@ -425,7 +425,12 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, pixman_region32_fini(&clip); xcb_render_picture_t src_pict = xd->back[2], dst_pict = tmp_picture[0]; - auto alpha_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)]; + auto mask_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)]; + bool mask_allocated = false; + if (mask != NULL) { + mask_pict = process_mask(xd, mask, opacity != 1.0 ? mask_pict : XCB_NONE, + &mask_allocated); + } int current = 0; x_set_picture_clip_region(c, src_pict, 0, 0, ®_op_resized); @@ -461,11 +466,12 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, } else { x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op); // This is the last pass, and we are doing more than 1 pass - xcb_render_composite(c, XCB_RENDER_PICT_OP_OVER, src_pict, - alpha_pict, xd->back[2], 0, 0, 0, 0, - to_i16_checked(extent_resized->x1), - to_i16_checked(extent_resized->y1), - width_resized, height_resized); + xcb_render_composite( + c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], + 0, 0, to_i16_checked(extent_resized->x1 - mask_dst.x + 1), + to_i16_checked(extent_resized->y1 - mask_dst.y + 1), + to_i16_checked(extent_resized->x1), + to_i16_checked(extent_resized->y1), width_resized, height_resized); } // reset filter @@ -481,8 +487,10 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, if (i == 1) { x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op); xcb_render_composite( - c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, xd->back[2], 0, 0, - 0, 0, to_i16_checked(extent_resized->x1), + c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], 0, 0, + to_i16_checked(extent_resized->x1 - mask_dst.x + 1), + to_i16_checked(extent_resized->y1 - mask_dst.y + 1), + to_i16_checked(extent_resized->x1), to_i16_checked(extent_resized->y1), width_resized, height_resized); }