Currently there is some inconsistency in how image_op is implemented across backends. The glx backend applies some of the image operations lazily, and not always in the order the operations were made; while the xrender backend applies the operations eagerly. This can lead to different render result in some cases. Instead of trying to preserving the order of operations, which would be unnecessary, we re-model the API to better reflect the implementation. We make it clear that setting the property doesn't change the image data, and properties are only applied during composition and in a specific order. This makes sure the render result looks consistent across backends. Should also improve the performance of the xrender backend, even if only slightly. Also distill out the property management code so they can be shared. Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
792 lines
27 KiB
C
792 lines
27 KiB
C
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <xcb/composite.h>
|
|
#include <xcb/present.h>
|
|
#include <xcb/render.h>
|
|
#include <xcb/sync.h>
|
|
#include <xcb/xcb.h>
|
|
|
|
#include "backend/backend.h"
|
|
#include "backend/backend_common.h"
|
|
#include "common.h"
|
|
#include "config.h"
|
|
#include "kernel.h"
|
|
#include "log.h"
|
|
#include "picom.h"
|
|
#include "region.h"
|
|
#include "types.h"
|
|
#include "utils.h"
|
|
#include "win.h"
|
|
#include "x.h"
|
|
|
|
typedef struct _xrender_data {
|
|
backend_t base;
|
|
/// If vsync is enabled and supported by the current system
|
|
bool vsync;
|
|
xcb_visualid_t default_visual;
|
|
/// Target window
|
|
xcb_window_t target_win;
|
|
/// Painting target, it is either the root or the overlay
|
|
xcb_render_picture_t target;
|
|
/// Back buffers. Double buffer, with 1 for temporary render use
|
|
xcb_render_picture_t back[3];
|
|
/// The back buffer that is for temporary use
|
|
/// Age of each back buffer.
|
|
int buffer_age[3];
|
|
/// The back buffer we should be painting into
|
|
int curr_back;
|
|
/// The corresponding pixmap to the back buffer
|
|
xcb_pixmap_t back_pixmap[3];
|
|
/// Pictures of pixel of different alpha value, used as a mask to
|
|
/// paint transparent images
|
|
xcb_render_picture_t alpha_pict[256];
|
|
|
|
// XXX don't know if these are really needed
|
|
|
|
/// 1x1 white picture
|
|
xcb_render_picture_t white_pixel;
|
|
/// 1x1 black picture
|
|
xcb_render_picture_t black_pixel;
|
|
|
|
/// Width and height of the target pixmap
|
|
int target_width, target_height;
|
|
|
|
xcb_special_event_t *present_event;
|
|
} xrender_data;
|
|
|
|
struct _xrender_blur_context {
|
|
enum blur_method method;
|
|
/// Blur kernels converted to X format
|
|
struct x_convolution_kernel **x_blur_kernel;
|
|
|
|
int resize_width, resize_height;
|
|
|
|
/// Number of blur kernels
|
|
int x_blur_kernel_count;
|
|
};
|
|
|
|
struct _xrender_image_data_inner {
|
|
// struct backend_image_inner_base
|
|
int refcount;
|
|
bool has_alpha;
|
|
|
|
// Pixmap that the client window draws to,
|
|
// it will contain the content of client window.
|
|
xcb_pixmap_t pixmap;
|
|
// A Picture links to the Pixmap
|
|
xcb_render_picture_t pict;
|
|
int width, height;
|
|
xcb_visualid_t visual;
|
|
uint8_t depth;
|
|
// Whether we own this image, e.g. we allocated it;
|
|
// or not, e.g. this is a named pixmap of a X window.
|
|
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,
|
|
const region_t *reg_visible, xcb_render_picture_t result) {
|
|
auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)];
|
|
auto inner = (struct _xrender_image_data_inner *)img->inner;
|
|
region_t reg;
|
|
|
|
bool has_alpha = inner->has_alpha || img->opacity != 1;
|
|
const auto tmpw = to_u16_checked(inner->width);
|
|
const auto tmph = to_u16_checked(inner->height);
|
|
const auto tmpew = to_u16_checked(img->ewidth);
|
|
const auto tmpeh = to_u16_checked(img->eheight);
|
|
const xcb_render_color_t dim_color = {
|
|
.red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->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->color_inverted || img->dim != 0) && has_alpha) {
|
|
// Apply image properties using a temporary image, because the source
|
|
// image is transparent. Otherwise the properties can be applied directly
|
|
// on the target image.
|
|
auto tmp_pict =
|
|
x_create_picture_with_visual(xd->base.c, xd->base.root, inner->width,
|
|
inner->height, inner->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), ®);
|
|
// Copy source -> tmp
|
|
xcb_render_composite(xd->base.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 (inner->has_alpha) {
|
|
auto tmp_pict2 = x_create_picture_with_visual(
|
|
xd->base.c, xd->base.root, tmpw, tmph, inner->visual,
|
|
0, NULL);
|
|
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC,
|
|
tmp_pict, XCB_NONE, tmp_pict2, 0, 0,
|
|
0, 0, 0, 0, tmpw, tmph);
|
|
|
|
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
|
|
xd->white_pixel, XCB_NONE, tmp_pict,
|
|
0, 0, 0, 0, 0, 0, tmpw, tmph);
|
|
xcb_render_composite(
|
|
xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, tmp_pict2,
|
|
XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
|
|
xcb_render_free_picture(xd->base.c, tmp_pict2);
|
|
} else {
|
|
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
|
|
xd->white_pixel, XCB_NONE, tmp_pict,
|
|
0, 0, 0, 0, 0, 0, tmpw, tmph);
|
|
}
|
|
}
|
|
if (img->dim != 0) {
|
|
// Dim the actually content of window
|
|
xcb_rectangle_t rect = {
|
|
.x = 0,
|
|
.y = 0,
|
|
.width = tmpw,
|
|
.height = tmph,
|
|
};
|
|
|
|
xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER,
|
|
tmp_pict, dim_color, 1, &rect);
|
|
}
|
|
|
|
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);
|
|
xcb_render_free_picture(xd->base.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, op, inner->pict, alpha_pict, result, 0,
|
|
0, 0, 0, to_i16_checked(dst_x),
|
|
to_i16_checked(dst_y), tmpew, tmpeh);
|
|
if (img->dim != 0 || img->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, 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 (img->dim != 0) {
|
|
// Dim the actually content of window
|
|
xcb_rectangle_t rect = {
|
|
.x = to_i16_checked(dst_x),
|
|
.y = to_i16_checked(dst_y),
|
|
.width = tmpew,
|
|
.height = tmpeh,
|
|
};
|
|
|
|
xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER,
|
|
result, dim_color, 1, &rect);
|
|
}
|
|
}
|
|
}
|
|
pixman_region32_fini(®);
|
|
}
|
|
|
|
static void compose(backend_t *base, void *img_data, int dst_x, int dst_y,
|
|
const region_t *reg_paint, const region_t *reg_visible) {
|
|
struct _xrender_data *xd = (void *)base;
|
|
return compose_impl(xd, img_data, dst_x, dst_y, reg_paint, reg_visible, xd->back[2]);
|
|
}
|
|
|
|
static void fill(backend_t *base, struct color c, const region_t *clip) {
|
|
struct _xrender_data *xd = (void *)base;
|
|
const rect_t *extent = pixman_region32_extents((region_t *)clip);
|
|
x_set_picture_clip_region(base->c, xd->back[2], 0, 0, clip);
|
|
// color is in X fixed point representation
|
|
xcb_render_fill_rectangles(
|
|
base->c, XCB_RENDER_PICT_OP_OVER, xd->back[2],
|
|
(xcb_render_color_t){.red = (uint16_t)(c.red * 0xffff),
|
|
.green = (uint16_t)(c.green * 0xffff),
|
|
.blue = (uint16_t)(c.blue * 0xffff),
|
|
.alpha = (uint16_t)(c.alpha * 0xffff)},
|
|
1,
|
|
(xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1),
|
|
.y = to_i16_checked(extent->y1),
|
|
.width = to_u16_checked(extent->x2 - extent->x1),
|
|
.height = to_u16_checked(extent->y2 - extent->y1)}});
|
|
}
|
|
|
|
static bool blur(backend_t *backend_data, double opacity, void *ctx_,
|
|
const region_t *reg_blur, const region_t *reg_visible) {
|
|
struct _xrender_blur_context *bctx = ctx_;
|
|
if (bctx->method == BLUR_METHOD_NONE) {
|
|
return true;
|
|
}
|
|
|
|
struct _xrender_data *xd = (void *)backend_data;
|
|
xcb_connection_t *c = xd->base.c;
|
|
region_t reg_op;
|
|
pixman_region32_init(®_op);
|
|
pixman_region32_intersect(®_op, (region_t *)reg_blur, (region_t *)reg_visible);
|
|
if (!pixman_region32_not_empty(®_op)) {
|
|
pixman_region32_fini(®_op);
|
|
return true;
|
|
}
|
|
|
|
region_t reg_op_resized =
|
|
resize_region(®_op, bctx->resize_width, bctx->resize_height);
|
|
|
|
const pixman_box32_t *extent_resized = pixman_region32_extents(®_op_resized);
|
|
const auto height_resized = to_u16_checked(extent_resized->y2 - extent_resized->y1);
|
|
const auto width_resized = to_u16_checked(extent_resized->x2 - extent_resized->x1);
|
|
static const char *filter0 = "Nearest"; // The "null" filter
|
|
static const char *filter = "convolution";
|
|
|
|
// Create a buffer for storing blurred picture, make it just big enough
|
|
// for the blur region
|
|
const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT;
|
|
const xcb_render_create_picture_value_list_t pic_attrs = {.repeat = XCB_RENDER_REPEAT_PAD};
|
|
xcb_render_picture_t tmp_picture[2] = {
|
|
x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized,
|
|
xd->default_visual, pic_attrs_mask, &pic_attrs),
|
|
x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized,
|
|
xd->default_visual, pic_attrs_mask, &pic_attrs)};
|
|
|
|
if (!tmp_picture[0] || !tmp_picture[1]) {
|
|
log_error("Failed to build intermediate Picture.");
|
|
pixman_region32_fini(®_op);
|
|
pixman_region32_fini(®_op_resized);
|
|
return false;
|
|
}
|
|
|
|
region_t clip;
|
|
pixman_region32_init(&clip);
|
|
pixman_region32_copy(&clip, ®_op_resized);
|
|
pixman_region32_translate(&clip, -extent_resized->x1, -extent_resized->y1);
|
|
x_set_picture_clip_region(c, tmp_picture[0], 0, 0, &clip);
|
|
x_set_picture_clip_region(c, tmp_picture[1], 0, 0, &clip);
|
|
pixman_region32_fini(&clip);
|
|
|
|
xcb_render_picture_t src_pict = xd->back[2], dst_pict = tmp_picture[0];
|
|
auto alpha_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)];
|
|
int current = 0;
|
|
x_set_picture_clip_region(c, src_pict, 0, 0, ®_op_resized);
|
|
|
|
// For more than 1 pass, we do:
|
|
// back -(pass 1)-> tmp0 -(pass 2)-> tmp1 ...
|
|
// -(pass n-1)-> tmp0 or tmp1 -(pass n)-> back
|
|
// For 1 pass, we do
|
|
// back -(pass 1)-> tmp0 -(copy)-> target_buffer
|
|
int i;
|
|
for (i = 0; i < bctx->x_blur_kernel_count; i++) {
|
|
// Copy from source picture to destination. The filter must
|
|
// be applied on source picture, to get the nearby pixels outside the
|
|
// window.
|
|
xcb_render_set_picture_filter(c, src_pict, to_u16_checked(strlen(filter)),
|
|
filter,
|
|
to_u32_checked(bctx->x_blur_kernel[i]->size),
|
|
bctx->x_blur_kernel[i]->kernel);
|
|
|
|
if (i == 0) {
|
|
// First pass, back buffer -> tmp picture
|
|
// (we do this even if this is also the last pass, because we
|
|
// cannot do back buffer -> back buffer)
|
|
xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE,
|
|
dst_pict, to_i16_checked(extent_resized->x1),
|
|
to_i16_checked(extent_resized->y1), 0, 0, 0,
|
|
0, width_resized, height_resized);
|
|
} else if (i < bctx->x_blur_kernel_count - 1) {
|
|
// This is not the last pass or the first pass,
|
|
// tmp picture 1 -> tmp picture 2
|
|
xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict,
|
|
XCB_NONE, dst_pict, 0, 0, 0, 0, 0, 0,
|
|
width_resized, height_resized);
|
|
} else {
|
|
x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op);
|
|
// This is the last pass, and we are doing more than 1 pass
|
|
xcb_render_composite(c, XCB_RENDER_PICT_OP_OVER, src_pict,
|
|
alpha_pict, xd->back[2], 0, 0, 0, 0,
|
|
to_i16_checked(extent_resized->x1),
|
|
to_i16_checked(extent_resized->y1),
|
|
width_resized, height_resized);
|
|
}
|
|
|
|
// reset filter
|
|
xcb_render_set_picture_filter(
|
|
c, src_pict, to_u16_checked(strlen(filter0)), filter0, 0, NULL);
|
|
|
|
src_pict = tmp_picture[current];
|
|
dst_pict = tmp_picture[!current];
|
|
current = !current;
|
|
}
|
|
|
|
// There is only 1 pass
|
|
if (i == 1) {
|
|
x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op);
|
|
xcb_render_composite(
|
|
c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, xd->back[2], 0, 0,
|
|
0, 0, to_i16_checked(extent_resized->x1),
|
|
to_i16_checked(extent_resized->y1), width_resized, height_resized);
|
|
}
|
|
|
|
xcb_render_free_picture(c, tmp_picture[0]);
|
|
xcb_render_free_picture(c, tmp_picture[1]);
|
|
pixman_region32_fini(®_op);
|
|
pixman_region32_fini(®_op_resized);
|
|
return true;
|
|
}
|
|
|
|
static void *
|
|
bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
|
|
xcb_generic_error_t *e;
|
|
auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), &e);
|
|
if (!r) {
|
|
log_error("Invalid pixmap: %#010x", pixmap);
|
|
x_print_error(e->full_sequence, e->major_code, e->minor_code, e->error_code);
|
|
return NULL;
|
|
}
|
|
|
|
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->ewidth = r->width;
|
|
inner->height = img->eheight = r->height;
|
|
inner->pixmap = pixmap;
|
|
inner->has_alpha = fmt.alpha_size != 0;
|
|
inner->pict =
|
|
x_create_picture_with_visual_and_pixmap(base->c, fmt.visual, pixmap, 0, NULL);
|
|
inner->owned = owned;
|
|
inner->visual = fmt.visual;
|
|
inner->refcount = 1;
|
|
|
|
img->inner = (struct backend_image_inner_base *)inner;
|
|
img->opacity = 1;
|
|
free(r);
|
|
|
|
if (inner->pict == XCB_NONE) {
|
|
free(inner);
|
|
free(img);
|
|
return NULL;
|
|
}
|
|
return img;
|
|
}
|
|
static void release_image_inner(backend_t *base, struct _xrender_image_data_inner *inner) {
|
|
xcb_render_free_picture(base->c, inner->pict);
|
|
if (inner->owned) {
|
|
xcb_free_pixmap(base->c, inner->pixmap);
|
|
}
|
|
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);
|
|
}
|
|
free(img);
|
|
}
|
|
|
|
static void deinit(backend_t *backend_data) {
|
|
struct _xrender_data *xd = (void *)backend_data;
|
|
for (int i = 0; i < 256; i++) {
|
|
xcb_render_free_picture(xd->base.c, xd->alpha_pict[i]);
|
|
}
|
|
xcb_render_free_picture(xd->base.c, xd->target);
|
|
for (int i = 0; i < 2; i++) {
|
|
xcb_render_free_picture(xd->base.c, xd->back[i]);
|
|
xcb_free_pixmap(xd->base.c, xd->back_pixmap[i]);
|
|
}
|
|
if (xd->present_event) {
|
|
xcb_unregister_for_special_event(xd->base.c, xd->present_event);
|
|
}
|
|
xcb_render_free_picture(xd->base.c, xd->white_pixel);
|
|
xcb_render_free_picture(xd->base.c, xd->black_pixel);
|
|
free(xd);
|
|
}
|
|
|
|
static void present(backend_t *base, const region_t *region) {
|
|
struct _xrender_data *xd = (void *)base;
|
|
const rect_t *extent = pixman_region32_extents((region_t *)region);
|
|
int16_t orig_x = to_i16_checked(extent->x1), orig_y = to_i16_checked(extent->y1);
|
|
uint16_t region_width = to_u16_checked(extent->x2 - extent->x1),
|
|
region_height = to_u16_checked(extent->y2 - extent->y1);
|
|
|
|
// compose() sets clip region on the back buffer, so clear it first
|
|
x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]);
|
|
|
|
// limit the region of update
|
|
x_set_picture_clip_region(base->c, xd->back[2], 0, 0, region);
|
|
|
|
if (xd->vsync) {
|
|
// Update the back buffer first, then present
|
|
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2],
|
|
XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0,
|
|
0, orig_x, orig_y, region_width, region_height);
|
|
|
|
// Make sure we got reply from PresentPixmap before waiting for events,
|
|
// to avoid deadlock
|
|
auto e = xcb_request_check(
|
|
base->c, xcb_present_pixmap_checked(
|
|
xd->base.c, xd->target_win,
|
|
xd->back_pixmap[xd->curr_back], 0, XCB_NONE, XCB_NONE, 0,
|
|
0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL));
|
|
if (e) {
|
|
log_error("Failed to present pixmap");
|
|
free(e);
|
|
return;
|
|
}
|
|
// TODO(yshui) don't block wait for present completion
|
|
xcb_present_generic_event_t *pev =
|
|
(void *)xcb_wait_for_special_event(base->c, xd->present_event);
|
|
if (!pev) {
|
|
// We don't know what happened, maybe X died
|
|
// But reset buffer age, so in case we do recover, we will
|
|
// render correctly.
|
|
xd->buffer_age[0] = xd->buffer_age[1] = -1;
|
|
return;
|
|
}
|
|
assert(pev->evtype == XCB_PRESENT_COMPLETE_NOTIFY);
|
|
xcb_present_complete_notify_event_t *pcev = (void *)pev;
|
|
// log_trace("Present complete: %d %ld", pcev->mode, pcev->msc);
|
|
xd->buffer_age[xd->curr_back] = 1;
|
|
|
|
// buffer_age < 0 means that back buffer is empty
|
|
if (xd->buffer_age[1 - xd->curr_back] > 0) {
|
|
xd->buffer_age[1 - xd->curr_back]++;
|
|
}
|
|
if (pcev->mode == XCB_PRESENT_COMPLETE_MODE_FLIP) {
|
|
// We cannot use the pixmap we used anymore
|
|
xd->curr_back = 1 - xd->curr_back;
|
|
}
|
|
free(pev);
|
|
} else {
|
|
// No vsync needed, draw into the target picture directly
|
|
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2],
|
|
XCB_NONE, xd->target, orig_x, orig_y, 0, 0, orig_x,
|
|
orig_y, region_width, region_height);
|
|
}
|
|
}
|
|
|
|
static int buffer_age(backend_t *backend_data) {
|
|
struct _xrender_data *xd = (void *)backend_data;
|
|
if (!xd->vsync) {
|
|
// Only the target picture really holds the screen content, and its
|
|
// content is always up to date. So buffer age is always 1.
|
|
return 1;
|
|
}
|
|
return xd->buffer_age[xd->curr_back];
|
|
}
|
|
|
|
static struct _xrender_image_data_inner *
|
|
new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) {
|
|
auto new_inner = ccalloc(1, struct _xrender_image_data_inner);
|
|
new_inner->pixmap = x_create_pixmap(base->c, depth, base->root, w, h);
|
|
if (new_inner->pixmap == XCB_NONE) {
|
|
log_error("Failed to create pixmap for copy");
|
|
free(new_inner);
|
|
return NULL;
|
|
}
|
|
new_inner->pict = x_create_picture_with_visual_and_pixmap(
|
|
base->c, visual, new_inner->pixmap, 0, NULL);
|
|
if (new_inner->pict == XCB_NONE) {
|
|
log_error("Failed to create picture for copy");
|
|
xcb_free_pixmap(base->c, new_inner->pixmap);
|
|
free(new_inner);
|
|
return NULL;
|
|
}
|
|
new_inner->width = w;
|
|
new_inner->height = h;
|
|
new_inner->visual = visual;
|
|
new_inner->depth = depth;
|
|
new_inner->refcount = 1;
|
|
new_inner->owned = true;
|
|
return new_inner;
|
|
}
|
|
|
|
static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) {
|
|
if (img->inner->refcount == 1) {
|
|
return true;
|
|
}
|
|
auto inner = (struct _xrender_image_data_inner *)img->inner;
|
|
auto inner2 =
|
|
new_inner(base, inner->width, inner->height, inner->visual, inner->depth);
|
|
if (!inner2) {
|
|
return false;
|
|
}
|
|
|
|
x_set_picture_clip_region(base->c, inner->pict, 0, 0, reg);
|
|
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE,
|
|
inner2->pict, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width),
|
|
to_u16_checked(inner->height));
|
|
|
|
img->inner = (struct backend_image_inner_base *)inner2;
|
|
inner->refcount--;
|
|
return true;
|
|
}
|
|
static bool bake_image(backend_t *base, struct backend_image *img, const region_t *reg) {
|
|
struct _xrender_data *xd = (void *)base;
|
|
struct _xrender_image_data_inner *inner = (void *)img->inner;
|
|
assert(inner->visual != XCB_NONE);
|
|
|
|
if (!img->color_inverted && img->opacity == 1 && img->dim == 0) {
|
|
// Nothing to bake
|
|
return true;
|
|
}
|
|
|
|
log_trace("xrender: copying %#010x visual %#x", inner->pixmap, inner->visual);
|
|
auto inner2 =
|
|
new_inner(base, inner->width, inner->height, inner->visual, inner->depth);
|
|
if (!inner2) {
|
|
return false;
|
|
}
|
|
|
|
inner2->has_alpha = (inner->has_alpha || img->opacity != 1);
|
|
compose_impl(xd, img, 0, 0, reg, NULL, inner2->pict);
|
|
|
|
img->opacity = 1;
|
|
img->inner = (struct backend_image_inner_base *)inner2;
|
|
img->dim = 0;
|
|
img->color_inverted = false;
|
|
inner->refcount--;
|
|
if (inner->refcount == 0) {
|
|
release_image_inner(base, inner);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool image_op(backend_t *base, enum image_operations op, void *image,
|
|
const region_t *reg_op, const region_t *reg_visible, void *arg) {
|
|
struct _xrender_data *xd = (void *)base;
|
|
struct backend_image *img = image;
|
|
region_t reg;
|
|
double *dargs = arg;
|
|
|
|
pixman_region32_init(®);
|
|
pixman_region32_intersect(®, (region_t *)reg_op, (region_t *)reg_visible);
|
|
|
|
switch (op) {
|
|
case IMAGE_OP_BAKE_PROPERTIES: return bake_image(base, img, ®);
|
|
|
|
case IMAGE_OP_APPLY_ALPHA:
|
|
assert(reg_op);
|
|
|
|
if (!pixman_region32_not_empty(®)) {
|
|
break;
|
|
}
|
|
|
|
if (dargs[0] == 1) {
|
|
break;
|
|
}
|
|
|
|
if (!decouple_image(base, img, reg_visible)) {
|
|
pixman_region32_fini(®);
|
|
return false;
|
|
}
|
|
|
|
auto inner = (struct _xrender_image_data_inner *)img->inner;
|
|
auto alpha_pict = xd->alpha_pict[(int)((1 - dargs[0]) * MAX_ALPHA)];
|
|
x_set_picture_clip_region(base->c, inner->pict, 0, 0, ®);
|
|
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_OUT_REVERSE, alpha_pict,
|
|
XCB_NONE, inner->pict, 0, 0, 0, 0, 0, 0,
|
|
to_u16_checked(inner->width),
|
|
to_u16_checked(inner->height));
|
|
inner->has_alpha = true;
|
|
break;
|
|
}
|
|
pixman_region32_fini(®);
|
|
return true;
|
|
}
|
|
|
|
static void *
|
|
create_blur_context(backend_t *base attr_unused, enum blur_method method, void *args) {
|
|
auto ret = ccalloc(1, struct _xrender_blur_context);
|
|
if (!method || method >= BLUR_METHOD_INVALID) {
|
|
ret->method = BLUR_METHOD_NONE;
|
|
return ret;
|
|
}
|
|
if (method == BLUR_METHOD_DUAL_KAWASE) {
|
|
log_warn("Blur method 'dual_kawase' is not compatible with the 'xrender' "
|
|
"backend.");
|
|
ret->method = BLUR_METHOD_NONE;
|
|
return ret;
|
|
}
|
|
|
|
ret->method = BLUR_METHOD_KERNEL;
|
|
struct conv **kernels;
|
|
int kernel_count;
|
|
if (method == BLUR_METHOD_KERNEL) {
|
|
kernels = ((struct kernel_blur_args *)args)->kernels;
|
|
kernel_count = ((struct kernel_blur_args *)args)->kernel_count;
|
|
} else {
|
|
kernels = generate_blur_kernel(method, args, &kernel_count);
|
|
}
|
|
|
|
ret->x_blur_kernel = ccalloc(kernel_count, struct x_convolution_kernel *);
|
|
for (int i = 0; i < kernel_count; i++) {
|
|
int center = kernels[i]->h * kernels[i]->w / 2;
|
|
x_create_convolution_kernel(kernels[i], kernels[i]->data[center],
|
|
&ret->x_blur_kernel[i]);
|
|
ret->resize_width += kernels[i]->w / 2;
|
|
ret->resize_height += kernels[i]->h / 2;
|
|
}
|
|
ret->x_blur_kernel_count = kernel_count;
|
|
|
|
if (method != BLUR_METHOD_KERNEL) {
|
|
// Kernels generated by generate_blur_kernel, so we need to free them.
|
|
for (int i = 0; i < kernel_count; i++) {
|
|
free(kernels[i]);
|
|
}
|
|
free(kernels);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void destroy_blur_context(backend_t *base attr_unused, void *ctx_) {
|
|
struct _xrender_blur_context *ctx = ctx_;
|
|
for (int i = 0; i < ctx->x_blur_kernel_count; i++) {
|
|
free(ctx->x_blur_kernel[i]);
|
|
}
|
|
free(ctx->x_blur_kernel);
|
|
free(ctx);
|
|
}
|
|
|
|
static void get_blur_size(void *blur_context, int *width, int *height) {
|
|
struct _xrender_blur_context *ctx = blur_context;
|
|
*width = ctx->resize_width;
|
|
*height = ctx->resize_height;
|
|
}
|
|
|
|
static bool
|
|
read_pixel(backend_t *backend_data, void *image_data, int x, int y, struct color *output) {
|
|
auto xd = (struct _xrender_data *)backend_data;
|
|
auto img = (struct backend_image *)image_data;
|
|
auto inner = (struct _xrender_image_data_inner *)img->inner;
|
|
|
|
auto r = XCB_AWAIT(xcb_get_image, xd->base.c, XCB_IMAGE_FORMAT_XY_PIXMAP, inner->pixmap,
|
|
to_i16_checked(x), to_i16_checked(y), 1, 1, (uint32_t)-1L);
|
|
|
|
if (!r) {
|
|
return false;
|
|
}
|
|
|
|
// Color format seems to be BGRA8888, see glamor_format_for_pixmap from the
|
|
// Xserver codebase.
|
|
uint8_t *pixels = xcb_get_image_data(r);
|
|
output->blue = pixels[0] / 255.0;
|
|
output->green = pixels[1] / 255.0;
|
|
output->red = pixels[2] / 255.0;
|
|
output->alpha = pixels[3] / 255.0;
|
|
|
|
return true;
|
|
}
|
|
|
|
static backend_t *backend_xrender_init(session_t *ps) {
|
|
auto xd = ccalloc(1, struct _xrender_data);
|
|
init_backend_base(&xd->base, ps);
|
|
|
|
for (int i = 0; i <= MAX_ALPHA; ++i) {
|
|
double o = (double)i / (double)MAX_ALPHA;
|
|
xd->alpha_pict[i] = solid_picture(ps->c, ps->root, false, o, 0, 0, 0);
|
|
assert(xd->alpha_pict[i] != XCB_NONE);
|
|
}
|
|
|
|
xd->target_width = ps->root_width;
|
|
xd->target_height = ps->root_height;
|
|
xd->default_visual = ps->vis;
|
|
xd->black_pixel = solid_picture(ps->c, ps->root, true, 1, 0, 0, 0);
|
|
xd->white_pixel = solid_picture(ps->c, ps->root, true, 1, 1, 1, 1);
|
|
|
|
xd->target_win = session_get_target_window(ps);
|
|
xcb_render_create_picture_value_list_t pa = {
|
|
.subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS,
|
|
};
|
|
xd->target = x_create_picture_with_visual_and_pixmap(
|
|
ps->c, ps->vis, xd->target_win, XCB_RENDER_CP_SUBWINDOW_MODE, &pa);
|
|
|
|
auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis);
|
|
if (!pictfmt) {
|
|
log_fatal("Default visual is invalid");
|
|
abort();
|
|
}
|
|
|
|
xd->vsync = ps->o.vsync;
|
|
if (ps->present_exists) {
|
|
auto eid = x_new_id(ps->c);
|
|
auto e =
|
|
xcb_request_check(ps->c, xcb_present_select_input_checked(
|
|
ps->c, eid, xd->target_win,
|
|
XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY));
|
|
if (e) {
|
|
log_error("Cannot select present input, vsync will be disabled");
|
|
xd->vsync = false;
|
|
free(e);
|
|
}
|
|
|
|
xd->present_event =
|
|
xcb_register_for_special_xge(ps->c, &xcb_present_id, eid, NULL);
|
|
if (!xd->present_event) {
|
|
log_error("Cannot register for special XGE, vsync will be "
|
|
"disabled");
|
|
xd->vsync = false;
|
|
}
|
|
} else {
|
|
xd->vsync = false;
|
|
}
|
|
|
|
// We might need to do double buffering for vsync, and buffer 0 and 1 are for
|
|
// double buffering.
|
|
int first_buffer_index = xd->vsync ? 0 : 2;
|
|
for (int i = first_buffer_index; i < 3; i++) {
|
|
xd->back_pixmap[i] = x_create_pixmap(ps->c, pictfmt->depth, ps->root,
|
|
to_u16_checked(ps->root_width),
|
|
to_u16_checked(ps->root_height));
|
|
const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT;
|
|
const xcb_render_create_picture_value_list_t pic_attrs = {
|
|
.repeat = XCB_RENDER_REPEAT_PAD};
|
|
xd->back[i] = x_create_picture_with_pictfmt_and_pixmap(
|
|
ps->c, pictfmt, xd->back_pixmap[i], pic_attrs_mask, &pic_attrs);
|
|
xd->buffer_age[i] = -1;
|
|
if (xd->back_pixmap[i] == XCB_NONE || xd->back[i] == XCB_NONE) {
|
|
log_error("Cannot create pixmap for rendering");
|
|
goto err;
|
|
}
|
|
}
|
|
xd->curr_back = 0;
|
|
|
|
return &xd->base;
|
|
err:
|
|
deinit(&xd->base);
|
|
return NULL;
|
|
}
|
|
|
|
struct backend_operations xrender_ops = {
|
|
.init = backend_xrender_init,
|
|
.deinit = deinit,
|
|
.blur = blur,
|
|
.present = present,
|
|
.compose = compose,
|
|
.fill = fill,
|
|
.bind_pixmap = bind_pixmap,
|
|
.release_image = release_image,
|
|
.render_shadow = default_backend_render_shadow,
|
|
//.prepare_win = prepare_win,
|
|
//.release_win = release_win,
|
|
.is_image_transparent = default_is_image_transparent,
|
|
.buffer_age = buffer_age,
|
|
.max_buffer_age = 2,
|
|
|
|
.image_op = image_op,
|
|
.read_pixel = read_pixel,
|
|
.clone_image = default_clone_image,
|
|
.set_image_property = default_set_image_property,
|
|
.create_blur_context = create_blur_context,
|
|
.destroy_blur_context = destroy_blur_context,
|
|
.get_blur_size = get_blur_size,
|
|
};
|
|
|
|
// vim: set noet sw=8 ts=8:
|