From de31cd40960a527aa6f8c42694d9c0fe8d3f1fbf Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 16 Jul 2021 19:11:23 +0100 Subject: [PATCH 1/8] backend: add new image property: corner radius Signed-off-by: Yuxuan Shui --- src/backend/backend.h | 4 ++++ src/backend/backend_common.c | 1 + src/backend/backend_common.h | 1 + 3 files changed, 6 insertions(+) diff --git a/src/backend/backend.h b/src/backend/backend.h index 775f570..613073a 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -36,6 +36,7 @@ typedef void (*backend_ready_callback_t)(void *); // particular order: // // Color inversion -> Dimming -> Opacity multiply -> Limit maximum brightness +// (Corner radius could be applied in any order) enum image_properties { // Whether the color of the image is inverted // 1 boolean, default: false @@ -54,6 +55,9 @@ enum image_properties { // brightness down to the max brightness value. // 1 double, default: 1 IMAGE_PROPERTY_MAX_BRIGHTNESS, + // Gives the image a rounded corner. + // 1 double, default: 0 + IMAGE_PROPERTY_CORNER_RADIUS, }; enum image_operations { diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index 1608827..0f8b06e 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -449,6 +449,7 @@ bool default_set_image_property(backend_t *base attr_unused, enum image_properti tex->ewidth = iargs[0]; tex->eheight = iargs[1]; break; + case IMAGE_PROPERTY_CORNER_RADIUS: tex->corner_radius = dargs[0]; break; case IMAGE_PROPERTY_MAX_BRIGHTNESS: tex->max_brightness = dargs[0]; break; } diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h index 702be5c..1aaf133 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -37,6 +37,7 @@ struct backend_image { double opacity; double dim; double max_brightness; + double corner_radius; // Effective size of the image int ewidth, eheight; bool color_inverted; From 1fec938740149924e66e799e97dba108c7a3f5cf Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 17 Jul 2021 00:01:09 +0100 Subject: [PATCH 2/8] backend: gl_common: handle corner radius property Signed-off-by: Yuxuan Shui --- src/backend/gl/gl_common.c | 18 ++++++++++++++++++ src/backend/gl/gl_common.h | 1 + 2 files changed, 19 insertions(+) diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 7cb7e6b..c0480fc 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -401,6 +401,9 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe if (gd->win_shader.unifm_max_brightness >= 0) { glUniform1f(gd->win_shader.unifm_max_brightness, (float)img->max_brightness); } + if (gd->win_shader.unifm_corner_radius >= 0) { + glUniform1f(gd->win_shader.unifm_corner_radius, (float)img->corner_radius); + } // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", // x, y, width, height, dx, dy, ptex->width, ptex->height, z); @@ -900,6 +903,8 @@ static int gl_win_shader_from_string(const char *vshader_str, const char *fshade ret->unifm_brightness = glGetUniformLocationChecked(ret->prog, "brightness"); ret->unifm_max_brightness = glGetUniformLocationChecked(ret->prog, "max_brightness"); + ret->unifm_corner_radius = + glGetUniformLocationChecked(ret->prog, "corner_radius"); gl_check_err(); @@ -1534,11 +1539,18 @@ void gl_get_blur_size(void *blur_context, int *width, int *height) { const char *win_shader_glsl = GLSL(330, uniform float opacity; uniform float dim; + uniform float corner_radius; uniform bool invert_color; in vec2 texcoord; uniform sampler2D tex; uniform sampler2D brightness; uniform float max_brightness; + // Signed distance field for rectangle center at (0, 0), with size of + // half_size * 2 + float rectangle_sdf(vec2 point, vec2 half_size) { + vec2 d = abs(point) - half_size; + return length(max(d, 0.0)); + } void main() { vec4 c = texelFetch(tex, ivec2(texcoord), 0); @@ -1555,6 +1567,12 @@ const char *win_shader_glsl = GLSL(330, if (brightness > max_brightness) c.rgb = c.rgb * (max_brightness / brightness); + vec2 outer_size = vec2(textureSize(tex, 0)); + vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f; + float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f, + inner_size / 2.0f) - corner_radius; + c *= 1.0f - clamp(rect_distance, 0.0f, 1.0f); + gl_FragColor = c; } ); diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 4fb50c8..3daf746 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -22,6 +22,7 @@ typedef struct { GLint unifm_dim; GLint unifm_brightness; GLint unifm_max_brightness; + GLint unifm_corner_radius; } gl_win_shader_t; // Program and uniforms for brightness shader From 6d72bf2974384edafa6cb298e7df9bfc0125af2a Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 23 Oct 2021 12:24:21 +0100 Subject: [PATCH 3/8] options: don't disable rounded corner for new backends Signed-off-by: Yuxuan Shui --- src/options.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/options.c b/src/options.c index 1f316fe..1b0a5dc 100644 --- a/src/options.c +++ b/src/options.c @@ -1013,12 +1013,6 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, "properly under X Render backend."); } - if (opt->corner_radius > 0 && opt->experimental_backends) { - log_warn("Rounded corner is only supported on legacy backends, it " - "will be disabled"); - opt->corner_radius = 0; - } - return true; } From d397b307dd9cf7b37c3b2aba903b2b91a80b31a9 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 23 Oct 2021 12:52:50 +0100 Subject: [PATCH 4/8] backend: set corner radius property Signed-off-by: Yuxuan Shui --- src/backend/backend.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backend/backend.c b/src/backend/backend.c index 42ce7b2..34806b1 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -339,6 +339,9 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { &dim_opacity); ps->backend_data->ops->set_image_property( ps->backend_data, IMAGE_PROPERTY_OPACITY, w->win_image, &w->opacity); + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS, w->win_image, + (double[]){w->corner_radius}); } if (w->opacity * MAX_ALPHA < 1) { From 76db8cca06a7e3a9a1a928a6e8723f7afd9742fe Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 21 Oct 2021 18:19:23 +0100 Subject: [PATCH 5/8] backend: xrender: handle corner radius Signed-off-by: Yuxuan Shui --- src/backend/xrender/xrender.c | 123 ++++++++++++++++++++++++++++++---- src/x.c | 4 -- src/x.h | 4 ++ 3 files changed, 114 insertions(+), 17 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 32ddabf..d01f00a 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -88,9 +88,74 @@ struct _xrender_image_data_inner { bool owned; }; -static void compose_impl(struct _xrender_data *xd, const struct backend_image *img, - int dst_x, int dst_y, const region_t *reg_paint, +struct xrender_image { + struct backend_image base; + + // A triangulated rounded rectangle + xcb_render_pointfix_t *rounded_rectangle_triangles; + // Number of points in the above list of triangles + int triangle_count; +}; + +/// Make a picture of size width x height, which has a rounded rectangle of corner_radius +/// rendered in it. +xcb_render_pointfix_t * +make_rounded_corner_points(int width, int height, int corner_radius, int *out_point_count) { + int inner_height = height - 2 * corner_radius; + int cap_height = corner_radius; + if (inner_height < 0) { + cap_height = height / 2; + inner_height = 0; + } + auto points = ccalloc(cap_height * 4 + 4, xcb_render_pointfix_t); + int point_count = 0; + +#define ADD_POINT(px, py) \ + assert(point_count < cap_height * 4 + 4); \ + points[point_count].x = DOUBLE_TO_XFIXED(px); \ + points[point_count].y = DOUBLE_TO_XFIXED(py); \ + point_count += 1; + + // The top cap + for (int i = 0; i <= cap_height; i++) { + double y = corner_radius - i; + double delta = sqrt(corner_radius * corner_radius - y * y); + double left = corner_radius - delta; + double right = width - corner_radius + delta; + if (left >= right) { + continue; + } + ADD_POINT(left, i); + ADD_POINT(right, i); + } + + // The middle rectangle + if (inner_height > 0) { + ADD_POINT(0, cap_height + inner_height); + ADD_POINT(width, cap_height + inner_height); + } + + // The bottom cap + for (int i = cap_height + inner_height + 1; i <= height; i++) { + double y = corner_radius - (height - i); + double delta = sqrt(corner_radius * corner_radius - y * y); + double left = corner_radius - delta; + double right = width - corner_radius + delta; + if (left >= right) { + break; + } + ADD_POINT(left, i); + ADD_POINT(right, i); + } +#undef ADD_POINT + *out_point_count = point_count; + return points; +} + +static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, int dst_x, + int dst_y, const region_t *reg_paint, const region_t *reg_visible, xcb_render_picture_t result) { + const struct backend_image *img = &xrimg->base; auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)]; auto inner = (struct _xrender_image_data_inner *)img->inner; region_t reg; @@ -110,7 +175,12 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i pixman_region32_init(®); pixman_region32_intersect(®, (region_t *)reg_paint, (region_t *)reg_visible); x_set_picture_clip_region(xd->base.c, result, 0, 0, ®); - if ((img->color_inverted || img->dim != 0) && has_alpha) { + if (img->corner_radius != 0 && xrimg->rounded_rectangle_triangles == NULL) { + xrimg->rounded_rectangle_triangles = make_rounded_corner_points( + inner->width, inner->height, (int)img->corner_radius, + &xrimg->triangle_count); + } + if (((img->color_inverted || img->dim != 0) && has_alpha) || img->corner_radius != 0) { // Apply image properties using a temporary image, because the source // image is transparent. Otherwise the properties can be applied directly // on the target image. @@ -159,6 +229,15 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i tmp_pict, dim_color, 1, &rect); } + if (img->corner_radius != 0) { + // Clip tmp_pict with a rounded rectangle + xcb_render_tri_strip( + xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, xd->white_pixel, tmp_pict, + x_get_pictfmt_for_standard(xd->base.c, XCB_PICT_STANDARD_A_8), + 0, 0, (uint32_t)xrimg->triangle_count, + xrimg->rounded_rectangle_triangles); + } + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict, alpha_pict, result, 0, 0, 0, 0, to_i16_checked(dst_x), to_i16_checked(dst_y), tmpew, tmpeh); @@ -350,11 +429,11 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool return NULL; } - auto img = ccalloc(1, struct backend_image); + auto img = ccalloc(1, struct xrender_image); auto inner = ccalloc(1, struct _xrender_image_data_inner); inner->depth = (uint8_t)fmt.visual_depth; - inner->width = img->ewidth = r->width; - inner->height = img->eheight = r->height; + inner->width = img->base.ewidth = r->width; + inner->height = img->base.eheight = r->height; inner->pixmap = pixmap; inner->has_alpha = fmt.alpha_size != 0; inner->pict = @@ -363,8 +442,10 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool inner->visual = fmt.visual; inner->refcount = 1; - img->inner = (struct backend_image_inner_base *)inner; - img->opacity = 1; + img->base.inner = (struct backend_image_inner_base *)inner; + img->base.opacity = 1; + img->rounded_rectangle_triangles = NULL; + img->triangle_count = 0; free(r); if (inner->pict == XCB_NONE) { @@ -382,10 +463,12 @@ static void release_image_inner(backend_t *base, struct _xrender_image_data_inne free(inner); } static void release_image(backend_t *base, void *image) { - struct backend_image *img = image; - img->inner->refcount--; - if (img->inner->refcount == 0) { - release_image_inner(base, (void *)img->inner); + struct xrender_image *img = image; + free(img->rounded_rectangle_triangles); + img->triangle_count = 0; + img->base.inner->refcount--; + if (img->base.inner->refcount == 0) { + release_image_inner(base, (void *)img->base.inner); } free(img); } @@ -735,6 +818,20 @@ err: return NULL; } +static bool +set_image_property(backend_t *base, enum image_properties op, void *image, void *args) { + auto xrimg = (struct xrender_image *)image; + if (op == IMAGE_PROPERTY_CORNER_RADIUS && + ((double *)args)[0] != xrimg->base.corner_radius && + xrimg->rounded_rectangle_triangles != NULL) { + // Free cached rounded rectangle if corner radius changed + free(xrimg->rounded_rectangle_triangles); + xrimg->triangle_count = 0; + xrimg->rounded_rectangle_triangles = NULL; + } + return default_set_image_property(base, op, image, args); +} + struct backend_operations xrender_ops = { .init = backend_xrender_init, .deinit = deinit, @@ -754,7 +851,7 @@ struct backend_operations xrender_ops = { .image_op = image_op, .read_pixel = read_pixel, .clone_image = default_clone_image, - .set_image_property = default_set_image_property, + .set_image_property = set_image_property, .create_blur_context = create_blur_context, .destroy_blur_context = destroy_blur_context, .get_blur_size = get_blur_size, diff --git a/src/x.c b/src/x.c index c146f48..795211d 100644 --- a/src/x.c +++ b/src/x.c @@ -652,10 +652,6 @@ err: return false; } -// xcb-render specific macros -#define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536) -#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536)) - /** * Convert a struct conv to a X picture convolution filter, normalizing the kernel * in the process. Allow the caller to specify the element at the center of the kernel, diff --git a/src/x.h b/src/x.h index e01aa0a..3b8787c 100644 --- a/src/x.h +++ b/src/x.h @@ -87,6 +87,10 @@ struct xvisual_info { #define log_fatal_x_error(e, fmt, ...) \ LOG(FATAL, fmt " (%s)", ##__VA_ARGS__, x_strerror(e)) +// xcb-render specific macros +#define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536) +#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536)) + /// Wraps x_new_id. abort the program if x_new_id returns error static inline uint32_t x_new_id(xcb_connection_t *c) { auto ret = xcb_generate_id(c); From 1dea294051e4ba7aa2746b071ee2f64f68f520c5 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 21 Oct 2021 18:28:01 +0100 Subject: [PATCH 6/8] backend: xrender: cache rounded rectangle mask xcb_render_triangle is slow because (at least for Glamor) it's rasterizing the triangles on CPU. Signed-off-by: Yuxuan Shui --- src/backend/xrender/xrender.c | 57 +++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index d01f00a..5f17020 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -91,16 +91,22 @@ struct _xrender_image_data_inner { struct xrender_image { struct backend_image base; - // A triangulated rounded rectangle - xcb_render_pointfix_t *rounded_rectangle_triangles; - // Number of points in the above list of triangles - int triangle_count; + // A cached picture of a rounded rectangle. Xorg rasterizes shapes on CPU so it's + // exceedingly slow. + xcb_render_picture_t rounded_rectangle; }; /// Make a picture of size width x height, which has a rounded rectangle of corner_radius /// rendered in it. -xcb_render_pointfix_t * -make_rounded_corner_points(int width, int height, int corner_radius, int *out_point_count) { +xcb_render_picture_t +make_rounded_corner_picture(xcb_connection_t *c, xcb_render_picture_t src, + xcb_drawable_t root, int width, int height, int corner_radius) { + auto picture = x_create_picture_with_standard(c, root, width, height, + XCB_PICT_STANDARD_ARGB_32, 0, NULL); + if (picture == XCB_NONE) { + return picture; + } + int inner_height = height - 2 * corner_radius; int cap_height = corner_radius; if (inner_height < 0) { @@ -148,8 +154,12 @@ make_rounded_corner_points(int width, int height, int corner_radius, int *out_po ADD_POINT(right, i); } #undef ADD_POINT - *out_point_count = point_count; - return points; + + XCB_AWAIT_VOID(xcb_render_tri_strip, c, XCB_RENDER_PICT_OP_SRC, src, picture, + x_get_pictfmt_for_standard(c, XCB_PICT_STANDARD_A_8), 0, 0, + (uint32_t)point_count, points); + free(points); + return picture; } static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, int dst_x, @@ -175,10 +185,10 @@ static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, pixman_region32_init(®); pixman_region32_intersect(®, (region_t *)reg_paint, (region_t *)reg_visible); x_set_picture_clip_region(xd->base.c, result, 0, 0, ®); - if (img->corner_radius != 0 && xrimg->rounded_rectangle_triangles == NULL) { - xrimg->rounded_rectangle_triangles = make_rounded_corner_points( - inner->width, inner->height, (int)img->corner_radius, - &xrimg->triangle_count); + if (img->corner_radius != 0 && xrimg->rounded_rectangle == XCB_NONE) { + xrimg->rounded_rectangle = make_rounded_corner_picture( + xd->base.c, xd->white_pixel, xd->base.root, inner->width, + inner->height, (int)img->corner_radius); } if (((img->color_inverted || img->dim != 0) && has_alpha) || img->corner_radius != 0) { // Apply image properties using a temporary image, because the source @@ -231,11 +241,9 @@ static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, if (img->corner_radius != 0) { // Clip tmp_pict with a rounded rectangle - xcb_render_tri_strip( - xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, xd->white_pixel, tmp_pict, - x_get_pictfmt_for_standard(xd->base.c, XCB_PICT_STANDARD_A_8), - 0, 0, (uint32_t)xrimg->triangle_count, - xrimg->rounded_rectangle_triangles); + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, + xrimg->rounded_rectangle, XCB_NONE, tmp_pict, + 0, 0, 0, 0, 0, 0, tmpw, tmph); } xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict, @@ -444,8 +452,7 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool img->base.inner = (struct backend_image_inner_base *)inner; img->base.opacity = 1; - img->rounded_rectangle_triangles = NULL; - img->triangle_count = 0; + img->rounded_rectangle = XCB_NONE; free(r); if (inner->pict == XCB_NONE) { @@ -464,9 +471,8 @@ static void release_image_inner(backend_t *base, struct _xrender_image_data_inne } static void release_image(backend_t *base, void *image) { struct xrender_image *img = image; - free(img->rounded_rectangle_triangles); - img->triangle_count = 0; - img->base.inner->refcount--; + xcb_free_pixmap(base->c, img->rounded_rectangle); + img->rounded_rectangle = XCB_NONE; if (img->base.inner->refcount == 0) { release_image_inner(base, (void *)img->base.inner); } @@ -823,11 +829,10 @@ set_image_property(backend_t *base, enum image_properties op, void *image, void auto xrimg = (struct xrender_image *)image; if (op == IMAGE_PROPERTY_CORNER_RADIUS && ((double *)args)[0] != xrimg->base.corner_radius && - xrimg->rounded_rectangle_triangles != NULL) { + xrimg->rounded_rectangle != XCB_NONE) { // Free cached rounded rectangle if corner radius changed - free(xrimg->rounded_rectangle_triangles); - xrimg->triangle_count = 0; - xrimg->rounded_rectangle_triangles = NULL; + xcb_free_pixmap(base->c, xrimg->rounded_rectangle); + xrimg->rounded_rectangle = XCB_NONE; } return default_set_image_property(base, op, image, args); } From ffb8bc5b29c3cf40cdb5578886d11dcbfd59a74e Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 8 Nov 2021 00:19:27 +0000 Subject: [PATCH 7/8] backend: xrender: fix accidentally deleted refcount decrement Thanks, @tryone144 Signed-off-by: Yuxuan Shui --- src/backend/xrender/xrender.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 5f17020..473eeba 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -473,6 +473,7 @@ static void release_image(backend_t *base, void *image) { struct xrender_image *img = image; xcb_free_pixmap(base->c, img->rounded_rectangle); img->rounded_rectangle = XCB_NONE; + img->base.inner->refcount -= 1; if (img->base.inner->refcount == 0) { release_image_inner(base, (void *)img->base.inner); } From 8eb5bb08f8e62e91d0bd65fdb81654caf8adc597 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 8 Nov 2021 00:20:52 +0000 Subject: [PATCH 8/8] backend: explicitly initialize corner_radius Signed-off-by: Yuxuan Shui --- src/backend/backend_common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index 0f8b06e..464b5d9 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -469,6 +469,7 @@ struct backend_image *default_new_backend_image(int w, int h) { ret->eheight = h; ret->ewidth = w; ret->color_inverted = false; + ret->corner_radius = 0; return ret; }