Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rounded corner for new backends #658

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/backend/backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/backend/backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions src/backend/backend_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -468,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;
}

Expand Down
1 change: 1 addition & 0 deletions src/backend/backend_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 18 additions & 0 deletions src/backend/gl/gl_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
);
Expand Down
1 change: 1 addition & 0 deletions src/backend/gl/gl_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
129 changes: 116 additions & 13 deletions src/backend/xrender/xrender.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,84 @@ 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 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_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) {
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

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,
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;
Expand All @@ -110,7 +185,12 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i
pixman_region32_init(&reg);
pixman_region32_intersect(&reg, (region_t *)reg_paint, (region_t *)reg_visible);
x_set_picture_clip_region(xd->base.c, result, 0, 0, &reg);
if ((img->color_inverted || img->dim != 0) && has_alpha) {
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
// image is transparent. Otherwise the properties can be applied directly
// on the target image.
Expand Down Expand Up @@ -159,6 +239,13 @@ 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_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,
alpha_pict, result, 0, 0, 0, 0, to_i16_checked(dst_x),
to_i16_checked(dst_y), tmpew, tmpeh);
Expand Down Expand Up @@ -350,11 +437,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 =
Expand All @@ -363,8 +450,9 @@ 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 = XCB_NONE;
free(r);

if (inner->pict == XCB_NONE) {
Expand All @@ -382,10 +470,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;
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);
}
free(img);
}
Expand Down Expand Up @@ -735,6 +825,19 @@ static backend_t *backend_xrender_init(session_t *ps) {
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 != XCB_NONE) {
// Free cached rounded rectangle if corner radius changed
xcb_free_pixmap(base->c, xrimg->rounded_rectangle);
xrimg->rounded_rectangle = XCB_NONE;
}
return default_set_image_property(base, op, image, args);
}

struct backend_operations xrender_ops = {
.init = backend_xrender_init,
.deinit = deinit,
Expand All @@ -754,7 +857,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,
Expand Down
6 changes: 0 additions & 6 deletions src/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
4 changes: 0 additions & 4 deletions src/x.c
Original file line number Diff line number Diff line change
Expand Up @@ -652,10 +652,6 @@ bool x_fence_sync(xcb_connection_t *c, xcb_sync_fence_t f) {
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,
Expand Down
4 changes: 4 additions & 0 deletions src/x.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down