diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 7f370091dc..3f2f26c6cc 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -106,6 +106,18 @@ OPTIONS *--inactive-dim* 'VALUE':: Dim inactive windows. (0.0 - 1.0, defaults to 0.0) +*--corner-radius* 'VALUE':: + Round the corners of windows. (defaults to 0). + +*--rounded-corners-exclude* 'CONDITION':: + Exclude conditions for rounded corners. + +*--round-borders* 'VALUE':: + When rounding corners, Round the borders of windows. (defaults to 1). + +*--round-borders-exclude* 'CONDITION':: + Exclude conditions for rounding borders. + *--mark-wmwin-focused*:: Try to detect WM windows (a non-override-redirect window with no child that has 'WM_STATE') and mark them as active. diff --git a/src/backend/backend.c b/src/backend/backend.c index 3593919c30..1143e14a52 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -151,7 +151,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { } if (ps->root_image) { - ps->backend_data->ops->compose(ps->backend_data, ps->root_image, 0, 0, + ps->backend_data->ops->compose(ps->backend_data, t, ps->root_image, 0, 0, ®_paint, ®_visible); } else { ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1}, @@ -171,7 +171,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // The bounding shape of the window, in global/target coordinates // reminder: bounding shape contains the WM frame - auto reg_bound = win_get_bounding_shape_global_by_val(w); + auto reg_bound = win_get_bounding_shape_global_by_val(w, true); // The clip region for the current window, in global/target coordinates // reg_paint_in_bound \in reg_paint @@ -190,6 +190,17 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ®_paint_in_bound, ®_visible); } + // Store the window background for rounded corners + // If rounded corners backup the region first + if (w->corner_radius > 0) { + const int16_t x = w->g.x; + const int16_t y = w->g.y; + const auto wid = to_u16_checked(w->widthb); + const auto hei = to_u16_checked(w->heightb); + ps->backend_data->ops->store_back_texture(ps->backend_data, w, + ps->backend_round_context, ®_bound, x, y, wid, hei); + } + // Blur window background // TODO since the background might change the content of the window (e.g. // with shaders), we should consult the background whether the window @@ -230,7 +241,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { assert(ps->o.blur_background_frame); assert(real_win_mode == WMODE_FRAME_TRANS); - auto reg_blur = win_get_region_frame_local_by_val(w); + auto reg_blur = win_get_region_frame_local_by_val(w, true); pixman_region32_translate(®_blur, w->g.x, w->g.y); // make sure reg_blur \in reg_paint pixman_region32_intersect(®_blur, ®_blur, ®_paint); @@ -286,7 +297,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { assert(w->shadow_image); if (w->opacity == 1) { ps->backend_data->ops->compose( - ps->backend_data, w->shadow_image, w->g.x + w->shadow_dx, + ps->backend_data, w, w->shadow_image, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, ®_shadow, ®_visible); } else { auto new_img = ps->backend_data->ops->copy( @@ -295,7 +306,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ps->backend_data, IMAGE_OP_APPLY_ALPHA_ALL, new_img, NULL, ®_visible, (double[]){w->opacity}); ps->backend_data->ops->compose( - ps->backend_data, new_img, w->g.x + w->shadow_dx, + ps->backend_data, w, new_img, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, ®_shadow, ®_visible); ps->backend_data->ops->release_image(ps->backend_data, new_img); } @@ -311,7 +322,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // Draw window on target if (!w->invert_color && !w->dim && w->frame_opacity == 1 && w->opacity == 1) { - ps->backend_data->ops->compose(ps->backend_data, w->win_image, + ps->backend_data->ops->compose(ps->backend_data, w, w->win_image, w->g.x, w->g.y, ®_paint_in_bound, ®_visible); } else if (w->opacity * MAX_ALPHA >= 1) { @@ -360,7 +371,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ®_visible_local, (double[]){dim_opacity}); } if (w->frame_opacity != 1) { - auto reg_frame = win_get_region_frame_local_by_val(w); + auto reg_frame = win_get_region_frame_local_by_val(w, true); ps->backend_data->ops->image_op( ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, ®_frame, ®_visible_local, (double[]){w->frame_opacity}); @@ -371,13 +382,21 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ps->backend_data, IMAGE_OP_APPLY_ALPHA_ALL, new_img, NULL, ®_visible_local, (double[]){w->opacity}); } - ps->backend_data->ops->compose(ps->backend_data, new_img, w->g.x, + ps->backend_data->ops->compose(ps->backend_data, w, new_img, w->g.x, w->g.y, ®_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); } + + // Round the corners as last step after blur/shadow/dim/etc + if (w->corner_radius > 0.0) { + ps->backend_data->ops->round(ps->backend_data, w, + ps->backend_round_context, w->win_image, + ®_bound, ®_visible); + } + pixman_region32_fini(®_bound); pixman_region32_fini(®_paint_in_bound); } diff --git a/src/backend/backend.h b/src/backend/backend.h index 5a18e0ac01..486ce69755 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -28,6 +28,9 @@ typedef struct backend_base { /// Whether the backend can accept new render request at the moment bool busy; // ... + + // Session data + session_t *ps; } backend_t; typedef void (*backend_ready_callback_t)(void *); @@ -65,6 +68,11 @@ struct kernel_blur_args { int kernel_count; }; +struct round_corners_args { + int corner_radius; + bool round_borders; +}; + struct backend_operations { // =========== Initialization =========== @@ -126,7 +134,7 @@ struct backend_operations { * @param reg_paint the clip region, in target coordinates * @param reg_visible the visible region, in target coordinates */ - void (*compose)(backend_t *backend_data, void *image_data, int dst_x, int dst_y, + void (*compose)(backend_t *backend_data, struct managed_win *const w, void *image_data, int dst_x, int dst_y, const region_t *reg_paint, const region_t *reg_visible); /// Fill rectangle of the rendering buffer, mostly for debug purposes, optional. @@ -137,6 +145,11 @@ struct backend_operations { const region_t *reg_blur, const region_t *reg_visible) attr_nonnull(1, 3, 4, 5); + /// Round a given region of the rendering buffer. + bool (*round)(backend_t *backend_data, struct managed_win *w, void *round_ctx, + void *image_data, const region_t *reg_round, const region_t *reg_visible) + attr_nonnull(1, 2, 3, 5, 6); + /// 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 /// target window directly). @@ -218,6 +231,15 @@ struct backend_operations { /// Get how many pixels outside of the blur area is needed for blur void (*get_blur_size)(void *blur_context, int *width, int *height); + /// Backup our current window background so we can use it for "erasing" corners + bool (*store_back_texture)(backend_t *base, struct managed_win *w, void *ctx_, + const region_t *reg_tgt, int x, int y, int width, int height); + + /// Create a rounded corners context + void *(*create_round_context)(backend_t *base, void *args); + /// Destroy a rounded corners context + void (*destroy_round_context)(backend_t *base, void *ctx); + // =========== Hooks ============ /// Let the backend hook into the event handling queue /// Not implemented yet diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index 471a6779f5..19fb750fd2 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -368,4 +368,5 @@ void init_backend_base(struct backend_base *base, session_t *ps) { base->root = ps->root; base->busy = false; base->ops = NULL; + base->ps = ps; } diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 4b24f9fd58..73f2b643bf 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -56,7 +56,7 @@ static void dummy_check_image(struct backend_base *base, const struct dummy_imag assert(*tmp->refcount > 0); } -void dummy_compose(struct backend_base *base, void *image, int dst_x attr_unused, +void dummy_compose(struct backend_base *base, struct managed_win *w attr_unused, void *image, int dst_x attr_unused, int dst_y attr_unused, const region_t *reg_paint attr_unused, const region_t *reg_visible attr_unused) { dummy_check_image(base, image); @@ -72,6 +72,12 @@ bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity at return true; } +bool dummy_round(struct backend_base *backend_data attr_unused, struct managed_win *w attr_unused, + void *ctx_ attr_unused, void *image_data attr_unused, const region_t *reg_round attr_unused, + const region_t *reg_visible attr_unused) { + return true; +} + void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned attr_unused) { auto dummy = (struct dummy_data *)base; @@ -138,6 +144,14 @@ void *dummy_create_blur_context(struct backend_base *base attr_unused, void dummy_destroy_blur_context(struct backend_base *base attr_unused, void *ctx attr_unused) { } +void *dummy_create_round_context(struct backend_base *base attr_unused, void *args attr_unused) { + static int dummy_context; + return &dummy_context; +} + +void dummy_destroy_round_context(struct backend_base *base attr_unused, void *ctx attr_unused) { +} + void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) { // These numbers are arbitrary, to make sure the reisze_region code path is // covered. @@ -145,12 +159,18 @@ void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) { *height = 5; } +bool dummy_store_back_texture(backend_t *backend_data attr_unused, struct managed_win *w attr_unused, void *ctx_ attr_unused, + const region_t *reg_tgt attr_unused, int x attr_unused, int y attr_unused, int width attr_unused, int height attr_unused) { + return true; +} + struct backend_operations dummy_ops = { .init = dummy_init, .deinit = dummy_deinit, .compose = dummy_compose, .fill = dummy_fill, .blur = dummy_blur, + .round = dummy_round, .bind_pixmap = dummy_bind_pixmap, .render_shadow = default_backend_render_shadow, .release_image = dummy_release_image, @@ -162,6 +182,9 @@ struct backend_operations dummy_ops = { .copy = dummy_image_copy, .create_blur_context = dummy_create_blur_context, .destroy_blur_context = dummy_destroy_blur_context, + .create_round_context = dummy_create_round_context, + .destroy_round_context = dummy_destroy_round_context, .get_blur_size = dummy_get_blur_size, + .store_back_texture = dummy_store_back_texture }; diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index b89c49e8ce..2fffdb7d55 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -18,6 +18,7 @@ #include "string_utils.h" #include "types.h" #include "utils.h" +#include "win.h" #include "backend/backend_common.h" #include "backend/gl/gl_common.h" @@ -47,6 +48,20 @@ struct gl_blur_context { int npasses; }; +struct gl_round_context { + gl_round_shader_t *round_shader; + GLuint *bg_fbo; + GLuint *bg_tex; + /// Cached size of each blur_texture + struct tex_size { + int width; + int height; + } * tex_sizes; + int tex_count; + int fbo_count; + bool round_borders; +}; + static GLint glGetUniformLocationChecked(GLuint p, const char *name) { auto ret = glGetUniformLocation(p, name); if (ret < 0) { @@ -504,7 +519,7 @@ x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int text } // TODO: make use of reg_visible -void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, +void gl_compose(backend_t *base, struct managed_win *w attr_unused, void *image_data, int dst_x, int dst_y, const region_t *reg_tgt, const region_t *reg_visible attr_unused) { auto gd = (struct gl_data *)base; struct gl_image *img = image_data; @@ -730,6 +745,182 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu return ret; } +bool gl_round(backend_t *backend_data attr_unused, struct managed_win *w, void *ctx_, void *image_data, + const region_t *reg_round attr_unused, const region_t *reg_visible attr_unused) { + + struct gl_round_context *cctx = ctx_; + auto gd = (struct gl_data *)backend_data; + auto img = (struct gl_image*)image_data; + + //log_warn("r(%d) b(%d), wxy(%d %d) wwh(%d %d) img(%d %d)", + // w->corner_radius, w->g.border_width, w->g.x, w->g.y, + // w->widthb, w->heightb, img->inner->width, img->inner->height); + + int nrects; + const rect_t *rects; + rects = pixman_region32_rectangles((region_t *)reg_round, &nrects); + if (!nrects) { + // Nothing to paint + return false; + } + + GLuint target = gd->back_fbo; + int dst_x = w->g.x; + int dst_y = w->g.y; + + auto coord = ccalloc(nrects * 16, GLint); + auto indices = ccalloc(nrects * 6, GLuint); + x_rect_to_coords(nrects, rects, dst_x, dst_y, + img ? img->inner->height : w->heightb, gd->height, + img ? img->inner->y_inverted : true, coord, indices); + + 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) * 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)); + + // XXX: do we need projection matrix at all? + // Note: OpenGL matrices are column major + GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)gd->width, 0, 0, 0}, + {0, 2.0f / (GLfloat)gd->height, 0, 0}, + {0, 0, 0, 0}, + {-1, -1, 0, 1}}; + + //glDisable(GL_BLEND); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + // Bind texture + glViewport(0, 0, gd->width, gd->height); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, cctx->bg_tex[0]); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, img ? img->inner->texture : gd->back_texture); + + const gl_round_shader_t *ppass = &cctx->round_shader[0]; + glUseProgram(ppass->prog); + + if (ppass->projection_loc >= 0) + glUniformMatrix4fv(ppass->projection_loc, 1, false, projection_matrix[0]); + if (ppass->unifm_tex_bg >= 0) + glUniform1i(ppass->unifm_tex_bg, (GLint)1); + if (ppass->unifm_radius) + glUniform1f(ppass->unifm_radius, (float)w->corner_radius); + if (ppass->unifm_texcoord) + glUniform2f(ppass->unifm_texcoord, (float)w->g.x, (float)w->g.y); + if (ppass->unifm_texsize) + glUniform2f(ppass->unifm_texsize, (float)w->widthb, (float)w->heightb); + if (ppass->unifm_borderw) + glUniform1f(ppass->unifm_borderw, (w->round_borders) ? w->g.border_width : 0); + if (ppass->unifm_resolution) + glUniform2f(ppass->unifm_resolution, (float)gd->width, (float)gd->height); + + // Draw + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); + glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); + glDisableVertexAttribArray(vert_coord_loc); + glDisableVertexAttribArray(vert_in_texcoord_loc); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + + // Cleanup + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDrawBuffer(GL_BACK); + glEnable(GL_BLEND); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDeleteBuffers(2, bo); + + glUseProgram(0); + gl_check_err(); + + free(indices); + free(coord); + + return true; +} + +// Assumes the two textures are the same dimensions +static bool copyFrameBufferTexture(int width, int height, GLuint fboIn, GLuint textureIn, GLuint fboOut, GLuint textureOut) +{ + bool ret = false; + + // Bind input FBO + texture to a color attachment + glBindFramebuffer(GL_READ_FRAMEBUFFER, fboIn); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureIn, 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != + GL_FRAMEBUFFER_COMPLETE) { + log_error("Source framebuffer attachment failed."); + goto out; + } + glReadBuffer(GL_COLOR_ATTACHMENT0); + + // Bind destination FBO + texture to another color attachment + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboOut); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, textureOut, 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != + GL_FRAMEBUFFER_COMPLETE) { + log_error("Destination framebuffer attachment failed."); + goto out; + } + glDrawBuffer(GL_COLOR_ATTACHMENT1); + + // specify source, destination drawing (sub)rectangles. + glBlitFramebuffer(0, 0, width, height, + 0, 0, width, height, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + + ret = true; + +out: + // unbind the color attachments + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + gl_check_err(); + + return ret; +} + +bool gl_store_back_texture(backend_t *backend_data attr_unused, + struct managed_win *w attr_unused, void *ctx_ attr_unused, + const region_t *reg_tgt attr_unused, int x attr_unused, int y attr_unused, + int width attr_unused, int height attr_unused) { + + struct gl_round_context *cctx = ctx_; + auto gd = (struct gl_data *)backend_data; + + //log_info("Copying xy(%d %d) wh(%d %d)", x, y, width, height); + + { + // Prepare our backup texture + glBindTexture(GL_TEXTURE_2D, cctx->bg_tex[0]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gd->width, + gd->height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + copyFrameBufferTexture(gd->width, gd->height, gd->back_fbo, gd->back_texture, cctx->bg_fbo[0], cctx->bg_tex[0]); + } + + return true; +} + // clang-format off const char *vertex_shader = GLSL(330, uniform mat4 projection; @@ -975,6 +1166,36 @@ void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) { gl_check_err(); } +void gl_destroy_round_context(struct backend_base *base attr_unused, void *ctx attr_unused) { + + struct gl_round_context *cctx = ctx; + + if (cctx->round_shader && cctx->round_shader->prog) { + glDeleteProgram(cctx->round_shader->prog); + cctx->round_shader->prog = 0; + free(cctx->round_shader); + } + + if (cctx->bg_tex) { + glDeleteTextures(cctx->tex_count, cctx->bg_tex); + free(cctx->bg_tex); + } + if (cctx->bg_fbo) { + glDeleteFramebuffers(cctx->fbo_count, cctx->bg_fbo); + free(cctx->bg_fbo); + } + if (cctx->tex_sizes) { + free(cctx->tex_sizes); + } + + cctx->tex_count = 0; + cctx->fbo_count = 0; + + free(cctx); + + gl_check_err(); +} + /** * Initialize GL blur filters. */ @@ -1145,6 +1366,165 @@ void gl_get_blur_size(void *blur_context, int *width, int *height) { *height = ctx->resize_height; } +void *gl_create_round_context(struct backend_base *base attr_unused, void *args attr_unused) { + bool success; + auto gd = (struct gl_data *)base; + auto ctx = ccalloc(1, struct gl_round_context); + + struct round_corners_args *round_params = (struct round_corners_args *)args; + + ctx->round_borders = round_params->round_borders; + ctx->round_shader = ccalloc(1, gl_round_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 pass = ctx->round_shader; + { + // TEST passthrough shader + /*static const char frag_passthrough[] = GLSL(330, + uniform sampler2D tex; + in vec2 texcoord; + void main() { + //gl_FragColor = texture2D(tex, texcoord); + //gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1); + gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0); + //gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } + );*/ + + // dst0 shader from opengl.c + // clang-format off + static const char *FRAG_SHADER_ROUND_CORNERS = GLSL(330, + uniform sampler2D tex; + uniform sampler2D tex_bg; + uniform float u_radius; + uniform float u_borderw; + uniform vec2 u_texcoord; + uniform vec2 u_texsize; + uniform vec2 u_resolution; + in vec2 texcoord; + out vec4 out_color; + // https://www.shadertoy.com/view/ltS3zW + float RectSDF(vec2 p, vec2 b, float r) { + vec2 d = abs(p) - b + vec2(r); + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r; + } + void main() { + vec2 coord = vec2(u_texcoord.x, u_resolution.y-u_texsize.y-u_texcoord.y); + vec4 u_v4WndBgColor = texelFetch(tex_bg, ivec2(gl_FragCoord.xy), 0); + vec4 u_v4BorderColor = texelFetch(tex, ivec2(0, 0), 0); + vec4 u_v4FillColor = vec4(0.0, 0.0, 0.0, 0.0); // Inside rect, transparent + vec4 v4FromColor = u_v4BorderColor; // Always the border color. If no border, this still should be set + vec4 v4ToColor = u_v4WndBgColor; // Outside corners color = background texture + float u_fRadiusPx = u_radius; + float u_fHalfBorderThickness = u_borderw / 2.0; + + // misc tests, uncomment for diff rect colors + //u_v4FillColor = texture2D(tex, texcoord/u_texsize); + //u_v4FillColor = texelFetch(tex, ivec2(texcoord.xy), 0); + //u_v4FillColor = vec4(0.0, 1.0, 0.0, 0.0); // Inside rect color + //v4FromColor = u_v4BorderColor = vec4(1.0, 1.0, 0.0, 1.0); + //v4ToColor = vec4(0.0, 0.0, 1.0, 1.0); //Outside color + + vec2 u_v2HalfShapeSizePx = u_texsize/2.0 - vec2(u_fHalfBorderThickness); + vec2 v_v2CenteredPos = (gl_FragCoord.xy - u_texsize.xy / 2.0 - coord); + + float fDist = RectSDF(v_v2CenteredPos, u_v2HalfShapeSizePx, u_fRadiusPx - u_fHalfBorderThickness); + if (u_fHalfBorderThickness > 0.0) { + if (fDist < 0.0) { + v4ToColor = u_v4FillColor; + } + fDist = abs(fDist) - u_fHalfBorderThickness; + } else { + v4FromColor = u_v4FillColor; + } + float fBlendAmount = smoothstep(-1.0, 1.0, fDist); + + // final color + vec4 c = mix(v4FromColor, v4ToColor, fBlendAmount); + // we don't use discard due to alleged worse perf + // instead we can use alpha blending + //if ( c == vec4(0.0,0.0,0.0,0.0) ) discard; else + out_color = c; + } + ); + // clang-format on + + // Build shader + const char* SHADER_STR = FRAG_SHADER_ROUND_CORNERS; + //const char* SHADER_STR = frag_passthrough; + size_t shader_len = strlen(SHADER_STR) + 1 /* null terminator */; + char *shader_str = ccalloc(shader_len, char); + auto real_shader_len = snprintf(shader_str, shader_len, "%s", SHADER_STR); + CHECK(real_shader_len >= 0); + CHECK((size_t)real_shader_len < shader_len); + + // Build program + pass->prog = gl_create_program_from_str(vertex_shader, shader_str); + 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 + pass->projection_loc = glGetUniformLocationChecked(pass->prog, "projection"); + pass->unifm_tex_bg = glGetUniformLocationChecked(pass->prog, "tex_bg"); + pass->unifm_radius = glGetUniformLocationChecked(pass->prog, "u_radius"); + pass->unifm_texcoord = glGetUniformLocationChecked(pass->prog, "u_texcoord"); + pass->unifm_texsize = glGetUniformLocationChecked(pass->prog, "u_texsize"); + pass->unifm_borderw = glGetUniformLocationChecked(pass->prog, "u_borderw"); + pass->unifm_resolution = glGetUniformLocationChecked(pass->prog, "u_resolution"); + } + + // Texture size will be defined by gl_round + ctx->tex_count = 1; + ctx->bg_tex = ccalloc(ctx->tex_count, GLuint); + ctx->tex_sizes = ccalloc(ctx->tex_count, struct tex_size); + glGenTextures(ctx->tex_count, ctx->bg_tex); + for (int i = 0; i < ctx->tex_count; ++i) { + glBindTexture(GL_TEXTURE_2D, ctx->bg_tex[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->fbo_count = 1; + ctx->bg_fbo = ccalloc(ctx->fbo_count, GLuint); + glGenFramebuffers(ctx->fbo_count, ctx->bg_fbo); + for (int i = 0; i < ctx->fbo_count; ++i) { + if (!ctx->bg_fbo[i]) { + log_error("Failed to generate framebuffer object for blur"); + success = false; + goto out; + } + } + + success = true; + +out: + + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + + if (!success) { + gl_destroy_round_context(&gd->base, ctx); + ctx = NULL; + } + + gl_check_err(); + return ctx; +} + // clang-format off const char *win_shader_glsl = GLSL(330, uniform float opacity; diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 0a4ff49345..6a178b9d94 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -37,6 +37,18 @@ typedef struct { GLint texorig_loc; } gl_blur_shader_t; +typedef struct { + GLuint prog; + GLint projection_loc; + GLint unifm_radius; + GLint unifm_texcoord; + GLint unifm_texsize; + GLint unifm_borderw; + GLint unifm_resolution; + GLint unifm_tex_bg; + GLint unifm_tex_wnd; +} gl_round_shader_t; + typedef struct { GLuint prog; GLint color_loc; @@ -98,7 +110,7 @@ GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_ /** * @brief Render a region with texture data. */ -void gl_compose(backend_t *, void *ptex, int dst_x, int dst_y, const region_t *reg_tgt, +void gl_compose(backend_t *, struct managed_win *, void *ptex, int dst_x, int dst_y, const region_t *reg_tgt, const region_t *reg_visible); void gl_resize(struct gl_data *, int width, int height); @@ -121,6 +133,13 @@ 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); +bool gl_round(backend_t *backend_data, struct managed_win *w, void *ctx_, + void *image_data, const region_t *reg_round, const region_t *reg_visible); +void *gl_create_round_context(backend_t *base, void *args); +void gl_destroy_round_context(backend_t *base, void *ctx); +bool gl_store_back_texture(backend_t *backend_data, struct managed_win *w, + void *ctx_, const region_t *reg_tgt, int x, int y, int width, int height); + bool gl_is_image_transparent(backend_t *base, void *image_data); void gl_fill(backend_t *base, struct color, const region_t *clip); diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index ac066199f8..6d710771df 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -492,6 +492,7 @@ struct backend_operations glx_ops = { .image_op = gl_image_op, .copy = gl_copy, .blur = gl_blur, + .round = gl_round, .is_image_transparent = gl_is_image_transparent, .present = glx_present, .buffer_age = glx_buffer_age, @@ -499,7 +500,10 @@ struct backend_operations glx_ops = { .fill = gl_fill, .create_blur_context = gl_create_blur_context, .destroy_blur_context = gl_destroy_blur_context, + .create_round_context = gl_create_round_context, + .destroy_round_context = gl_destroy_round_context, .get_blur_size = gl_get_blur_size, + .store_back_texture = gl_store_back_texture, .max_buffer_age = 5, // Why? }; diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index a44dbc197d..0dd3b08640 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -90,7 +90,9 @@ struct _xrender_image_data { bool owned; }; -static void compose(backend_t *base, void *img_data, int dst_x, int dst_y, +uint32_t make_rounded_window_shape(xcb_render_trapezoid_t traps[], uint32_t max_ntraps, int cr, int wid, int hei); + +static void compose(backend_t *base, struct managed_win *w, void *img_data, int dst_x, int dst_y, const region_t *reg_paint, const region_t *reg_visible) { struct _xrender_data *xd = (void *)base; struct _xrender_image_data *img = img_data; @@ -104,10 +106,52 @@ static void compose(backend_t *base, void *img_data, int dst_x, int dst_y, // sure we get everything into the buffer x_clear_picture_clip_region(base->c, img->pict); - x_set_picture_clip_region(base->c, xd->back[2], 0, 0, ®); - xcb_render_composite(base->c, op, img->pict, alpha_pict, xd->back[2], 0, 0, 0, 0, - to_i16_checked(dst_x), to_i16_checked(dst_y), - to_u16_checked(img->ewidth), to_u16_checked(img->eheight)); + // Are we rounding corners? + session_t *ps = base->ps; + int cr = (w ? w->corner_radius : 0); + + if (cr == 0) { + x_set_picture_clip_region(base->c, xd->back[2], 0, 0, ®); + xcb_render_composite(base->c, op, img->pict, alpha_pict, xd->back[2], 0, 0, 0, 0, + to_i16_checked(dst_x), to_i16_checked(dst_y), + to_u16_checked(img->ewidth), to_u16_checked(img->eheight)); + } else { + // Rounded corners + const int fullwid = w ? w->widthb : 0; + const int fullhei = w ? w-> heightb : 0; + //const int fullwid = img->width; + //const int fullhei = img->height; + //log_warn("f(%d, %d) imge(%d %d) imgf(%d %d) sdw(%d %d) dst(%d %d) s:%d b:%d", fullwid, fullhei, img->ewidth, img->eheight, img->width, img->height, w->shadow_width, w->shadow_height, dst_x, dst_y, w->shadow, w->g.border_width); + xcb_render_picture_t p_tmp = x_create_picture_with_standard( + ps->c, ps->root, fullwid, fullhei, XCB_PICT_STANDARD_ARGB_32, 0, 0); + xcb_render_color_t trans = { + .red = 0, .blue = 0, .green = 0, .alpha = 0}; + const xcb_rectangle_t rect = {.x = 0, + .y = 0, + .width = to_u16_checked(fullwid), + .height = to_u16_checked(fullhei)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, + p_tmp, trans, 1, &rect); + + uint32_t max_ntraps = to_u32_checked(cr); + xcb_render_trapezoid_t traps[4 * max_ntraps + 5]; + + uint32_t n = make_rounded_window_shape(traps, max_ntraps, cr, fullwid, fullhei); + + xcb_render_trapezoids( + ps->c, XCB_RENDER_PICT_OP_OVER, alpha_pict, p_tmp, + x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), + 0, 0, n, traps); + + x_set_picture_clip_region(base->c, xd->back[2], 0, 0, ®); + xcb_render_composite(base->c, XCB_RENDER_PICT_OP_OVER, img->pict, p_tmp, xd->back[2], + 0, 0, 0, 0, + //0, 0, to_i16_checked(x), to_i16_checked(y), + to_i16_checked(dst_x), to_i16_checked(dst_y), + to_u16_checked(img->ewidth), to_u16_checked(img->eheight)); + + xcb_render_free_picture(ps->c, p_tmp); + } pixman_region32_fini(®); } @@ -242,6 +286,16 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, xcb_render_free_picture(c, tmp_picture[0]); xcb_render_free_picture(c, tmp_picture[1]); pixman_region32_fini(®_op); + pixman_region32_fini(®_op_resized); + return true; +} + +static bool x_round(struct backend_base *backend_data attr_unused, struct managed_win *w attr_unused, + void *ctx_ attr_unused, void *image_data attr_unused, const region_t *reg_blur attr_unused, + const region_t *reg_visible attr_unused) { + + // dummy implementation, we already perform the rounding in compose + // TODO: should move the compose code here and call it from here return true; } @@ -551,6 +605,19 @@ void get_blur_size(void *blur_context, int *width, int *height) { *height = ctx->resize_height; } +bool store_back_texture(backend_t *backend_data attr_unused, struct managed_win *w attr_unused, void *ctx_ attr_unused, + const region_t *reg_tgt attr_unused, int x attr_unused, int y attr_unused, int width attr_unused, int height attr_unused) { + return true; +} + +void *create_round_context(struct backend_base *base attr_unused, void *args attr_unused) { + static int dummy_context; + return &dummy_context; +} + +void destroy_round_context(struct backend_base *base attr_unused, void *ctx attr_unused) { +} + backend_t *backend_xrender_init(session_t *ps) { auto xd = ccalloc(1, struct _xrender_data); init_backend_base(&xd->base, ps); @@ -638,6 +705,7 @@ struct backend_operations xrender_ops = { .init = backend_xrender_init, .deinit = deinit, .blur = blur, + .round = x_round, .present = present, .compose = compose, .fill = fill, @@ -654,7 +722,10 @@ struct backend_operations xrender_ops = { .copy = copy, .create_blur_context = create_blur_context, .destroy_blur_context = destroy_blur_context, + .create_round_context = create_round_context, + .destroy_round_context = destroy_round_context, .get_blur_size = get_blur_size, + .store_back_texture = store_back_texture }; // vim: set noet sw=8 ts=8: diff --git a/src/common.h b/src/common.h index 84a42ceb4a..1b75f6bd74 100644 --- a/src/common.h +++ b/src/common.h @@ -157,6 +157,8 @@ typedef struct session { backend_t *backend_data; /// backend blur context void *backend_blur_context; + /// round corners context + void *backend_round_context; /// graphic drivers used enum driver drivers; /// file watch handle @@ -340,11 +342,13 @@ typedef struct session { /// Whether X Present extension exists. bool present_exists; /// Whether X GLX extension exists. +#ifdef CONFIG_OPENGL bool glx_exists; /// Event base number for X GLX extension. int glx_event; /// Error base number for X GLX extension. int glx_error; +#endif /// Whether X Xinerama extension exists. bool xinerama_exists; /// Xinerama screen regions. diff --git a/src/config.c b/src/config.c index a88bc02536..30f0dd27d9 100644 --- a/src/config.c +++ b/src/config.c @@ -560,6 +560,9 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .no_ewmh_fullscreen = false, .track_leader = false, + + .rounded_corners_blacklist = NULL, + .round_borders_blacklist = NULL }; char *ret = NULL; diff --git a/src/config.h b/src/config.h index cd32add2ff..e237f497fb 100644 --- a/src/config.h +++ b/src/config.h @@ -23,8 +23,8 @@ #include "kernel.h" #include "log.h" #include "region.h" -#include "win_defs.h" #include "types.h" +#include "win_defs.h" typedef struct session session_t; @@ -237,6 +237,14 @@ typedef struct options { // Make transparent windows clip other windows, instead of blending on top of // them bool transparent_clipping; + // === Rounded corners related === + int corner_radius; + /// Rounded corners blacklist. A linked list of conditions. + c2_lptr_t *rounded_corners_blacklist; + /// Do we round the borders of rounded windows? + int round_borders; + /// Rounded borders blacklist. A linked list of conditions. + c2_lptr_t *round_borders_blacklist; } options_t; extern const char *const BACKEND_STRS[NUM_BKEND + 1]; diff --git a/src/config_libconfig.c b/src/config_libconfig.c index b07155c56f..c7ab438cc0 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -367,6 +367,14 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // --active_opacity if (config_lookup_float(&cfg, "active-opacity", &dval)) opt->active_opacity = normalize_d(dval); + // --corner-radius + config_lookup_int(&cfg, "corner-radius", &opt->corner_radius); + // --rounded-corners-exclude + parse_cfg_condlst(&cfg, &opt->rounded_corners_blacklist, "rounded-corners-exclude"); + // --round-borders + config_lookup_int(&cfg, "round-borders", &opt->round_borders); + // --round-borders-exclude + parse_cfg_condlst(&cfg, &opt->round_borders_blacklist, "round-borders-exclude"); // -e (frame_opacity) config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity); // -c (shadow_enable) @@ -446,6 +454,7 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad } lcfg_lookup_bool(&cfg, "vsync", &opt->vsync); // --backend + lcfg_lookup_bool(&cfg, "experimental-backends", &opt->experimental_backends); if (config_lookup_string(&cfg, "backend", &sval)) { opt->backend = parse_backend(sval); if (opt->backend >= NUM_BKEND) { diff --git a/src/event.c b/src/event.c index 89c06eff4d..fc1c83efb3 100644 --- a/src/event.c +++ b/src/event.c @@ -377,6 +377,7 @@ static inline void expose_root(session_t *ps, const rect_t *rects, int nrects) { region_t region; pixman_region32_init_rects(®ion, rects, nrects); add_damage(ps, ®ion); + pixman_region32_fini(®ion); } static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) { @@ -622,14 +623,14 @@ static inline void ev_shape_notify(session_t *ps, xcb_shape_notify_event_t *ev) * if we attempt to rebuild border_size */ // Mark the old border_size as damaged - region_t tmp = win_get_bounding_shape_global_by_val(w); + region_t tmp = win_get_bounding_shape_global_by_val(w, true); add_damage(ps, &tmp); pixman_region32_fini(&tmp); win_update_bounding_shape(ps, w); // Mark the new border_size as damaged - tmp = win_get_bounding_shape_global_by_val(w); + tmp = win_get_bounding_shape_global_by_val(w, true); add_damage(ps, &tmp); pixman_region32_fini(&tmp); diff --git a/src/opengl.c b/src/opengl.c index 1e956be4a1..b289df28f0 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -95,6 +95,19 @@ bool glx_init(session_t *ps, bool need_render) { ppass->unifm_offset_x = -1; ppass->unifm_offset_y = -1; } + + ps->psglx->round_passes = ccalloc(2, glx_round_pass_t); + for (int i = 0; i < 2; ++i) { + glx_round_pass_t *ppass = &ps->psglx->round_passes[i]; + ppass->unifm_radius = -1; + ppass->unifm_texcoord = -1; + ppass->unifm_texsize = -1; + ppass->unifm_borderw = -1; + ppass->unifm_borderc = -1; + ppass->unifm_resolution = -1; + ppass->unifm_tex_scr = -1; + ppass->unifm_tex_wnd = -1; + } } glx_session_t *psglx = ps->psglx; @@ -239,6 +252,15 @@ void glx_destroy(session_t *ps) { } free(ps->psglx->blur_passes); + for (int i = 0; i < 2; ++i) { + glx_round_pass_t *ppass = &ps->psglx->round_passes[i]; + if (ppass->frag_shader) + glDeleteShader(ppass->frag_shader); + if (ppass->prog) + glDeleteProgram(ppass->prog); + } + free(ps->psglx->round_passes); + glx_free_prog_main(&ps->glx_prog_win); gl_check_err(); @@ -413,6 +435,184 @@ bool glx_init_blur(session_t *ps) { return true; } +static inline bool glx_init_frag_shader_corners(glx_round_pass_t *ppass, + const int shader_idx, const char *PREFIX_STR, const char* SHADER_STR, + const char *extension, const char *sampler_type, const char *texture_func) { + + // Build rounded corners shader + { + auto len = strlen(PREFIX_STR) + strlen(extension) + + strlen(sampler_type)*2 + strlen(texture_func)*2 + + strlen(SHADER_STR) + 1; + char *shader_str = calloc(len, sizeof(char)); + if (!shader_str) { + log_error("Failed to allocate %zd bytes for shader string.", len); + return false; + } + + char *pc = shader_str; + sprintf(pc, PREFIX_STR, extension, sampler_type, sampler_type, texture_func, texture_func); + pc += strlen(pc); + assert(strlen(shader_str) < len); + + sprintf(pc, "%s", SHADER_STR); + assert(strlen(shader_str) < len); +#ifdef DEBUG_GLX + log_debug("Generated rounded corners shader %d:\n%s\n", shader_idx, shader_str); +#endif + + //log_info("Generated rounded corners shader %d:\n%s\n", shader_idx, shader_str); + + ppass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str); + free(shader_str); + + if (!ppass->frag_shader) { + log_error("Failed to create rounded corners fragment shader."); + return false; + } + + // Build program + ppass->prog = gl_create_program(&ppass->frag_shader, 1); + if (!ppass->prog) { + log_error("Failed to create GLSL program."); + return false; + } + + // Get uniform addresses +#define P_GET_UNIFM_LOC(name, target) \ +{ \ + ppass->target = glGetUniformLocation(ppass->prog, name); \ + if (ppass->target < 0) { \ + log_error("Failed to get location of rounded corners uniform '" name \ + "'. Might be troublesome. (shader_idx: %d)" \ + , shader_idx); \ + } \ +} + P_GET_UNIFM_LOC("u_radius", unifm_radius); + P_GET_UNIFM_LOC("u_texcoord", unifm_texcoord); + P_GET_UNIFM_LOC("u_texsize", unifm_texsize); + P_GET_UNIFM_LOC("u_borderw", unifm_borderw); + P_GET_UNIFM_LOC("u_borderc", unifm_borderc); + P_GET_UNIFM_LOC("u_resolution", unifm_resolution); + P_GET_UNIFM_LOC("tex_scr", unifm_tex_scr); + // We don't need this one anymore since we get + // the border color using glReadPixel + // uncomment if you need to use 'tex_wnd' in the shader + //P_GET_UNIFM_LOC("tex_wnd", unifm_tex_wnd); +#undef P_GET_UNIFM_LOC + } + + return true; +} + +/** + * Initialize GLX rounded corners filter. + */ +bool glx_init_rounded_corners(session_t *ps) { + + { + 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"); + + static const char *FRAG_SHADER_PREFIX = + "#version 110\n" + "%s" // extensions + "uniform float u_radius;\n" + "uniform float u_borderw;\n" + "uniform vec4 u_borderc;\n" + "uniform vec2 u_texcoord;\n" + "uniform vec2 u_texsize;\n" + "uniform vec2 u_resolution;\n" + "uniform %s tex_scr;\n" // sampler2D | sampler2DRect + "uniform %s tex_wnd;\n" // sampler2D | sampler2DRect + "\n" + "// https://www.shadertoy.com/view/ltS3zW\n" + "float RectSDF(vec2 p, vec2 b, float r) {\n" + " vec2 d = abs(p) - b + vec2(r);\n" + " return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n" + "}\n\n" + "void main()\n" + "{\n" + " vec2 coord = vec2(u_texcoord.x, u_resolution.y-u_texsize.y-u_texcoord.y);\n" + " // Get the window_bg color (so we can \"erase\" corners) from the bg texture\n" + " // and the border color from the mid x-axis of the target window (hacky...)\n" + " vec4 u_v4WndBgColor = %s(tex_scr, vec2(gl_TexCoord[0].st));\n" + " //vec4 u_v4BorderColor = %s(tex_wnd, vec2(0, u_texsize.t/2.));\n" + " vec4 u_v4BorderColor = u_borderc;\n" + " vec4 u_v4FillColor = vec4(0.0, 0.0, 0.0, 0.0); // Inside rect, transparent\n" + " vec4 v4FromColor = u_v4BorderColor; //Always the border color. If no border, this still should be set\n" + " vec4 v4ToColor = u_v4WndBgColor; //Outside corners color, we set it to background texture\n" + "\n"; + + // Fragment shader (round corners) + // dst1 shader + static const char *FRAG_SHADER_ROUND_CORNERS = + " float u_fRadiusPx = u_radius;\n" + " float u_fHalfBorderThickness = u_borderw / 2.0;\n" + " //float u_fHalfBorderThickness = 20.0 /2.0;\n" + " //u_v4FillColor = vec4(0.0, 1.0, 0.0, 1.0);\n" + " //v4FromColor = u_v4BorderColor = vec4(1.0, 0.0, 0.0, 1.0);\n" + " //v4ToColor = vec4(0.0, 0.0, 1.0, 1.0); //Outside color\n" + "\n" + " vec2 u_v2HalfShapeSizePx = u_texsize/2.0 - vec2(u_fHalfBorderThickness);\n" + " vec2 v_v2CenteredPos = (gl_FragCoord.xy - u_texsize.xy / 2.0 - coord);\n" + "\n" + " float fDist = RectSDF(v_v2CenteredPos, u_v2HalfShapeSizePx, u_fRadiusPx - u_fHalfBorderThickness);\n" + " if (u_fHalfBorderThickness > 0.0) {\n" + " if (fDist < 0.0) {\n" + " v4ToColor = u_v4FillColor;\n" + " }\n" + " fDist = abs(fDist) - u_fHalfBorderThickness;\n" + " } else {\n" + " v4FromColor = u_v4FillColor;\n" + " }\n" + " float fBlendAmount = smoothstep(-1.0, 1.0, fDist);\n" + " vec4 c = mix(v4FromColor, v4ToColor, fBlendAmount);" + "\n" + " // final color\n" + " //if ( c == vec4(0.0,0.0,0.0,0.0) ) discard; else\n" + " gl_FragColor = c;\n" + " //gl_FragColor = vec4(vec3(fBlendAmount), 1.0);\n" + " //gl_FragColor = vec4(vec3(abs(dist) / (2.0 * corner)), 1.0);\n" + "}\n"; + + const bool use_texture_rect = !ps->psglx->has_texture_non_power_of_two; + const char *sampler_type = (use_texture_rect ? "sampler2DRect": "sampler2D"); + const char *texture_func = (use_texture_rect ? "texture2DRect": "texture2D"); + char *extension = NULL; + if (use_texture_rect) { + mstrextend(&extension, "#extension GL_ARB_texture_rectangle : " + "require\n"); + } + if (!extension) { + extension = strdup(""); + } + + if (!glx_init_frag_shader_corners(&ps->psglx->round_passes[0], 0, + FRAG_SHADER_PREFIX, FRAG_SHADER_ROUND_CORNERS, + extension, sampler_type, texture_func)) { + + log_error("Failed to create rounded corners fragment shader PRE."); + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + free(extension); + return false; + } + + free(extension); + + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + } + + gl_check_err(); + + return true; +} + /** * Load a GLSL main program from shader strings. */ @@ -447,6 +647,114 @@ bool glx_load_prog_main(const char *vshader_str, const char *fshader_str, return true; } +/** + * @brief Release binding of a texture. + */ +void glx_release_texture(session_t *ps attr_unused, glx_texture_t **pptex) { + glx_texture_t *ptex = *pptex; + // Release binding + if (ptex->texture) { + //log_info("Deleting texture wh(%d %d)", ptex->width, ptex->height); + glBindTexture(ptex->target, 0); + glDeleteTextures(1, &ptex->texture); + } + free(ptex); + *pptex = NULL; + + gl_check_err(); +} + +/** + * Bind a X pixmap to an OpenGL texture. + */ +bool glx_bind_texture(session_t *ps attr_unused, glx_texture_t **pptex, + int x, int y, int width attr_unused, int height attr_unused, bool repeat attr_unused) { + if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID) + return true; + + glx_texture_t *ptex = *pptex; + + //log_trace("Copying xy(%d %d) wh(%d %d) ptex(%p)", x, y, width, height, ptex); + + // Release texture if parameters are inconsistent + if (ptex && ptex->texture && + (ptex->width != width || ptex->height != height)) { + //log_info("Windows size changed old_wh(%d %d) new_wh(%d %d)", ptex->width, ptex->height, width, height); + glx_release_texture(ps, &ptex); + } + + // Allocate structure + if (!ptex) { + static const glx_texture_t GLX_TEX_DEF = { + .texture = 0, + .glpixmap = 0, + .pixmap = 0, + .target = 0, + .width = 0, + .height = 0, + .y_inverted = false, + }; + + ptex = cmalloc(glx_texture_t); + memcpy(ptex, &GLX_TEX_DEF, sizeof(glx_texture_t)); + *pptex = ptex; + + ptex->width = width; + ptex->height = height; + ptex->target = GL_TEXTURE_RECTANGLE; + if (ps->psglx->has_texture_non_power_of_two) + ptex->target = GL_TEXTURE_2D; + } + + // Create texture + if (!ptex->texture) { + //log_info("Generating texture for xy(%d %d) wh(%d %d)", x, y, width, height); + GLuint texture = 0; + glGenTextures(1, &texture); + + if (texture) { + glEnable(ptex->target); + glBindTexture(ptex->target, texture); + + glTexParameteri(ptex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(ptex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if (repeat) { + glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_REPEAT); + } else { + glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + glTexImage2D(ptex->target, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + + glBindTexture(ptex->target, 0); + //glDisable(ptex->target); + } + + ptex->texture = texture; + } + if (!ptex->texture) { + log_error("Failed to allocate texture."); + return false; + } + + // Read destination pixels into a texture + glEnable(ptex->target); + glBindTexture(ptex->target, ptex->texture); + if (width > 0 && height > 0) + glCopyTexSubImage2D(ptex->target, 0, 0, 0, x, ps->root_height - y - height, width, height); + + // Cleanup + glBindTexture(ptex->target, 0); + glDisable(ptex->target); + + gl_check_err(); + + return true; +} + + /** * Bind a X pixmap to an OpenGL texture. */ @@ -887,6 +1195,163 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, return ret; } +// TODO: this is a mess and needs a more consistent way of getting the border pixel +// I tried looking for a notify event for XCB_CW_BORDER_PIXEL (in xcb_create_window()) +// or a way to get the pixels from xcb_render_picture_t but the documentation for +// the xcb_xrender extension is literaly non existent... +bool glx_read_border_pixel(struct managed_win *w, int root_height, int x, int y, + int width attr_unused, int height, int cr, float *ppixel) +{ + if (!ppixel) return false; + + // First try bottom left corner past the + // circle radius (after the rounded corner ends) + auto openglx = x + cr*2; + auto opengly = root_height-height-y; + + // X is out of bounds + // move to the right side + if (openglx < 0) + openglx = x + width - cr; + + // Y is out of bounds + // move to to top part + if (opengly < 0) { + opengly += height-1; + } + + // bottom left corner is out of bounds + // use top border line instead + if (openglx < 0 || opengly < 0) { + + //log_warn("OUT OF BOUNDS: xy(%d, %d), glxy(%d %d) wh(%d %d), border_col(%.2f, %.2f, %.2f, %.2f)", + // x, y, openglx, opengly, width, height, + // (float)w->border_col[0], (float)w->border_col[1], (float)w->border_col[2], (float)w->border_col[3]); + + // Reset the color so the shader doesn't use it + w->border_col[0] = w->border_col[1] = w->border_col[2] = w->border_col[3] = -1.0; + } + + // Invert Y-axis so we can query border color from texture (0,0) + glReadPixels((openglx < 0) ? 0 : openglx, (opengly < 0) ? 0 : opengly, 1, 1, + GL_RGBA, GL_FLOAT, (void*)&w->border_col[0]); + + //log_warn("xy(%d, %d), glxy(%d %d) wh(%d %d), border_col(%.2f, %.2f, %.2f, %.2f)", + // x, y, openglx, opengly, width, height, + // (float)w->border_col[0], (float)w->border_col[1], (float)w->border_col[2], (float)w->border_col[3]); + + gl_check_err(); + + return true; +} + +bool glx_round_corners_dst(session_t *ps, struct managed_win *w, const glx_texture_t *ptex, + int dx, int dy, int width, int height, float z, float cr, + const region_t *reg_tgt attr_unused, glx_blur_cache_t *pbc attr_unused) { + + assert(ps->psglx->round_passes[0].prog); + assert(ps->psglx->round_passes[1].prog); + bool ret = false; + + if (w->g.border_width >= 1 /*&& w->border_col[0] == -1.0*/) { + glx_read_border_pixel(w, ps->root_height, dx, dy, width, height, w->corner_radius, &w->border_col[0]); + } + + { + const glx_round_pass_t *ppass = &ps->psglx->round_passes[0]; + assert(ppass->prog); + + // If caller specified a texture use it as source + if (ptex) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(ptex->target, ptex->texture); + } + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glUseProgram(ppass->prog); + + if (ppass->unifm_tex_scr >= 0) + glUniform1i(ppass->unifm_tex_scr, (GLint)0); + // We have no GL_TEXTURE1 here so just pass the default + if (ppass->unifm_tex_wnd >= 0) + glUniform1i(ppass->unifm_tex_wnd, (GLint)0); + + if (ppass->unifm_radius >= 0) + glUniform1f(ppass->unifm_radius, cr); + if (ppass->unifm_texcoord >= 0) + glUniform2f(ppass->unifm_texcoord, (float)dx, (float)dy); + if (ppass->unifm_texsize >= 0) + glUniform2f(ppass->unifm_texsize, (float)width, (float)height); + if (ppass->unifm_borderw >= 0) + glUniform1f(ppass->unifm_borderw, (w->round_borders && w->border_col[0] != -1.) ? w->g.border_width : 0); + if (ppass->unifm_borderc >= 0) + glUniform4fv(ppass->unifm_borderc, 1, (GLfloat *)&w->border_col[0]); + if (ppass->unifm_resolution >= 0) + glUniform2f(ppass->unifm_resolution, (float)ps->root_width, (float)ps->root_height); + + // Painting + { + P_PAINTREG_START(crect) { + // XXX explain these variables + auto rx = (GLfloat)(crect.x1 - dx); + auto ry = (GLfloat)(crect.y1 - dy); + auto rxe = rx + (GLfloat)(crect.x2 - crect.x1); + auto rye = ry + (GLfloat)(crect.y2 - crect.y1); + // Rectangle textures have [0-w] [0-h] while 2D texture has [0-1] + // [0-1] Thanks to amonakov for pointing out! + if (GL_TEXTURE_2D == ptex->target) { + rx = rx / (GLfloat)width; + ry = ry / (GLfloat)height; + rxe = rxe / (GLfloat)width; + rye = rye / (GLfloat)height; + } + auto rdx = (GLfloat)crect.x1; + auto rdy = (GLfloat)(ps->root_height - crect.y1); + auto rdxe = (GLfloat)rdx + (GLfloat)(crect.x2 - crect.x1); + auto rdye = (GLfloat)rdy - (GLfloat)(crect.y2 - crect.y1); + + // Invert Y if needed, this may not work as expected, though. I + // don't have such a FBConfig to test with. + //if (ptex && !ptex->y_inverted) { + { + ry = 1.0f - ry; + rye = 1.0f - rye; + } + + //log_trace("Rect %d (i:%d): %f, %f, %f, %f -> %f, %f, %f, %f", + // ri ,ptex ? ptex->y_inverted : -1, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); + + glTexCoord2f(rx, ry); + glVertex3f(rdx, rdy, z); + + glTexCoord2f(rxe, ry); + glVertex3f(rdxe, rdy, z); + + glTexCoord2f(rxe, rye); + glVertex3f(rdxe, rdye, z); + + glTexCoord2f(rx, rye); + glVertex3f(rdx, rdye, z); + + } + P_PAINTREG_END(); + } + + glUseProgram(0); + glDisable(GL_BLEND); + } + + ret = true; + + //glBindFramebuffer(GL_FRAMEBUFFER, 0); + + gl_check_err(); + + return ret; +} + bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, GLfloat factor, const region_t *reg_tgt) { // It's possible to dim in glx_render(), but it would be over-complicated @@ -920,9 +1385,9 @@ bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, /** * @brief Render a region with texture data. */ -bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy, - int width, int height, int z, double opacity, bool argb, bool neg, - const region_t *reg_tgt, const glx_prog_main_t *pprogram) { +bool glx_render(session_t *ps, struct managed_win *w attr_unused, const glx_texture_t *ptex, + int x, int y, int dx, int dy, int width, int height, int z, double opacity, bool argb, + bool neg, int cr attr_unused, const region_t *reg_tgt, const glx_prog_main_t *pprogram) { if (!ptex || !ptex->texture) { log_error("Missing texture."); return false; @@ -936,7 +1401,7 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, glEnable(ptex->target); // Enable blending if needed - if (opacity < 1.0 || argb) { + if (opacity < 1.0 || argb || cr > 0) { glEnable(GL_BLEND); diff --git a/src/opengl.h b/src/opengl.h index 033cf16ea8..0c8dfac412 100644 --- a/src/opengl.h +++ b/src/opengl.h @@ -40,6 +40,30 @@ typedef struct { GLint unifm_factor_center; } glx_blur_pass_t; +typedef struct { + /// Fragment shader for rounded corners. + GLuint frag_shader; + /// GLSL program for rounded corners. + GLuint prog; + /// Location of uniform "radius" in rounded-corners GLSL program. + GLint unifm_radius; + /// Location of uniform "texcoord" in rounded-corners GLSL program. + GLint unifm_texcoord; + /// Location of uniform "texsize" in rounded-corners GLSL program. + GLint unifm_texsize; + /// Location of uniform "borderw" in rounded-corners GLSL program. + GLint unifm_borderw; + /// Location of uniform "borderc" in rounded-corners GLSL program. + GLint unifm_borderc; + /// Location of uniform "resolution" in rounded-corners GLSL program. + GLint unifm_resolution; + /// Location of uniform "texture_scr" in rounded-corners GLSL program. + GLint unifm_tex_scr; + /// Location of uniform "texture_wnd" in rounded-corners GLSL program. + GLint unifm_tex_wnd; + +} glx_round_pass_t; + /// Structure containing GLX-dependent data for a session. typedef struct glx_session { // === OpenGL related === @@ -50,6 +74,7 @@ typedef struct glx_session { /// Current GLX Z value. int z; glx_blur_pass_t *blur_passes; + glx_round_pass_t *round_passes; } glx_session_t; /// @brief Wrapper of a binded GLX texture. @@ -69,8 +94,8 @@ typedef struct _glx_texture { bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, GLfloat factor, const region_t *reg_tgt); -bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy, - int width, int height, int z, double opacity, bool argb, bool neg, +bool glx_render(session_t *ps, struct managed_win *w, const glx_texture_t *ptex, int x, int y, int dx, int dy, + int width, int height, int z, double opacity, bool argb, bool neg, int cr, const region_t *reg_tgt, const glx_prog_main_t *pprogram); bool glx_init(session_t *ps, bool need_render); @@ -81,6 +106,8 @@ void glx_on_root_change(session_t *ps); bool glx_init_blur(session_t *ps); +bool glx_init_rounded_corners(session_t *ps); + #ifdef CONFIG_OPENGL bool glx_load_prog_main(const char *vshader_str, const char *fshader_str, glx_prog_main_t *pprogram); @@ -91,6 +118,11 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, void glx_release_pixmap(session_t *ps, glx_texture_t *ptex); +bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, + int x, int y, int width, int height, bool repeat); + +void glx_release_texture(session_t *ps, glx_texture_t **ptex); + void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2); /** @@ -105,6 +137,10 @@ void glx_set_clip(session_t *ps, const region_t *reg); bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc); +bool glx_round_corners_dst(session_t *ps, struct managed_win *w, const glx_texture_t *ptex, + int dx, int dy, int width, int height, float z, float cr, + const region_t *reg_tgt, glx_blur_cache_t *pbc); + GLuint glx_create_shader(GLenum shader_type, const char *shader_str); GLuint glx_create_program(const GLuint *const shaders, int nshaders); @@ -206,6 +242,8 @@ static inline void free_win_res_glx(session_t *ps, struct managed_win *w) { free_paint_glx(ps, &w->shadow_paint); #ifdef CONFIG_OPENGL free_glx_bc(ps, &w->glx_blur_cache); + free_glx_bc(ps, &w->glx_round_cache); + free_texture(ps, &w->glx_texture_bg); free(w->paint.fbcfg); #endif } diff --git a/src/options.c b/src/options.c index 8f44b4336f..ec370c2469 100644 --- a/src/options.c +++ b/src/options.c @@ -114,6 +114,18 @@ static void usage(const char *argv0, int ret) { "--active-opacity opacity\n" " Default opacity for active windows. (0.0 - 1.0)\n" "\n" + "--corner-radius value\n" + " Round the corners of windows. (defaults to 0)\n" + "\n" + "--rounded-corners-exclude condition\n" + " Exclude conditions for rounded corners.\n" + "\n" + "--round-borders value\n" + " When rounding corners, round the borders of windows. (defaults to 1)\n" + "\n" + "--round-borders-exclude condition\n" + " Exclude conditions for rounding borders.\n" + "\n" "--mark-wmwin-focused\n" " Try to detect WM windows and mark them as active.\n" "\n" @@ -437,6 +449,10 @@ static const struct option longopts[] = { {"blur-method", required_argument, NULL, 328}, {"blur-size", required_argument, NULL, 329}, {"blur-deviation", required_argument, NULL, 330}, + {"corner-radius", required_argument, NULL, 331}, + {"rounded-corners-exclude", required_argument, NULL, 332}, + {"round-borders", required_argument, NULL, 334}, + {"round-borders-exclude", required_argument, NULL, 335}, {"experimental-backends", no_argument, NULL, 733}, {"monitor-repaint", no_argument, NULL, 800}, {"diagnostics", no_argument, NULL, 801}, @@ -842,6 +858,10 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, opt->blur_deviation = atof(optarg); break; + case 331: opt->corner_radius = atoi(optarg); break; + case 332: condlst_add(&opt->rounded_corners_blacklist, optarg); break; + case 334: opt->round_borders = atoi(optarg); break; + case 335: condlst_add(&opt->round_borders_blacklist, optarg); break; P_CASEBOOL(733, experimental_backends); P_CASEBOOL(800, monitor_repaint); case 801: opt->print_diagnostics = true; break; diff --git a/src/picom.c b/src/picom.c index fd84081a3a..2137ddf816 100644 --- a/src/picom.c +++ b/src/picom.c @@ -484,6 +484,16 @@ static bool initialize_blur(session_t *ps) { return ps->backend_blur_context != NULL; } + +static bool initialize_round_corners(session_t *ps) { + struct round_corners_args cargs; + cargs.corner_radius = ps->o.corner_radius; + cargs.round_borders = ps->o.round_borders; + ps->backend_round_context = ps->backend_data->ops->create_round_context( + ps->backend_data, &cargs); + return ps->backend_round_context != NULL; +} + /// Init the backend and bind all the window pixmap to backend images static bool initialize_backend(session_t *ps) { if (ps->o.experimental_backends) { @@ -506,6 +516,11 @@ static bool initialize_backend(session_t *ps) { return false; } + if (!initialize_round_corners(ps)) { + log_fatal("Failed to prepare for rounded corners, will ignore..."); + ps->o.corner_radius = 0; + } + // window_stack shouldn't include window that's // not in the hash table at this point. Since // there cannot be any fading windows. @@ -694,8 +709,20 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) { w->frame_opacity = 1.0; } + // The below moved to it's own function: + // `win_determine_rounded_corners` (win.c) + /* + // Don't round full screen windows & excluded windows + if ((w && win_is_fullscreen(ps, w)) || + c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL)) { + w->corner_radius = 0; + } else { + w->corner_radius = ps->o.corner_radius; + } + */ + // Update window mode - w->mode = win_calc_mode(w); + w->mode = win_calc_mode(ps, w); // Destroy all reg_ignore above when frame opaque state changes on // SOLID mode @@ -775,11 +802,10 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) { ps->o.transparent_clipping) { // w->mode == WMODE_SOLID or WMODE_FRAME_TRANS region_t *tmp = rc_region_new(); - if (w->mode == WMODE_SOLID) { - *tmp = win_get_bounding_shape_global_by_val(w); - } else { - // w->mode == WMODE_FRAME_TRANS - win_get_region_noframe_local(w, tmp); + if (w->frame_opacity == 1) + *tmp = win_get_bounding_shape_global_by_val(w, false); + else { + win_get_region_noframe_local(w, tmp, false); pixman_region32_intersect(tmp, tmp, &w->bounding_shape); pixman_region32_translate(tmp, w->g.x, w->g.y); } @@ -1653,9 +1679,11 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .randr_exists = 0, .randr_event = 0, .randr_error = 0, +#ifdef CONFIG_OPENGL .glx_exists = false, .glx_event = 0, .glx_error = 0, +#endif .xrfilter_convolution_exists = false, .atoms_wintypes = {0}, @@ -1780,9 +1808,11 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ext_info = xcb_get_extension_data(ps->c, &xcb_glx_id); if (ext_info && ext_info->present) { +#if CONFIG_OPENGL ps->glx_exists = true; ps->glx_error = ext_info->first_error; ps->glx_event = ext_info->first_event; +#endif } // Parse configuration file @@ -1852,6 +1882,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, c2_list_postprocess(ps, ps->o.blur_background_blacklist) && c2_list_postprocess(ps, ps->o.invert_color_list) && c2_list_postprocess(ps, ps->o.opacity_rules) && + c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) && + c2_list_postprocess(ps, ps->o.round_borders_blacklist) && c2_list_postprocess(ps, ps->o.focus_blacklist))) { log_error("Post-processing of conditionals failed, some of your rules " "might not work"); @@ -2227,6 +2259,8 @@ static void session_destroy(session_t *ps) { free_wincondlst(&ps->o.opacity_rules); free_wincondlst(&ps->o.paint_blacklist); free_wincondlst(&ps->o.unredir_if_possible_blacklist); + free_wincondlst(&ps->o.rounded_corners_blacklist); + free_wincondlst(&ps->o.round_borders_blacklist); // Free tracked atom list { diff --git a/src/render.c b/src/render.c index c0e33e339f..fc704998cf 100644 --- a/src/render.c +++ b/src/render.c @@ -186,28 +186,138 @@ void free_paint(session_t *ps, paint_t *ppaint) { ppaint->pixmap = XCB_NONE; } -void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, double opacity, - bool argb, bool neg, xcb_render_picture_t pict, glx_texture_t *ptex, - const region_t *reg_paint, const glx_prog_main_t *pprogram) { +uint32_t +make_circle(int cx, int cy, int radius, uint32_t max_ntraps, xcb_render_trapezoid_t traps[]) { + uint32_t n = 0, k = 0; + int y1, y2; + double w; + while (k < max_ntraps) { + y1 = (int)(-radius * cos(M_PI * k / max_ntraps)); + traps[n].top = (cy + y1) << 16; + traps[n].left.p1.y = (cy + y1) << 16; + traps[n].right.p1.y = (cy + y1) << 16; + w = sqrt(radius * radius - y1 * y1) * 65536; + traps[n].left.p1.x = (int)((cx << 16) - w); + traps[n].right.p1.x = (int)((cx << 16) + w); + + do { + k++; + y2 = (int)(-radius * cos(M_PI * k / max_ntraps)); + } while (y1 == y2); + + traps[n].bottom = (cy + y2) << 16; + traps[n].left.p2.y = (cy + y2) << 16; + traps[n].right.p2.y = (cy + y2) << 16; + w = sqrt(radius * radius - y2 * y2) * 65536; + traps[n].left.p2.x = (int)((cx << 16) - w); + traps[n].right.p2.x = (int)((cx << 16) + w); + n++; + } + return n; +} + +uint32_t make_rectangle(int x, int y, int wid, int hei, xcb_render_trapezoid_t traps[]) { + traps[0].top = y << 16; + traps[0].left.p1.y = y << 16; + traps[0].left.p1.x = x << 16; + traps[0].left.p2.y = (y + hei) << 16; + traps[0].left.p2.x = x << 16; + traps[0].bottom = (y + hei) << 16; + traps[0].right.p1.x = (x + wid) << 16; + traps[0].right.p1.y = y << 16; + traps[0].right.p2.x = (x + wid) << 16; + traps[0].right.p2.y = (y + hei) << 16; + return 1; +} + +uint32_t make_rounded_window_shape(xcb_render_trapezoid_t traps[], uint32_t max_ntraps, int cr, int wid, int hei) +{ + uint32_t n = make_circle(cr, cr, cr, max_ntraps, traps); + n += make_circle(wid - cr, cr, cr, max_ntraps, traps + n); + n += make_circle(wid - cr, hei - cr, cr, max_ntraps, traps + n); + n += make_circle(cr, hei - cr, cr, max_ntraps, traps + n); + n += make_rectangle(0, cr, cr, hei - 2 * cr, traps + n); + n += make_rectangle(cr, 0, wid - 2 * cr, cr, traps + n); + n += make_rectangle(wid - cr, cr, cr, hei - 2 * cr, traps + n); + n += make_rectangle(cr, hei - cr, wid - 2 * cr, cr, traps + n); + n += make_rectangle(cr, cr, wid - 2 * cr, hei - 2 * cr, + traps + n); + return n; +} + +void render(session_t *ps, struct managed_win *w attr_unused, int x, int y, int dx, int dy, int wid, int hei, int fullwid, int fullhei, double opacity, + bool argb, bool neg, int cr, xcb_render_picture_t pict, glx_texture_t *ptex, + const region_t *reg_paint, const glx_prog_main_t *pprogram, clip_t *clip) { switch (ps->o.backend) { case BKEND_XRENDER: case BKEND_XR_GLX_HYBRID: { auto alpha_step = (int)(opacity * MAX_ALPHA); xcb_render_picture_t alpha_pict = ps->alpha_picts[alpha_step]; if (alpha_step != 0) { - uint8_t op = ((!argb && !alpha_pict) ? XCB_RENDER_PICT_OP_SRC - : XCB_RENDER_PICT_OP_OVER); - xcb_render_composite( - ps->c, op, pict, alpha_pict, ps->tgt_buffer.pict, - to_i16_checked(x), to_i16_checked(y), 0, 0, to_i16_checked(dx), - to_i16_checked(dy), to_u16_checked(wid), to_u16_checked(hei)); + if (cr) { + //log_warn("f(%d, %d) wh(%d %d) xy(%d %d) dxy(%d %d)", fullwid, fullhei, wid, hei, x, y, dx, dy); + xcb_render_picture_t p_tmp = x_create_picture_with_standard( + ps->c, ps->root, fullwid, fullhei, XCB_PICT_STANDARD_ARGB_32, 0, 0); + xcb_render_color_t trans = { + .red = 0, .blue = 0, .green = 0, .alpha = 0}; + const xcb_rectangle_t rect = {.x = 0, + .y = 0, + .width = to_u16_checked(fullwid), + .height = to_u16_checked(fullhei)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, + p_tmp, trans, 1, &rect); + + uint32_t max_ntraps = to_u32_checked(cr); + xcb_render_trapezoid_t traps[4 * max_ntraps + 5]; + + uint32_t n = make_rounded_window_shape(traps, max_ntraps, cr, fullwid, fullhei); + + xcb_render_trapezoids( + ps->c, XCB_RENDER_PICT_OP_OVER, alpha_pict, p_tmp, + x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), + 0, 0, n, traps); + + xcb_render_composite( + ps->c, XCB_RENDER_PICT_OP_OVER, pict, p_tmp, + ps->tgt_buffer.pict, to_i16_checked(x), to_i16_checked(y), + to_i16_checked(x), to_i16_checked(y), to_i16_checked(dx), to_i16_checked(dy), + to_u16_checked(wid), to_u16_checked(hei)); + + xcb_render_free_picture(ps->c, p_tmp); + + } else { + xcb_render_picture_t p_tmp = alpha_pict; + if(clip){ + p_tmp = x_create_picture_with_standard(ps->c, ps->root, wid, hei, XCB_PICT_STANDARD_ARGB_32, 0, 0); + + xcb_render_color_t black = {.red = 255, .blue = 255, .green = 255, .alpha = 255}; + const xcb_rectangle_t rect = {.x = 0, .y = 0, .width = to_u16_checked(wid), .height = to_u16_checked(hei)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, p_tmp, black, 1, &rect); + if(alpha_pict) { + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, alpha_pict, XCB_NONE, p_tmp, 0, 0, 0, 0, 0, 0, to_u16_checked(wid), to_u16_checked(hei)); + } + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OUT_REVERSE, clip->pict, XCB_NONE, p_tmp, 0, 0, 0, 0, to_i16_checked(clip->x), to_i16_checked(clip->y), to_u16_checked(wid), to_u16_checked(hei)); + } + uint8_t op = + ((!argb && !alpha_pict && !clip) ? XCB_RENDER_PICT_OP_SRC + : XCB_RENDER_PICT_OP_OVER); + + xcb_render_composite( + ps->c, op, pict, p_tmp, ps->tgt_buffer.pict, + to_i16_checked(x), to_i16_checked(y), 0, 0, + to_i16_checked(dx), to_i16_checked(dy), + to_u16_checked(wid), to_u16_checked(hei)); + if(clip){ + xcb_render_free_picture(ps->c, p_tmp); + } + } } break; } #ifdef CONFIG_OPENGL case BKEND_GLX: - glx_render(ps, ptex, x, y, dx, dy, wid, hei, ps->psglx->z, opacity, argb, - neg, reg_paint, pprogram); + glx_render(ps, w, ptex, x, y, dx, dy, wid, hei, ps->psglx->z, opacity, argb, + neg, cr, reg_paint, pprogram); ps->psglx->z += 1; break; #endif @@ -222,21 +332,24 @@ void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, doubl } static inline void -paint_region(session_t *ps, const struct managed_win *w, int x, int y, int wid, int hei, +paint_region(session_t *ps, struct managed_win *w, int x, int y, int wid, int hei, double opacity, const region_t *reg_paint, xcb_render_picture_t pict) { const int dx = (w ? w->g.x : 0) + x; const int dy = (w ? w->g.y : 0) + y; + const int fullwid = w ? w->widthb : 0; + const int fullhei = w ? w-> heightb : 0; const bool argb = (w && (win_has_alpha(w) || ps->o.force_win_blend)); const bool neg = (w && w->invert_color); - render(ps, x, y, dx, dy, wid, hei, opacity, argb, neg, pict, - (w ? w->paint.ptex : ps->root_tile_paint.ptex), reg_paint, + render(ps, w, x, y, dx, dy, wid, hei, fullwid, fullhei, opacity, argb, neg, + (w ? w->corner_radius : 0), + pict, (w ? w->paint.ptex : ps->root_tile_paint.ptex), reg_paint, #ifdef CONFIG_OPENGL w ? &ps->glx_prog_win : NULL #else NULL #endif - ); + , XCB_NONE); } /** @@ -259,6 +372,41 @@ static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) { return true; } +/** + * Rounde the corners of a window. + * Applies a fragment shader to discard corners + * + */ +static inline void +win_round_corners(session_t *ps, struct managed_win *w attr_unused, float cr attr_unused, + xcb_render_picture_t tgt_buffer attr_unused, const region_t *reg_paint) { +#ifdef CONFIG_OPENGL + const int16_t x = w->g.x; + const int16_t y = w->g.y; + const auto wid = to_u16_checked(w->widthb); + const auto hei = to_u16_checked(w->heightb); +#endif + + //log_debug("x:%d y:%d w:%d h:%d", x, y, wid, hei); + + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: { + // XRender method is implemented inside render() + } break; +#ifdef CONFIG_OPENGL + case BKEND_GLX: + glx_round_corners_dst(ps, w, w->glx_texture_bg, x, y, wid, hei, + (float)ps->psglx->z - 0.5f, cr, reg_paint, &w->glx_round_cache); + break; +#endif + default: assert(0); + } +#ifndef CONFIG_OPENGL + (void)reg_paint; +#endif +} + /** * Paint a window itself and dim it if asked. */ @@ -302,7 +450,7 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) log_error("Window %#010x is missing painting data.", w->base.id); return; } - + const int x = w->g.x; const int y = w->g.y; const uint16_t wid = to_u16_checked(w->widthb); @@ -612,9 +760,29 @@ win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) { return; } - render(ps, 0, 0, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, w->shadow_width, - w->shadow_height, w->shadow_opacity, true, false, w->shadow_paint.pict, - w->shadow_paint.ptex, reg_paint, NULL); + xcb_render_picture_t td = XCB_NONE; + if (w->corner_radius) { + uint32_t max_ntraps = to_u32_checked(w->corner_radius); + xcb_render_trapezoid_t traps[4 * max_ntraps + 5]; + uint32_t n = make_rounded_window_shape(traps, max_ntraps, w->corner_radius, w->widthb, w->heightb); + + td = x_create_picture_with_standard(ps->c, ps->root, w->widthb, w->heightb, XCB_PICT_STANDARD_ARGB_32, 0, 0); + xcb_render_color_t trans = {.red = 0, .blue = 0, .green = 0, .alpha = 0}; + const xcb_rectangle_t rect = {.x = 0, .y = 0, .width = to_u16_checked(w->widthb), .height = to_u16_checked(w->heightb)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td, trans, 1, &rect); + + auto solid = solid_picture(ps->c, ps->root, false, 1, 0, 0, 0); + xcb_render_trapezoids(ps->c, XCB_RENDER_PICT_OP_OVER, solid, td, x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), 0, 0, n, traps); + xcb_render_free_picture(ps->c, solid); + } + + clip_t clip = { .pict = td, -(w->shadow_dx), .y = -(w->shadow_dy) }; + render(ps, w, 0, 0, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, w->shadow_width, + w->shadow_height, w->widthb, w->heightb, w->shadow_opacity, true, false, 0, w->shadow_paint.pict, + w->shadow_paint.ptex, reg_paint, NULL, w->corner_radius ? &clip : NULL); + if(td){ + xcb_render_free_picture(ps->c, td); + } } /** @@ -634,7 +802,7 @@ win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) { */ static bool xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t x, int16_t y, uint16_t wid, uint16_t hei, struct x_convolution_kernel **blur_kerns, - int nkernels, const region_t *reg_clip) { + int nkernels, const region_t *reg_clip, xcb_render_picture_t rounded) { assert(blur_kerns); assert(blur_kerns[0]); @@ -679,7 +847,7 @@ static bool xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t } if (src_pict != tgt_buffer) - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict, rounded, tgt_buffer, 0, 0, 0, 0, x, y, wid, hei); free_picture(ps->c, &tmp_picture); @@ -697,6 +865,7 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t const int16_t y = w->g.y; const auto wid = to_u16_checked(w->widthb); const auto hei = to_u16_checked(w->heightb); + const int cr = (w ? w->corner_radius : 0); double factor_center = 1.0; // Adjust blur strength according to window opacity, to make it appear @@ -728,21 +897,42 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t &ps->blur_kerns_cache[i]); } + xcb_render_picture_t td = XCB_NONE; + if (cr) { + uint32_t max_ntraps = to_u32_checked(cr); + xcb_render_trapezoid_t traps[4 * max_ntraps + 5]; + uint32_t n = make_rounded_window_shape(traps, max_ntraps, cr, wid, hei); + + td = x_create_picture_with_standard(ps->c, ps->root, wid, hei, XCB_PICT_STANDARD_ARGB_32, 0, 0); + xcb_render_color_t trans = {.red = 0, .blue = 0, .green = 0, .alpha = 0}; + const xcb_rectangle_t rect = {.x = 0, .y = 0, .width = to_u16_checked(wid), .height = to_u16_checked(hei)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td, trans, 1, &rect); + + auto solid = solid_picture(ps->c, ps->root, false, 1, 0, 0, 0); + + xcb_render_trapezoids(ps->c, XCB_RENDER_PICT_OP_OVER, solid, td, x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), 0, 0, n, traps); + xcb_render_free_picture(ps->c, solid); + } + // Minimize the region we try to blur, if the window itself is not // opaque, only the frame is. - region_t reg_blur = win_get_bounding_shape_global_by_val(w); + region_t reg_blur = win_get_bounding_shape_global_by_val(w, true); if (w->mode == WMODE_FRAME_TRANS && !ps->o.force_win_blend) { region_t reg_noframe; pixman_region32_init(®_noframe); - win_get_region_noframe_local(w, ®_noframe); + win_get_region_noframe_local(w, ®_noframe, true); pixman_region32_translate(®_noframe, w->g.x, w->g.y); pixman_region32_subtract(®_blur, ®_blur, ®_noframe); pixman_region32_fini(®_noframe); } + // Translate global coordinates to local ones pixman_region32_translate(®_blur, -x, -y); xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache, - ps->o.blur_kernel_count, ®_blur); + ps->o.blur_kernel_count, ®_blur, td); + if(td){ + xcb_render_free_picture(ps->c, td); + } pixman_region32_clear(®_blur); } break; #ifdef CONFIG_OPENGL @@ -851,7 +1041,9 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { // // Whether this is beneficial is to be determined XXX for (auto w = t; w; w = w->prev_trans) { - region_t bshape = win_get_bounding_shape_global_by_val(w); + region_t bshape_no_corners = win_get_bounding_shape_global_by_val(w, false); + region_t bshape_corners = win_get_bounding_shape_global_by_val(w, true); + // Painting shadow if (w->shadow) { // Lazy shadow building @@ -880,7 +1072,7 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { // saving GPU power and handling shaped windows (XXX // unconfirmed) if (!ps->o.wintype_option[w->window_type].full_shadow) - pixman_region32_subtract(®_tmp, ®_tmp, &bshape); + pixman_region32_subtract(®_tmp, ®_tmp, &bshape_no_corners); if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0 && w->xinerama_scr < ps->xinerama_nscrs) @@ -907,11 +1099,24 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { // Remember, reg_ignore is the union of all windows above the current // window. pixman_region32_subtract(®_tmp, ®ion, w->reg_ignore); - pixman_region32_intersect(®_tmp, ®_tmp, &bshape); - pixman_region32_fini(&bshape); + pixman_region32_intersect(®_tmp, ®_tmp, &bshape_corners); + pixman_region32_fini(&bshape_corners); + pixman_region32_fini(&bshape_no_corners); if (pixman_region32_not_empty(®_tmp)) { set_tgt_clip(ps, ®_tmp); + +#ifdef CONFIG_OPENGL + // If rounded corners backup the region first + if (w->corner_radius > 0) { + const int16_t x = w->g.x; + const int16_t y = w->g.y; + const auto wid = to_u16_checked(w->widthb); + const auto hei = to_u16_checked(w->heightb); + glx_bind_texture(ps, &w->glx_texture_bg, x, y, wid, hei, false); + } +#endif + // Blur window background if (w->blur_background && (w->mode == WMODE_TRANS || @@ -921,6 +1126,12 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { // Painting the window paint_one(ps, w, ®_tmp); + + // Round window corners + if (w->corner_radius > 0) { + win_round_corners(ps, w, (float)w->corner_radius, + ps->tgt_buffer.pict, ®_tmp); + } } } @@ -1009,8 +1220,8 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { else glFlush(); glXWaitX(); - glx_render(ps, ps->tgt_buffer.ptex, 0, 0, 0, 0, ps->root_width, - ps->root_height, 0, 1.0, false, false, ®ion, NULL); + glx_render(ps, t, ps->tgt_buffer.ptex, 0, 0, 0, 0, ps->root_width, + ps->root_height, 0, 1.0, false, false, 0, ®ion, NULL); // falls through case BKEND_GLX: glXSwapBuffers(ps->dpy, get_tgt_window(ps)); break; #endif @@ -1174,6 +1385,18 @@ bool init_render(session_t *ps) { return false; } } + + // Initialize our rounded corners fragment shader + if (ps->o.corner_radius > 0 && ps->o.backend == BKEND_GLX) { +#ifdef CONFIG_OPENGL + if (!glx_init_rounded_corners(ps)) { + log_error("Failed to init rounded corners shader."); + return false; + } +#else + assert(false); +#endif + } return true; } diff --git a/src/render.h b/src/render.h index 92b71c8e01..335a020bdd 100644 --- a/src/render.h +++ b/src/render.h @@ -25,9 +25,16 @@ typedef struct paint { #endif } paint_t; -void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, double opacity, - bool argb, bool neg, xcb_render_picture_t pict, glx_texture_t *ptex, - const region_t *reg_paint, const glx_prog_main_t *pprogram); + +typedef struct clip { + xcb_render_picture_t pict; + int x; + int y; +} clip_t; + +void render(session_t *ps, struct managed_win *, int x, int y, int dx, int dy, int w, int h, int fullw, int fullh, double opacity, + bool argb, bool neg, int cr, xcb_render_picture_t pict, glx_texture_t *ptex, + const region_t *reg_paint, const glx_prog_main_t *pprogram, clip_t *clip); void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint); void paint_all(session_t *ps, struct managed_win *const t, bool ignore_damage); diff --git a/src/win.c b/src/win.c index 7d95b947db..7ca2f9cd42 100644 --- a/src/win.c +++ b/src/win.c @@ -56,6 +56,17 @@ static const int WIN_GET_LEADER_MAX_RECURSION = 20; static const int ROUNDED_PIXELS = 1; static const double ROUNDED_PERCENT = 0.05; +/// Generate a "return by value" function, from a function that returns the +/// region via a region_t pointer argument. +/// Function signature has to be (win *, region_t *, bool) +#define gen_by_val_corners(fun) \ + region_t fun##_by_val(const struct managed_win *w, bool include_corners) { \ + region_t ret; \ + pixman_region32_init(&ret); \ + fun(w, &ret, include_corners); \ + return ret; \ + } + /// Generate a "return by value" function, from a function that returns the /// region via a region_t pointer argument. /// Function signature has to be (win *, region_t *) @@ -173,16 +184,18 @@ static inline bool group_is_focused(session_t *ps, xcb_window_t leader) { /** * Get a rectangular region a window occupies, excluding shadow. */ -static void win_get_region_local(const struct managed_win *w, region_t *res) { +static void win_get_region_local(const struct managed_win *w, region_t *res, bool include_corners) { assert(w->widthb >= 0 && w->heightb >= 0); pixman_region32_fini(res); pixman_region32_init_rect(res, 0, 0, (uint)w->widthb, (uint)w->heightb); + + if(!include_corners) win_region_remove_corners(w, res); } /** * Get a rectangular region a window occupies, excluding frame and shadow. */ -void win_get_region_noframe_local(const struct managed_win *w, region_t *res) { +void win_get_region_noframe_local(const struct managed_win *w, region_t *res, bool include_corners) { const margin_t extents = win_calc_frame_extents(w); int x = extents.left; @@ -193,10 +206,11 @@ void win_get_region_noframe_local(const struct managed_win *w, region_t *res) { pixman_region32_fini(res); if (width > 0 && height > 0) { pixman_region32_init_rect(res, x, y, (uint)width, (uint)height); + if(!include_corners) win_region_remove_corners(w, res); } } -void win_get_region_frame_local(const struct managed_win *w, region_t *res) { +void win_get_region_frame_local(const struct managed_win *w, region_t *res, bool include_corners) { const margin_t extents = win_calc_frame_extents(w); auto outer_width = extents.left + extents.right + w->g.width; auto outer_height = extents.top + extents.bottom + w->g.height; @@ -219,10 +233,11 @@ void win_get_region_frame_local(const struct managed_win *w, region_t *res) { region_t reg_win; pixman_region32_init_rects(®_win, (rect_t[]){0, 0, outer_width, outer_height}, 1); pixman_region32_intersect(res, ®_win, res); + if(!include_corners) win_region_remove_corners(w, res); pixman_region32_fini(®_win); } -gen_by_val(win_get_region_frame_local); +gen_by_val_corners(win_get_region_frame_local); /** * Add a window to damaged area. @@ -538,11 +553,15 @@ bool win_client_has_alpha(const struct managed_win *w) { w->client_pictfmt->direct.alpha_mask; } -winmode_t win_calc_mode(const struct managed_win *w) { +winmode_t win_calc_mode(session_t *ps, const struct managed_win *w) { if (w->opacity < 1.0) { return WMODE_TRANS; } + if (ps->o.backend == BKEND_GLX && w->corner_radius > 0) { + return WMODE_TRANS; + } + if (win_has_alpha(w)) { if (w->client_win == XCB_NONE) { // This is a window not managed by the WM, and it has alpha, @@ -875,6 +894,33 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w) win_set_blur_background(ps, w, blur_background_new); } +/** + * Determine if a window should have rounded corners. + */ +static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) { + if (w->a.map_state != XCB_MAP_STATE_VIEWABLE || ps->o.corner_radius == 0) + return; + + // Don't round full screen windows & excluded windows + if ((w && win_is_fullscreen(ps, w)) || + c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL)) { + w->corner_radius = 0; + //log_warn("xy(%d %d) wh(%d %d) will NOT round corners", w->g.x, w->g.y, w->widthb, w->heightb); + } else { + w->corner_radius = ps->o.corner_radius; + //log_warn("xy(%d %d) wh(%d %d) will round corners", w->g.x, w->g.y, w->widthb, w->heightb); + // HACK: we reset this so we can query the color once + // we query the color in glx_round_corners_dst0 using glReadPixels + //w->border_col = { -1., -1, -1, -1 }; + w->border_col[0] = w->border_col[1] = w->border_col[2] = w->border_col[3] = -1.0; + if (w && c2_match(ps, w, ps->o.round_borders_blacklist, NULL)) { + w->round_borders = 0; + } else { + w->round_borders = ps->o.round_borders; + } + } +} + /** * Update window opacity according to opacity rules. */ @@ -908,6 +954,7 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) { win_determine_shadow(ps, w); win_determine_invert_color(ps, w); win_determine_blur_background(ps, w); + win_determine_rounded_corners(ps, w); win_update_opacity_rule(ps, w); if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) w->paint_excluded = c2_match(ps, w, ps->o.paint_blacklist, NULL); @@ -1227,6 +1274,8 @@ struct win *fill_win(session_t *ps, struct win *w) { // Initialized during paint .paint = PAINT_INIT, .shadow_paint = PAINT_INIT, + + .corner_radius = 0, }; assert(!w->destroyed); @@ -1516,7 +1565,7 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) { pixman_region32_clear(&w->bounding_shape); // Start with the window rectangular region - win_get_region_local(w, &w->bounding_shape); + win_get_region_local(w, &w->bounding_shape, true); // Only request for a bounding region if the window is shaped // (while loop is used to avoid goto, not an actual loop) @@ -2065,7 +2114,7 @@ void map_win_start(session_t *ps, struct managed_win *w) { } // Update window mode here to check for ARGB windows - w->mode = win_calc_mode(w); + w->mode = win_calc_mode(ps, w); // Detect client window here instead of in add_win() as the client // window should have been prepared at this point @@ -2102,6 +2151,7 @@ void map_win_start(session_t *ps, struct managed_win *w) { w->opacity, w->opacity_target); win_determine_blur_background(ps, w); + win_determine_rounded_corners(ps, w); // Cannot set w->ever_damaged = false here, since window mapping could be // delayed, so a damage event might have already arrived before this function diff --git a/src/win.h b/src/win.h index 68037b19ee..5c13927ad1 100644 --- a/src/win.h +++ b/src/win.h @@ -202,6 +202,11 @@ struct managed_win { /// Last window opacity value set by the rules. double opacity_set; + /// Corner radius + int corner_radius; + bool round_borders; + float border_col[4]; + // Fading-related members /// Override value of window fade state. Set by D-Bus method calls. switch_t fade_force; @@ -249,6 +254,9 @@ struct managed_win { #ifdef CONFIG_OPENGL /// Textures and FBO background blur use. glx_blur_cache_t glx_blur_cache; + glx_blur_cache_t glx_round_cache; + /// Background texture of the window + glx_texture_t *glx_texture_bg; #endif }; @@ -279,7 +287,7 @@ bool must_use destroy_win_start(session_t *ps, struct win *w); void win_release_images(struct backend_base *base, struct managed_win *w); int win_update_name(session_t *ps, struct managed_win *w); int win_get_role(session_t *ps, struct managed_win *w); -winmode_t attr_pure win_calc_mode(const struct managed_win *w); +winmode_t attr_pure win_calc_mode(session_t *ps, const struct managed_win *w); void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val); void win_set_fade_force(struct managed_win *w, switch_t val); void win_set_focused_force(session_t *ps, struct managed_win *w, switch_t val); @@ -357,12 +365,12 @@ void add_damage_from_win(session_t *ps, const struct managed_win *w); * * Return region in global coordinates. */ -void win_get_region_noframe_local(const struct managed_win *w, region_t *); +void win_get_region_noframe_local(const struct managed_win *w, region_t *, bool include_corners); /// Get the region for the frame of the window -void win_get_region_frame_local(const struct managed_win *w, region_t *res); +void win_get_region_frame_local(const struct managed_win *w, region_t *res, bool include_corners); /// Get the region for the frame of the window, by value -region_t win_get_region_frame_local_by_val(const struct managed_win *w); +region_t win_get_region_frame_local_by_val(const struct managed_win *w, bool include_corners); /** * Retrieve frame extents from a window. */ @@ -438,10 +446,26 @@ struct managed_win *attr_pure win_stack_find_next_managed(const session_t *ps, /// Free all resources in a struct win void free_win_res(session_t *ps, struct managed_win *w); -static inline region_t win_get_bounding_shape_global_by_val(struct managed_win *w) { +static inline void win_region_remove_corners(const struct managed_win *w, region_t *res) { + region_t corners; + pixman_region32_init_rects( + &corners, + (rect_t[]){ + {.x1 = 0, .y1 = 0, .x2 = w->corner_radius, .y2 = w->corner_radius}, + {.x1 = 0, .y1 = w->heightb-w->corner_radius, .x2 = w->corner_radius, .y2 = w->heightb}, + {.x1 = w->widthb-w->corner_radius, .y1 = 0, .x2 = w->widthb, .y2 = w->corner_radius}, + {.x1 = w->widthb-w->corner_radius, .y1 = w->heightb-w->corner_radius, .x2 = w->widthb, .y2 = w->heightb}, + }, + 4); + pixman_region32_subtract(res, res, &corners); + pixman_region32_fini(&corners); +} + +static inline region_t win_get_bounding_shape_global_by_val(struct managed_win *w, bool include_corners) { region_t ret; pixman_region32_init(&ret); pixman_region32_copy(&ret, &w->bounding_shape); + if(!include_corners) win_region_remove_corners(w, &ret); pixman_region32_translate(&ret, w->g.x, w->g.y); return ret; } diff --git a/src/x.c b/src/x.c index 1b359faed0..d75d0171b8 100644 --- a/src/x.c +++ b/src/x.c @@ -168,6 +168,15 @@ xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_ return x_get_visual_for_pictfmt(g_pictfmts, pictfmt->id); } +xcb_render_pictformat_t +x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) { + x_get_server_pictfmts(c); + + auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std); + + return pictfmt->id; +} + int x_get_visual_depth(xcb_connection_t *c, xcb_visualid_t visual) { auto setup = xcb_get_setup(c); for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; @@ -231,6 +240,17 @@ x_create_picture_with_standard_and_pixmap(xcb_connection_t *c, xcb_pict_standard return x_create_picture_with_pictfmt_and_pixmap(c, pictfmt, pixmap, valuemask, attr); } +xcb_render_picture_t +x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h, + xcb_pict_standard_t standard, uint32_t valuemask, + const xcb_render_create_picture_value_list_t *attr) { + x_get_server_pictfmts(c); + + auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, standard); + assert(pictfmt); + return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr); +} + /** * Create an picture. */ @@ -362,6 +382,7 @@ _x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_c CASESTRRET2(RENDER_GLYPH); } +#ifdef CONFIG_OPENGL if (ps->glx_exists) { o = error_code - ps->glx_error; switch (o) { @@ -381,6 +402,7 @@ _x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_c CASESTRRET2(GLX_GLX_BAD_PROFILE_ARB); } } +#endif if (ps->xsync_exists) { o = error_code - ps->xsync_error; diff --git a/src/x.h b/src/x.h index e408428a0c..fb57ce3062 100644 --- a/src/x.h +++ b/src/x.h @@ -172,6 +172,12 @@ x_create_picture_with_standard_and_pixmap(xcb_connection_t *, xcb_pict_standard_ const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1); +xcb_render_picture_t +x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h, + xcb_pict_standard_t standard, uint32_t valuemask, + const xcb_render_create_picture_value_list_t *attr) + attr_nonnull(1); + /** * Create an picture. */ @@ -261,6 +267,9 @@ struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); +xcb_render_pictformat_t +x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); + xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen); uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c);