backend: xrender: prepare for new backend interface transition

Morphing xrender_compose_impl to look like the blit operation. Also move
the rounded corner cache to xrender image inner, since we are going to
get rid of the image properties abstraction, so there is no space for it
there anymore.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-04-02 20:22:28 +01:00
parent ab95f208b7
commit 76615a817f
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
1 changed files with 171 additions and 131 deletions

View File

@ -2,6 +2,7 @@
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@ -78,6 +79,7 @@ struct xrender_image_data_inner {
int refcount;
bool has_alpha;
struct xrender_rounded_rectangle_cache *rounded_rectangle;
// Pixmap that the client window draws to,
// it will contain the content of client window.
xcb_pixmap_t pixmap;
@ -92,16 +94,10 @@ struct xrender_image_data_inner {
};
struct xrender_rounded_rectangle_cache {
int refcount;
// A cached picture of a rounded rectangle. Xorg rasterizes shapes on CPU so it's
// exceedingly slow.
xcb_render_picture_t p;
};
struct xrender_image {
struct backend_image base;
struct xrender_rounded_rectangle_cache *rounded_rectangle;
int radius;
};
/// Make a picture of size width x height, which has a rounded rectangle of corner_radius
@ -169,17 +165,30 @@ xrender_make_rounded_corner_cache(struct x_connection *c, xcb_render_picture_t s
free(points);
auto ret = ccalloc(1, struct xrender_rounded_rectangle_cache);
ret->p = picture;
ret->refcount = 1;
ret->radius = corner_radius;
return ret;
}
static void
xrender_release_rounded_corner_cache(backend_t *base,
struct xrender_rounded_rectangle_cache *cache) {
if (!cache) {
return;
}
x_free_picture(base->c, cache->p);
free(cache);
}
static xcb_render_picture_t
xrender_process_mask(struct xrender_data *xd, struct xrender_image *mask,
xrender_process_mask(struct xrender_data *xd, struct backend_mask *mask,
xcb_render_picture_t alpha_pict, bool *allocated) {
auto inner = (struct xrender_image_data_inner *)mask->base.inner;
if (!mask->base.color_inverted && mask->base.corner_radius == 0) {
auto inner = (struct xrender_image_data_inner *)mask->image;
if (!mask->inverted && mask->corner_radius == 0) {
// FIXME(yshui) if inner is not NULL here, alpha_pict will be
// ignored, and opacity will not be applied.
*allocated = false;
return inner->pict;
return inner ? inner->pict : alpha_pict;
}
auto const tmpw = to_u16_checked(inner->width);
auto const tmph = to_u16_checked(inner->height);
@ -191,18 +200,24 @@ xrender_process_mask(struct xrender_data *xd, struct xrender_image *mask,
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE,
ret, 0, 0, 0, 0, 0, 0, tmpw, tmph);
// Remember: the mask has a 1-pixel border
if (mask->base.corner_radius != 0) {
if (mask->rounded_rectangle == NULL) {
mask->rounded_rectangle = xrender_make_rounded_corner_cache(
if (mask->corner_radius != 0) {
if (inner->rounded_rectangle != NULL &&
inner->rounded_rectangle->radius != (int)mask->corner_radius) {
xrender_release_rounded_corner_cache(&xd->base,
inner->rounded_rectangle);
inner->rounded_rectangle = NULL;
}
if (inner->rounded_rectangle == NULL) {
inner->rounded_rectangle = xrender_make_rounded_corner_cache(
xd->base.c, xd->white_pixel, inner->width - 2,
inner->height - 2, (int)mask->base.corner_radius);
inner->height - 2, (int)mask->corner_radius);
}
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_IN_REVERSE,
mask->rounded_rectangle->p, XCB_NONE, ret, 0, 0, 0,
inner->rounded_rectangle->p, XCB_NONE, ret, 0, 0, 0,
0, 1, 1, (uint16_t)(tmpw - 2), (uint16_t)(tmph - 2));
}
if (mask->base.color_inverted) {
if (mask->inverted) {
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_XOR, xd->white_pixel,
XCB_NONE, ret, 0, 0, 0, 0, 0, 0, tmpw, tmph);
}
@ -216,64 +231,96 @@ xrender_process_mask(struct xrender_data *xd, struct xrender_image *mask,
return ret;
}
static void xrender_compose_impl(struct xrender_data *xd, struct xrender_image *xrimg,
coord_t dst, struct xrender_image *mask, coord_t mask_dst,
const region_t *reg_paint, const region_t *reg_visible,
xcb_render_picture_t result) {
const struct backend_image *img = &xrimg->base;
// origin is dst
static void
xrender_blit_inner(struct xrender_data *xd, struct coord origin,
xcb_render_picture_t target, struct backend_blit_args *blit_args) {
auto inner = (struct xrender_image_data_inner *)blit_args->source_image;
bool mask_allocated = false;
auto mask_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)];
if (mask != NULL) {
mask_pict = xrender_process_mask(
xd, mask, img->opacity < 1.0 ? mask_pict : XCB_NONE, &mask_allocated);
}
auto inner = (struct xrender_image_data_inner *)img->inner;
auto mask_pict = xd->alpha_pict[(int)(blit_args->opacity * MAX_ALPHA)];
region_t reg;
pixman_region32_init(&reg);
if (blit_args->mask != NULL) {
mask_pict = xrender_process_mask(
xd, blit_args->mask, blit_args->opacity < 1.0 ? mask_pict : XCB_NONE,
&mask_allocated);
pixman_region32_copy(&reg, &blit_args->mask->region);
pixman_region32_translate(&reg, blit_args->mask->origin.x,
blit_args->mask->origin.y);
}
bool has_alpha = inner->has_alpha || img->opacity != 1;
bool has_alpha = inner->has_alpha || blit_args->opacity != 1;
auto const tmpw = to_u16_checked(inner->width);
auto const tmph = to_u16_checked(inner->height);
auto const tmpew = to_u16_checked(img->ewidth);
auto const tmpeh = to_u16_checked(img->eheight);
auto const tmpew = to_u16_checked(blit_args->ewidth);
auto const tmpeh = to_u16_checked(blit_args->eheight);
// Remember: the mask has a 1-pixel border
auto const mask_dst_x = to_i16_checked(dst.x - mask_dst.x + 1);
auto const mask_dst_y = to_i16_checked(dst.y - mask_dst.y + 1);
int16_t mask_dst_x = 0, mask_dst_y = 0;
if (blit_args->mask) {
mask_dst_x = to_i16_checked(blit_args->mask->origin.x + 1);
mask_dst_y = to_i16_checked(blit_args->mask->origin.y + 1);
}
// auto const mask_dst_x = to_i16_checked(dst.x - mask_dst.x + 1);
// auto const mask_dst_y = to_i16_checked(dst.y - mask_dst.y + 1);
const xcb_render_color_t dim_color = {
.red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)};
.red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * blit_args->dim)};
// Clip region of rendered_pict might be set during rendering, clear it to
// make sure we get everything into the buffer
x_clear_picture_clip_region(xd->base.c, inner->pict);
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->corner_radius != 0 && xrimg->rounded_rectangle == NULL) {
xrimg->rounded_rectangle = xrender_make_rounded_corner_cache(
xd->base.c, xd->white_pixel, inner->width, inner->height,
(int)img->corner_radius);
// pixman_region32_init(&reg);
// pixman_region32_intersect(&reg, (region_t *)reg_paint, (region_t
// *)reg_visible);
if (blit_args->mask) {
x_set_picture_clip_region(xd->base.c, target, 0, 0, &blit_args->mask->region);
}
if (((img->color_inverted || img->dim != 0) && has_alpha) || img->corner_radius != 0) {
if (blit_args->corner_radius != 0) {
if (inner->rounded_rectangle != NULL &&
inner->rounded_rectangle->radius != (int)blit_args->corner_radius) {
log_info("Releasing rounded rectangle cache because radius "
"changed: "
"%d -> %f",
inner->rounded_rectangle->radius, blit_args->corner_radius);
xrender_release_rounded_corner_cache(&xd->base,
inner->rounded_rectangle);
inner->rounded_rectangle = NULL;
}
if (inner->rounded_rectangle == NULL) {
log_info("Creating rounded rectangle cache: %dx%d, radius %f, "
"for image %p",
inner->width - 2, inner->height - 2,
blit_args->corner_radius, inner);
inner->rounded_rectangle = xrender_make_rounded_corner_cache(
xd->base.c, xd->white_pixel, inner->width, inner->height,
(int)blit_args->corner_radius);
}
}
if (((blit_args->color_inverted || blit_args->dim != 0) && has_alpha) ||
blit_args->corner_radius != 0) {
// Apply image properties using a temporary image, because the source
// image is transparent or will get transparent corners. Otherwise the
// properties can be applied directly on the target image.
// Also force a 32-bit ARGB visual for transparent corners, otherwise the
// corners become black.
auto visual =
(img->corner_radius != 0 && inner->depth != 32)
(blit_args->corner_radius != 0 && inner->depth != 32)
? x_get_visual_for_standard(xd->base.c, XCB_PICT_STANDARD_ARGB_32)
: inner->visual;
auto tmp_pict = x_create_picture_with_visual(
xd->base.c, inner->width, inner->height, visual, 0, NULL);
// Set clip region translated to source coordinate
x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x),
to_i16_checked(-dst.y), &reg);
if (blit_args->mask) {
x_set_picture_clip_region(xd->base.c, tmp_pict,
to_i16_checked(-origin.x),
to_i16_checked(-origin.y), &reg);
}
// Copy source -> tmp
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_SRC, inner->pict,
XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
if (img->color_inverted) {
if (blit_args->color_inverted) {
if (inner->has_alpha) {
auto tmp_pict2 = x_create_picture_with_visual(
xd->base.c, tmpw, tmph, inner->visual, 0, NULL);
@ -297,7 +344,7 @@ static void xrender_compose_impl(struct xrender_data *xd, struct xrender_image *
}
}
if (img->dim != 0) {
if (blit_args->dim != 0) {
// Dim the actually content of window
xcb_rectangle_t rect = {
.x = 0,
@ -310,47 +357,47 @@ static void xrender_compose_impl(struct xrender_data *xd, struct xrender_image *
tmp_pict, dim_color, 1, &rect);
}
if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) {
if (blit_args->corner_radius != 0 && inner->rounded_rectangle != NULL) {
// Clip tmp_pict with a rounded rectangle
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_IN_REVERSE,
xrimg->rounded_rectangle->p, XCB_NONE,
inner->rounded_rectangle->p, XCB_NONE,
tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
}
xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_OVER, tmp_pict,
mask_pict, result, 0, 0, mask_dst_x, mask_dst_y,
to_i16_checked(dst.x), to_i16_checked(dst.y), tmpew,
tmpeh);
mask_pict, target, 0, 0, mask_dst_x, mask_dst_y,
to_i16_checked(origin.x), to_i16_checked(origin.y),
tmpew, tmpeh);
xcb_render_free_picture(xd->base.c->c, tmp_pict);
} else {
uint8_t op = (has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC);
xcb_render_composite(xd->base.c->c, op, inner->pict, mask_pict, result, 0,
0, mask_dst_x, mask_dst_y, to_i16_checked(dst.x),
to_i16_checked(dst.y), tmpew, tmpeh);
if (img->dim != 0 || img->color_inverted) {
xcb_render_composite(xd->base.c->c, op, inner->pict, mask_pict, target, 0,
0, mask_dst_x, mask_dst_y, to_i16_checked(origin.x),
to_i16_checked(origin.y), tmpew, tmpeh);
if (blit_args->dim != 0 || blit_args->color_inverted) {
// Apply properties, if we reach here, then has_alpha == false
assert(!has_alpha);
if (img->color_inverted) {
xcb_render_composite(xd->base.c->c,
XCB_RENDER_PICT_OP_DIFFERENCE,
xd->white_pixel, XCB_NONE, result, 0,
0, 0, 0, to_i16_checked(dst.x),
to_i16_checked(dst.y), tmpew, tmpeh);
if (blit_args->color_inverted) {
xcb_render_composite(
xd->base.c->c, XCB_RENDER_PICT_OP_DIFFERENCE,
xd->white_pixel, XCB_NONE, target, 0, 0, 0, 0,
to_i16_checked(origin.x), to_i16_checked(origin.y),
tmpew, tmpeh);
}
if (img->dim != 0) {
if (blit_args->dim != 0) {
// Dim the actually content of window
xcb_rectangle_t rect = {
.x = to_i16_checked(dst.x),
.y = to_i16_checked(dst.y),
.x = to_i16_checked(origin.x),
.y = to_i16_checked(origin.y),
.width = tmpew,
.height = tmpeh,
};
xcb_render_fill_rectangles(xd->base.c->c,
XCB_RENDER_PICT_OP_OVER,
result, dim_color, 1, &rect);
target, dim_color, 1, &rect);
}
}
}
@ -364,10 +411,31 @@ static void
xrender_compose(backend_t *base, image_handle image_, coord_t dst, image_handle mask_,
coord_t mask_dst, const region_t *reg_paint, const region_t *reg_visible) {
auto xd = (struct xrender_data *)base;
auto image = (struct xrender_image *)image_;
auto mask = (struct xrender_image *)mask_;
return xrender_compose_impl(xd, image, dst, mask, mask_dst, reg_paint,
reg_visible, xd->back[2]);
auto image = (struct backend_image *)image_;
auto mask = (struct backend_image *)mask_;
struct backend_mask mask_args = {
.image = mask ? (image_handle)mask->inner : NULL,
.origin = {.x = dst.x - mask_dst.x, .y = dst.y - mask_dst.y},
.inverted = mask ? mask->color_inverted : 0,
.corner_radius = mask ? mask->corner_radius : 0,
};
pixman_region32_init(&mask_args.region);
pixman_region32_intersect(&mask_args.region, reg_paint, reg_visible);
pixman_region32_translate(&mask_args.region, mask_dst.x - dst.x, mask_dst.y - dst.y);
struct backend_blit_args args = {
.mask = &mask_args,
.source_image = (image_handle)image->inner,
.border_width = image->border_width,
.color_inverted = image->color_inverted,
.dim = image->dim,
.corner_radius = image->corner_radius,
.opacity = image->opacity,
.ewidth = image->ewidth,
.eheight = image->eheight,
.max_brightness = image->max_brightness,
};
xrender_blit_inner(xd, dst, xd->back[2], &args);
pixman_region32_fini(&mask_args.region);
}
static void xrender_fill(backend_t *base, struct color c, const region_t *clip) {
@ -392,7 +460,7 @@ static bool
xrender_blur(backend_t *backend_data, double opacity, void *ctx_, image_handle mask_,
coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible) {
auto bctx = (struct xrender_blur_context *)ctx_;
auto mask = (struct xrender_image *)mask_;
auto mask = (struct backend_image *)mask_;
if (bctx->method == BLUR_METHOD_NONE) {
return true;
}
@ -447,8 +515,13 @@ xrender_blur(backend_t *backend_data, double opacity, void *ctx_, image_handle m
auto mask_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)];
bool mask_allocated = false;
if (mask != NULL) {
struct backend_mask mask_args = {
.image = (image_handle)mask->inner,
.inverted = mask->color_inverted,
.corner_radius = mask->corner_radius,
};
mask_pict = xrender_process_mask(
xd, mask, opacity != 1.0 ? mask_pict : XCB_NONE, &mask_allocated);
xd, &mask_args, opacity != 1.0 ? mask_pict : XCB_NONE, &mask_allocated);
}
int current = 0;
x_set_picture_clip_region(c, src_pict, 0, 0, &reg_op_resized);
@ -534,11 +607,11 @@ static image_handle xrender_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap,
return NULL;
}
auto img = ccalloc(1, struct xrender_image);
auto img = ccalloc(1, struct backend_image);
auto inner = ccalloc(1, struct xrender_image_data_inner);
inner->depth = (uint8_t)fmt.visual_depth;
inner->width = img->base.ewidth = r->width;
inner->height = img->base.eheight = r->height;
inner->width = img->ewidth = r->width;
inner->height = img->eheight = r->height;
inner->pixmap = pixmap;
inner->has_alpha = fmt.alpha_size != 0;
xcb_render_create_picture_value_list_t pic_attrs = {.repeat = XCB_RENDER_REPEAT_NORMAL};
@ -548,9 +621,8 @@ static image_handle xrender_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap,
inner->visual = fmt.visual;
inner->refcount = 1;
img->base.inner = (struct backend_image_inner_base *)inner;
img->base.opacity = 1;
img->rounded_rectangle = NULL;
img->inner = (struct backend_image_inner_base *)inner;
img->opacity = 1;
free(r);
if (inner->pict == XCB_NONE) {
@ -569,29 +641,13 @@ xrender_release_image_inner(backend_t *base, struct xrender_image_data_inner *in
free(inner);
}
static void
xrender_release_rounded_corner_cache(backend_t *base,
struct xrender_rounded_rectangle_cache *cache) {
if (!cache) {
return;
}
assert(cache->refcount > 0);
cache->refcount--;
if (cache->refcount == 0) {
x_free_picture(base->c, cache->p);
free(cache);
}
}
static void xrender_release_image(backend_t *base, image_handle image) {
auto img = (struct xrender_image *)image;
xrender_release_rounded_corner_cache(base, img->rounded_rectangle);
img->rounded_rectangle = NULL;
img->base.inner->refcount -= 1;
if (img->base.inner->refcount == 0) {
xrender_release_image_inner(
base, (struct xrender_image_data_inner *)img->base.inner);
auto img = (struct backend_image *)image;
img->inner->refcount -= 1;
if (img->inner->refcount == 0) {
auto inner = (struct xrender_image_data_inner *)img->inner;
xrender_release_rounded_corner_cache(base, inner->rounded_rectangle);
xrender_release_image_inner(base, inner);
}
free(img);
}
@ -752,17 +808,16 @@ static image_handle xrender_make_mask(backend_t *base, geometry_t size, const re
{.x = (short)(w16 - 1), .y = 0, .width = 1, .height = h16}});
inner->refcount = 1;
auto img = ccalloc(1, struct xrender_image);
img->base.eheight = size.height + 2;
img->base.ewidth = size.width + 2;
img->base.border_width = 0;
img->base.color_inverted = false;
img->base.corner_radius = 0;
img->base.max_brightness = 1;
img->base.opacity = 1;
img->base.dim = 0;
img->base.inner = (struct backend_image_inner_base *)inner;
img->rounded_rectangle = NULL;
auto img = ccalloc(1, struct backend_image);
img->ewidth = size.width + 2;
img->border_width = 0;
img->eheight = size.height + 2;
img->color_inverted = false;
img->corner_radius = 0;
img->max_brightness = 1;
img->opacity = 1;
img->dim = 0;
img->inner = (struct backend_image_inner_base *)inner;
return (image_handle)img;
}
@ -976,27 +1031,12 @@ err:
image_handle xrender_clone_image(backend_t *base attr_unused, image_handle image,
const region_t *reg_visible attr_unused) {
auto new_img = ccalloc(1, struct xrender_image);
*new_img = *(struct xrender_image *)image;
new_img->base.inner->refcount++;
if (new_img->rounded_rectangle) {
new_img->rounded_rectangle->refcount++;
}
auto new_img = ccalloc(1, struct backend_image);
*new_img = *(struct backend_image *)image;
new_img->inner->refcount++;
return (image_handle)new_img;
}
static bool xrender_set_image_property(backend_t *base, enum image_properties op,
image_handle image, const void *args) {
auto xrimg = (struct xrender_image *)image;
if (op == IMAGE_PROPERTY_CORNER_RADIUS &&
((const double *)args)[0] != xrimg->base.corner_radius) {
// Free cached rounded rectangle if corner radius changed
xrender_release_rounded_corner_cache(base, xrimg->rounded_rectangle);
xrimg->rounded_rectangle = NULL;
}
return default_set_image_property(base, op, image, args);
}
struct backend_operations xrender_ops = {
.init = xrender_init,
.deinit = xrender_deinit,
@ -1013,7 +1053,7 @@ struct backend_operations xrender_ops = {
.is_image_transparent = default_is_image_transparent,
.buffer_age = xrender_buffer_age,
.max_buffer_age = 2,
.set_image_property = xrender_set_image_property,
.set_image_property = default_set_image_property,
.image_op = xrender_image_op,
.clone_image = xrender_clone_image,
.create_blur_context = xrender_create_blur_context,