diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 4b961564..52038faa 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -2,6 +2,7 @@ // Copyright (c) Yuxuan Shui #include #include +#include #include #include @@ -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(®); + 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(®, &blit_args->mask->region); + pixman_region32_translate(®, 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(®); - 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 == 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(®); + // pixman_region32_intersect(®, (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), ®); + if (blit_args->mask) { + x_set_picture_clip_region(xd->base.c, tmp_pict, + to_i16_checked(-origin.x), + to_i16_checked(-origin.y), ®); + } // 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, ®_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,