Merge pull request #716 from yshui/new-backend-rounded-corner-1
New backend rounded corner, part 1
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -110,7 +185,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 == 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.
|
||||
@@ -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);
|
||||
@@ -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 =
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
}
|
||||
@@ -735,6 +825,19 @@ 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 != 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,
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
4
src/x.c
4
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,
|
||||
|
||||
4
src/x.h
4
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);
|
||||
|
||||
Reference in New Issue
Block a user