2019-02-04 02:47:36 +08:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
2018-10-04 05:46:18 +08:00
|
|
|
#include <assert.h>
|
|
|
|
#include <math.h>
|
2019-01-21 05:15:20 +08:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2019-01-01 22:23:51 +08:00
|
|
|
|
2019-02-18 07:47:46 +08:00
|
|
|
#include <xcb/composite.h>
|
2019-01-01 22:23:51 +08:00
|
|
|
#include <xcb/present.h>
|
2019-01-21 05:15:20 +08:00
|
|
|
#include <xcb/render.h>
|
2019-02-18 07:47:46 +08:00
|
|
|
#include <xcb/sync.h>
|
|
|
|
#include <xcb/xcb.h>
|
2019-01-01 22:23:51 +08:00
|
|
|
|
2018-10-04 05:46:18 +08:00
|
|
|
#include "backend/backend.h"
|
2019-02-03 23:59:31 +08:00
|
|
|
#include "backend/backend_common.h"
|
2019-01-01 22:23:51 +08:00
|
|
|
#include "common.h"
|
2019-01-21 00:53:39 +08:00
|
|
|
#include "config.h"
|
2019-02-25 07:29:48 +08:00
|
|
|
#include "kernel.h"
|
2019-01-21 00:53:39 +08:00
|
|
|
#include "log.h"
|
|
|
|
#include "region.h"
|
2018-10-04 05:46:18 +08:00
|
|
|
#include "utils.h"
|
|
|
|
#include "win.h"
|
2019-01-21 00:53:39 +08:00
|
|
|
#include "x.h"
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
#define auto __auto_type
|
|
|
|
|
|
|
|
typedef struct _xrender_data {
|
2019-01-01 22:23:51 +08:00
|
|
|
/// The idle fence for the present extension
|
|
|
|
xcb_sync_fence_t idle_fence;
|
2019-02-25 07:29:48 +08:00
|
|
|
bool present_in_progress;
|
2019-01-01 22:23:51 +08:00
|
|
|
/// The target window
|
|
|
|
xcb_window_t target_win;
|
2018-10-04 05:46:18 +08:00
|
|
|
/// The painting target, it is either the root or the overlay
|
|
|
|
xcb_render_picture_t target;
|
2019-01-01 22:23:51 +08:00
|
|
|
/// A back buffer
|
2019-02-25 07:53:46 +08:00
|
|
|
xcb_render_picture_t back[2];
|
|
|
|
/// The back buffer we should be painting into
|
|
|
|
int curr_back;
|
2019-01-01 22:23:51 +08:00
|
|
|
/// The corresponding pixmap to the back buffer
|
2019-02-25 07:53:46 +08:00
|
|
|
xcb_pixmap_t back_pixmap[2];
|
2018-10-04 05:46:18 +08:00
|
|
|
/// The original root window content, usually the wallpaper.
|
|
|
|
/// We save it so we don't loss the wallpaper when we paint over
|
|
|
|
/// it.
|
|
|
|
xcb_render_picture_t root_pict;
|
|
|
|
/// 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;
|
|
|
|
|
|
|
|
/// 1x1 picture of the shadow color
|
|
|
|
xcb_render_picture_t shadow_pixel;
|
2019-02-21 02:17:16 +08:00
|
|
|
/// convolution kernel for the shadow
|
|
|
|
conv *shadow_kernel;
|
2019-02-21 00:43:42 +08:00
|
|
|
|
|
|
|
/// Blur kernels converted to X format
|
|
|
|
xcb_render_fixed_t *x_blur_kern[MAX_BLUR_PASS];
|
|
|
|
/// Number of elements in each blur kernel
|
|
|
|
size_t x_blur_kern_size[MAX_BLUR_PASS];
|
2019-02-25 07:29:48 +08:00
|
|
|
|
|
|
|
xcb_special_event_t *present_event;
|
2018-10-04 05:46:18 +08:00
|
|
|
} xrender_data;
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
/**
|
|
|
|
* Paint root window content.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
paint_root(session_t *ps, const region_t *reg_paint) {
|
|
|
|
if (!ps->root_tile_paint.pixmap)
|
|
|
|
get_root_tile(ps);
|
|
|
|
|
|
|
|
paint_region(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint,
|
|
|
|
ps->root_tile_paint.pict);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
struct _xrender_win_data {
|
|
|
|
// 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;
|
|
|
|
// A buffer used for rendering
|
|
|
|
xcb_render_picture_t buffer;
|
|
|
|
// The rendered content of the window (dimmed, inverted
|
|
|
|
// color, etc.). This is either `buffer` or `pict`
|
|
|
|
xcb_render_picture_t rendered_pict;
|
|
|
|
xcb_pixmap_t shadow_pixmap;
|
|
|
|
xcb_render_picture_t shadow_pict;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void compose(void *backend_data, session_t *ps, win *w, void *win_data, int dst_x,
|
|
|
|
int dst_y, const region_t *reg_paint) {
|
|
|
|
struct _xrender_data *xd = backend_data;
|
|
|
|
struct _xrender_win_data *wd = win_data;
|
|
|
|
bool blend = default_is_frame_transparent(NULL, w, win_data) ||
|
|
|
|
default_is_win_transparent(NULL, w, win_data);
|
|
|
|
int op = (blend ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC);
|
2019-02-09 23:50:40 +08:00
|
|
|
auto alpha_pict = xd->alpha_pict[(int)(w->opacity * 255.0)];
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
// XXX Move shadow drawing into a separate function,
|
|
|
|
// also do shadow excluding outside of backend
|
|
|
|
// XXX This is needed to implement full-shadow
|
|
|
|
if (w->shadow) {
|
|
|
|
// Put shadow on background
|
|
|
|
region_t shadow_reg = win_extents_by_val(w);
|
|
|
|
region_t bshape = win_get_bounding_shape_global_by_val(w);
|
|
|
|
region_t reg_tmp;
|
|
|
|
pixman_region32_init(®_tmp);
|
|
|
|
// Shadow doesn't need to be painted underneath the body of the window
|
|
|
|
// Because no one can see it
|
|
|
|
pixman_region32_subtract(®_tmp, &shadow_reg, w->reg_ignore);
|
|
|
|
|
|
|
|
// Mask out the region we don't want shadow on
|
|
|
|
if (pixman_region32_not_empty(&ps->shadow_exclude_reg))
|
2019-01-01 22:23:51 +08:00
|
|
|
pixman_region32_subtract(®_tmp, ®_tmp, &ps->shadow_exclude_reg);
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
// Might be worth while to crop the region to shadow border
|
|
|
|
pixman_region32_intersect_rect(®_tmp, ®_tmp, w->g.x + w->shadow_dx,
|
|
|
|
w->g.y + w->shadow_dy, w->shadow_width,
|
|
|
|
w->shadow_height);
|
|
|
|
|
|
|
|
// Crop the shadow to the damage region. If we draw out side of
|
|
|
|
// the damage region, we could be drawing over perfectly good
|
|
|
|
// content, and destroying it.
|
|
|
|
pixman_region32_intersect(®_tmp, ®_tmp, (region_t *)reg_paint);
|
|
|
|
|
2019-02-21 00:43:42 +08:00
|
|
|
if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0 &&
|
|
|
|
w->xinerama_scr < ps->xinerama_nscrs)
|
2018-10-04 05:46:18 +08:00
|
|
|
// There can be a window where number of screens is updated,
|
|
|
|
// but the screen number attached to the windows have not.
|
|
|
|
//
|
|
|
|
// Window screen number will be updated eventually, so here we
|
|
|
|
// just check to make sure we don't access out of bounds.
|
|
|
|
pixman_region32_intersect(
|
|
|
|
®_tmp, ®_tmp, &ps->xinerama_scr_regs[w->xinerama_scr]);
|
|
|
|
|
|
|
|
// Mask out the body of the window from the shadow
|
|
|
|
// Doing it here instead of in make_shadow() for saving GPU
|
|
|
|
// power and handling shaped windows (XXX unconfirmed)
|
|
|
|
pixman_region32_subtract(®_tmp, ®_tmp, &bshape);
|
|
|
|
pixman_region32_fini(&bshape);
|
|
|
|
|
|
|
|
// Detect if the region is empty before painting
|
|
|
|
if (pixman_region32_not_empty(®_tmp)) {
|
2019-02-25 07:53:46 +08:00
|
|
|
x_set_picture_clip_region(ps->c, xd->back[xd->curr_back], 0, 0,
|
|
|
|
®_tmp);
|
|
|
|
xcb_render_composite(
|
|
|
|
ps->c, XCB_RENDER_PICT_OP_OVER, wd->shadow_pict, alpha_pict,
|
|
|
|
xd->back[xd->curr_back], 0, 0, 0, 0, dst_x + w->shadow_dx,
|
|
|
|
dst_y + w->shadow_dy, w->shadow_width, w->shadow_height);
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
|
|
|
pixman_region32_fini(®_tmp);
|
|
|
|
pixman_region32_fini(&shadow_reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clip region of rendered_pict might be set during rendering, clear it to make
|
|
|
|
// sure we get everything into the buffer
|
2019-02-03 23:59:31 +08:00
|
|
|
x_clear_picture_clip_region(ps->c, wd->rendered_pict);
|
2018-10-04 05:46:18 +08:00
|
|
|
|
2019-02-25 07:53:46 +08:00
|
|
|
x_set_picture_clip_region(ps->c, xd->back[xd->curr_back], 0, 0, reg_paint);
|
|
|
|
xcb_render_composite(ps->c, op, wd->rendered_pict, alpha_pict, xd->back[xd->curr_back],
|
|
|
|
0, 0, 0, 0, dst_x, dst_y, w->widthb, w->heightb);
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
|
|
|
|
2019-02-18 07:47:46 +08:00
|
|
|
static bool
|
|
|
|
blur(void *backend_data, session_t *ps, double opacity, const region_t *reg_paint) {
|
2018-10-04 05:46:18 +08:00
|
|
|
struct _xrender_data *xd = backend_data;
|
|
|
|
const pixman_box32_t *reg = pixman_region32_extents((region_t *)reg_paint);
|
|
|
|
const int height = reg->y2 - reg->y1;
|
|
|
|
const int width = reg->x2 - reg->x1;
|
2019-02-21 00:43:42 +08:00
|
|
|
static const char *filter0 = "Nearest"; // The "null" filter
|
|
|
|
static const char *filter = "convolution";
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
// Create a buffer for storing blurred picture, make it just big enough
|
|
|
|
// for the blur region
|
|
|
|
xcb_render_picture_t tmp_picture[2] = {
|
|
|
|
x_create_picture_with_visual(ps, width, height, ps->vis, 0, NULL),
|
|
|
|
x_create_picture_with_visual(ps, width, height, ps->vis, 0, NULL)};
|
|
|
|
|
|
|
|
region_t clip;
|
|
|
|
pixman_region32_init(&clip);
|
|
|
|
pixman_region32_copy(&clip, (region_t *)reg_paint);
|
|
|
|
pixman_region32_translate(&clip, -reg->x1, -reg->y1);
|
|
|
|
|
|
|
|
if (!tmp_picture[0] || !tmp_picture[1]) {
|
|
|
|
log_error("Failed to build intermediate Picture.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-02-03 23:59:31 +08:00
|
|
|
x_set_picture_clip_region(ps->c, tmp_picture[0], 0, 0, &clip);
|
|
|
|
x_set_picture_clip_region(ps->c, tmp_picture[1], 0, 0, &clip);
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
// The multipass blur implemented here is not correct, but this is what old
|
|
|
|
// compton did anyway. XXX
|
2019-02-25 07:53:46 +08:00
|
|
|
xcb_render_picture_t src_pict = xd->back[xd->curr_back], dst_pict = tmp_picture[0];
|
2018-10-04 05:46:18 +08:00
|
|
|
auto alpha_pict = xd->alpha_pict[(int)(opacity * 255)];
|
|
|
|
int current = 0;
|
|
|
|
int src_x = reg->x1, src_y = reg->y1;
|
|
|
|
|
|
|
|
// For more than 1 pass, we do:
|
2019-01-01 22:23:51 +08:00
|
|
|
// back -(pass 1)-> tmp0 -(pass 2)-> tmp1 ...
|
|
|
|
// -(pass n-1)-> tmp0 or tmp1 -(pass n)-> back
|
2018-10-04 05:46:18 +08:00
|
|
|
// For 1 pass, we do
|
2019-01-01 22:23:51 +08:00
|
|
|
// back -(pass 1)-> tmp0 -(copy)-> target_buffer
|
2018-10-04 05:46:18 +08:00
|
|
|
int i;
|
2019-02-21 00:43:42 +08:00
|
|
|
for (i = 0; xd->x_blur_kern[i]; i++) {
|
2018-10-04 05:46:18 +08:00
|
|
|
assert(i < MAX_BLUR_PASS - 1);
|
|
|
|
|
|
|
|
// Copy from source picture to destination. The filter must
|
|
|
|
// be applied on source picture, to get the nearby pixels outside the
|
|
|
|
// window.
|
2019-02-18 07:47:46 +08:00
|
|
|
// TODO cache converted blur_kerns
|
2019-02-21 00:43:42 +08:00
|
|
|
xcb_render_set_picture_filter(ps->c, src_pict, strlen(filter), filter,
|
|
|
|
xd->x_blur_kern_size[i], xd->x_blur_kern[i]);
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
if (ps->o.blur_kerns[i + 1] || i == 0) {
|
|
|
|
// This is not the last pass, or this is the first pass
|
2019-02-18 07:47:46 +08:00
|
|
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict,
|
|
|
|
XCB_NONE, dst_pict, src_x, src_y, 0, 0, 0, 0,
|
|
|
|
width, height);
|
2018-10-04 05:46:18 +08:00
|
|
|
} else {
|
|
|
|
// This is the last pass, and this is also not the first
|
|
|
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict,
|
2019-02-25 07:53:46 +08:00
|
|
|
alpha_pict, xd->back[xd->curr_back], 0, 0, 0,
|
|
|
|
0, reg->x1, reg->y1, width, height);
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
|
|
|
|
2019-02-18 07:47:46 +08:00
|
|
|
// reset filter
|
2019-02-21 00:43:42 +08:00
|
|
|
xcb_render_set_picture_filter(ps->c, src_pict, strlen(filter0), filter0,
|
|
|
|
0, NULL);
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
src_pict = tmp_picture[current];
|
|
|
|
dst_pict = tmp_picture[!current];
|
|
|
|
src_x = 0;
|
|
|
|
src_y = 0;
|
|
|
|
current = !current;
|
|
|
|
}
|
|
|
|
|
|
|
|
// There is only 1 pass
|
|
|
|
if (i == 1) {
|
|
|
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict,
|
2019-02-25 07:53:46 +08:00
|
|
|
xd->back[xd->curr_back], 0, 0, 0, 0, reg->x1,
|
|
|
|
reg->y1, width, height);
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
xcb_render_free_picture(ps->c, tmp_picture[0]);
|
|
|
|
xcb_render_free_picture(ps->c, tmp_picture[1]);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-02-18 07:47:46 +08:00
|
|
|
static void render_win(void *backend_data, session_t *ps, win *w, void *win_data,
|
|
|
|
const region_t *reg_paint) {
|
2018-10-04 05:46:18 +08:00
|
|
|
struct _xrender_data *xd = backend_data;
|
|
|
|
struct _xrender_win_data *wd = win_data;
|
|
|
|
|
|
|
|
w->pixmap_damaged = false;
|
|
|
|
|
|
|
|
if (!w->invert_color && w->frame_opacity == 1 && !w->dim) {
|
|
|
|
// No extra processing needed
|
|
|
|
wd->rendered_pict = wd->pict;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-12-31 16:22:57 +08:00
|
|
|
region_t reg_paint_local;
|
|
|
|
pixman_region32_init(®_paint_local);
|
|
|
|
pixman_region32_copy(®_paint_local, (region_t *)reg_paint);
|
|
|
|
pixman_region32_translate(®_paint_local, -w->g.x, -w->g.y);
|
|
|
|
|
2018-10-04 05:46:18 +08:00
|
|
|
// We don't want to modify the content of the original window when we process
|
|
|
|
// it, so we create a buffer.
|
|
|
|
if (wd->buffer == XCB_NONE) {
|
|
|
|
wd->buffer = x_create_picture_with_pictfmt(ps, w->widthb, w->heightb,
|
|
|
|
w->pictfmt, 0, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the content of the window over to the buffer
|
2019-02-03 23:59:31 +08:00
|
|
|
x_clear_picture_clip_region(ps->c, wd->buffer);
|
2018-10-04 05:46:18 +08:00
|
|
|
wd->rendered_pict = wd->buffer;
|
2019-01-21 00:53:39 +08:00
|
|
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, wd->pict, XCB_NONE,
|
2018-10-04 05:46:18 +08:00
|
|
|
wd->rendered_pict, 0, 0, 0, 0, 0, 0, w->widthb, w->heightb);
|
|
|
|
|
|
|
|
if (w->invert_color) {
|
|
|
|
// Handle invert color
|
2019-02-03 23:59:31 +08:00
|
|
|
x_set_picture_clip_region(ps->c, wd->rendered_pict, 0, 0, ®_paint_local);
|
2018-10-04 05:46:18 +08:00
|
|
|
|
2019-02-18 07:47:46 +08:00
|
|
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_DIFFERENCE,
|
|
|
|
xd->white_pixel, XCB_NONE, wd->rendered_pict, 0, 0,
|
|
|
|
0, 0, 0, 0, w->widthb, w->heightb);
|
2018-10-04 05:46:18 +08:00
|
|
|
// We use an extra PictOpInReverse operation to get correct pixel
|
|
|
|
// alpha. There could be a better solution.
|
|
|
|
if (win_has_alpha(w))
|
|
|
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_IN_REVERSE,
|
2019-01-21 00:53:39 +08:00
|
|
|
wd->pict, XCB_NONE, wd->rendered_pict, 0, 0,
|
|
|
|
0, 0, 0, 0, w->widthb, w->heightb);
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (w->frame_opacity != 1) {
|
|
|
|
// Handle transparent frame
|
|
|
|
// Step 1: clip paint area to frame
|
|
|
|
region_t frame_reg;
|
|
|
|
pixman_region32_init(&frame_reg);
|
|
|
|
pixman_region32_copy(&frame_reg, &w->bounding_shape);
|
|
|
|
|
|
|
|
region_t body_reg = win_get_region_noframe_local_by_val(w);
|
|
|
|
pixman_region32_subtract(&frame_reg, &frame_reg, &body_reg);
|
|
|
|
|
|
|
|
// Draw the frame with frame opacity
|
|
|
|
xcb_render_picture_t alpha_pict =
|
2019-02-09 23:50:40 +08:00
|
|
|
xd->alpha_pict[(int)(w->frame_opacity * w->opacity * 255)];
|
2019-02-03 23:59:31 +08:00
|
|
|
x_set_picture_clip_region(ps->c, wd->rendered_pict, 0, 0, &frame_reg);
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
// Step 2: multiply alpha value
|
|
|
|
// XXX test
|
2019-02-18 07:47:46 +08:00
|
|
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, xd->white_pixel,
|
|
|
|
alpha_pict, wd->rendered_pict, 0, 0, 0, 0, 0, 0,
|
|
|
|
w->widthb, w->heightb);
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (w->dim) {
|
|
|
|
// Handle dimming
|
|
|
|
|
|
|
|
double dim_opacity = ps->o.inactive_dim;
|
|
|
|
if (!ps->o.inactive_dim_fixed)
|
2019-02-09 23:50:40 +08:00
|
|
|
dim_opacity *= w->opacity;
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
xcb_render_color_t color = {
|
|
|
|
.red = 0, .green = 0, .blue = 0, .alpha = 0xffff * dim_opacity};
|
|
|
|
|
|
|
|
// Dim the actually content of window
|
|
|
|
xcb_rectangle_t rect = {
|
|
|
|
.x = 0,
|
|
|
|
.y = 0,
|
|
|
|
.width = w->widthb,
|
|
|
|
.height = w->heightb,
|
|
|
|
};
|
|
|
|
|
2019-02-03 23:59:31 +08:00
|
|
|
x_clear_picture_clip_region(ps->c, wd->rendered_pict);
|
2018-10-04 05:46:18 +08:00
|
|
|
xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_OVER,
|
|
|
|
wd->rendered_pict, color, 1, &rect);
|
|
|
|
}
|
2018-12-31 16:22:57 +08:00
|
|
|
|
2018-12-31 22:08:35 +08:00
|
|
|
pixman_region32_fini(®_paint_local);
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void *prepare_win(void *backend_data, session_t *ps, win *w) {
|
|
|
|
auto wd = ccalloc(1, struct _xrender_win_data);
|
|
|
|
struct _xrender_data *xd = backend_data;
|
|
|
|
assert(w->a.map_state == XCB_MAP_STATE_VIEWABLE);
|
|
|
|
if (ps->has_name_pixmap) {
|
|
|
|
wd->pixmap = xcb_generate_id(ps->c);
|
|
|
|
xcb_composite_name_window_pixmap_checked(ps->c, w->id, wd->pixmap);
|
|
|
|
}
|
|
|
|
|
|
|
|
xcb_drawable_t draw = wd->pixmap;
|
|
|
|
if (!draw)
|
|
|
|
draw = w->id;
|
|
|
|
|
|
|
|
log_trace("%s %x", w->name, wd->pixmap);
|
2019-02-03 23:59:31 +08:00
|
|
|
wd->pict = x_create_picture_with_pictfmt_and_pixmap(ps->c, w->pictfmt, draw, 0, NULL);
|
2018-10-04 05:46:18 +08:00
|
|
|
wd->buffer = XCB_NONE;
|
|
|
|
|
|
|
|
// XXX delay allocating shadow pict until compose() will dramatical
|
|
|
|
// improve performance, probably because otherwise shadow pict
|
|
|
|
// can be created and destroyed multiple times per draw.
|
|
|
|
//
|
|
|
|
// However doing that breaks a assumption the backend API makes (i.e.
|
|
|
|
// either all needed data is here, or none is), therefore we will
|
|
|
|
// leave this here until we have chance to re-think the backend API
|
|
|
|
if (w->shadow) {
|
|
|
|
xcb_pixmap_t pixmap;
|
2019-02-21 02:17:16 +08:00
|
|
|
build_shadow(ps, 1, w->widthb, w->heightb, xd->shadow_kernel,
|
|
|
|
xd->shadow_pixel, &pixmap, &wd->shadow_pict);
|
2018-10-04 05:46:18 +08:00
|
|
|
xcb_free_pixmap(ps->c, pixmap);
|
|
|
|
}
|
|
|
|
return wd;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void release_win(void *backend_data, session_t *ps, win *w, void *win_data) {
|
|
|
|
struct _xrender_win_data *wd = win_data;
|
|
|
|
xcb_free_pixmap(ps->c, wd->pixmap);
|
|
|
|
// xcb_free_pixmap(ps->c, wd->shadow_pixmap);
|
|
|
|
xcb_render_free_picture(ps->c, wd->pict);
|
|
|
|
xcb_render_free_picture(ps->c, wd->shadow_pict);
|
|
|
|
if (wd->buffer != XCB_NONE)
|
|
|
|
xcb_render_free_picture(ps->c, wd->buffer);
|
|
|
|
free(wd);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *init(session_t *ps) {
|
|
|
|
auto xd = ccalloc(1, struct _xrender_data);
|
|
|
|
|
|
|
|
for (int i = 0; i < 256; ++i) {
|
|
|
|
double o = (double)i / 255.0;
|
|
|
|
xd->alpha_pict[i] = solid_picture(ps, false, o, 0, 0, 0);
|
2019-01-21 00:53:39 +08:00
|
|
|
assert(xd->alpha_pict[i] != XCB_NONE);
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
xd->black_pixel = solid_picture(ps, true, 1, 0, 0, 0);
|
|
|
|
xd->white_pixel = solid_picture(ps, true, 1, 1, 1, 1);
|
2019-02-18 07:47:46 +08:00
|
|
|
xd->shadow_pixel = solid_picture(ps, true, 1, ps->o.shadow_red,
|
|
|
|
ps->o.shadow_green, ps->o.shadow_blue);
|
2019-02-21 02:17:16 +08:00
|
|
|
xd->shadow_kernel = gaussian_kernel(ps->o.shadow_radius);
|
2019-02-25 07:29:48 +08:00
|
|
|
xd->present_in_progress = false;
|
2019-02-21 02:17:16 +08:00
|
|
|
sum_kernel_preprocess(xd->shadow_kernel);
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
if (ps->overlay != XCB_NONE) {
|
2019-02-18 07:47:46 +08:00
|
|
|
xd->target = x_create_picture_with_visual_and_pixmap(
|
|
|
|
ps->c, ps->vis, ps->overlay, 0, NULL);
|
2019-01-01 22:23:51 +08:00
|
|
|
xd->target_win = ps->overlay;
|
2018-10-04 05:46:18 +08:00
|
|
|
} else {
|
|
|
|
xcb_render_create_picture_value_list_t pa = {
|
|
|
|
.subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS,
|
|
|
|
};
|
|
|
|
xd->target = x_create_picture_with_visual_and_pixmap(
|
2019-02-03 23:59:31 +08:00
|
|
|
ps->c, ps->vis, ps->root, XCB_RENDER_CP_SUBWINDOW_MODE, &pa);
|
2019-01-01 22:23:51 +08:00
|
|
|
xd->target_win = ps->root;
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
|
|
|
|
2019-02-03 23:59:31 +08:00
|
|
|
auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis);
|
2019-01-01 22:23:51 +08:00
|
|
|
if (!pictfmt) {
|
|
|
|
log_fatal("Default visual is invalid");
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
2019-02-25 07:53:46 +08:00
|
|
|
// We might need to do double buffering for vsync
|
|
|
|
int pixmap_needed = ps->o.vsync ? 2 : 1;
|
|
|
|
for (int i = 0; i < pixmap_needed; i++) {
|
|
|
|
xd->back_pixmap[i] = x_create_pixmap(ps->c, pictfmt->depth, ps->root,
|
|
|
|
ps->root_width, ps->root_height);
|
|
|
|
xd->back[i] = x_create_picture_with_pictfmt_and_pixmap(
|
|
|
|
ps->c, pictfmt, xd->back_pixmap[i], 0, NULL);
|
|
|
|
}
|
|
|
|
xd->curr_back = 0;
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
xcb_pixmap_t root_pixmap = x_get_root_back_pixmap(ps);
|
|
|
|
if (root_pixmap == XCB_NONE) {
|
|
|
|
xd->root_pict = solid_picture(ps, false, 1, 0.5, 0.5, 0.5);
|
|
|
|
} else {
|
2019-02-18 07:47:46 +08:00
|
|
|
xd->root_pict = x_create_picture_with_visual_and_pixmap(
|
|
|
|
ps->c, ps->vis, root_pixmap, 0, NULL);
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
2019-01-01 22:23:51 +08:00
|
|
|
|
|
|
|
if (ps->present_exists) {
|
2019-02-25 07:29:48 +08:00
|
|
|
auto eid = xcb_generate_id(ps->c);
|
2019-02-25 07:53:46 +08:00
|
|
|
auto e =
|
|
|
|
xcb_request_check(ps->c, xcb_present_select_input_checked(
|
2019-02-25 07:29:48 +08:00
|
|
|
ps->c, eid, xd->target_win,
|
|
|
|
XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY));
|
2019-01-01 22:23:51 +08:00
|
|
|
if (e) {
|
2019-02-25 07:29:48 +08:00
|
|
|
log_error("Cannot select present input, vsync will be disabled");
|
2019-01-01 22:23:51 +08:00
|
|
|
free(e);
|
|
|
|
}
|
2019-02-25 07:29:48 +08:00
|
|
|
|
2019-02-25 07:53:46 +08:00
|
|
|
xd->present_event =
|
|
|
|
xcb_register_for_special_xge(ps->c, &xcb_present_id, eid, NULL);
|
2019-02-25 07:29:48 +08:00
|
|
|
if (!xd->present_event) {
|
2019-02-25 07:53:46 +08:00
|
|
|
log_error("Cannot register for special XGE, vsync will be "
|
|
|
|
"disabled");
|
2019-02-25 07:29:48 +08:00
|
|
|
}
|
2019-01-01 22:23:51 +08:00
|
|
|
}
|
2019-02-21 00:43:42 +08:00
|
|
|
for (int i = 0; ps->o.blur_kerns[i]; i++) {
|
|
|
|
assert(i < MAX_BLUR_PASS - 1);
|
|
|
|
xd->x_blur_kern_size[i] = x_picture_filter_from_conv(
|
|
|
|
ps->o.blur_kerns[i], 1, &xd->x_blur_kern[i], (size_t[]){0});
|
|
|
|
}
|
2018-10-04 05:46:18 +08:00
|
|
|
return xd;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void deinit(void *backend_data, session_t *ps) {
|
|
|
|
struct _xrender_data *xd = backend_data;
|
|
|
|
for (int i = 0; i < 256; i++)
|
|
|
|
xcb_render_free_picture(ps->c, xd->alpha_pict[i]);
|
|
|
|
xcb_render_free_picture(ps->c, xd->white_pixel);
|
|
|
|
xcb_render_free_picture(ps->c, xd->black_pixel);
|
2019-02-21 02:21:40 +08:00
|
|
|
free_conv(xd->shadow_kernel);
|
2018-10-04 05:46:18 +08:00
|
|
|
free(xd);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *root_change(void *backend_data, session_t *ps) {
|
|
|
|
deinit(backend_data, ps);
|
|
|
|
return init(ps);
|
|
|
|
}
|
|
|
|
|
2019-01-01 22:23:51 +08:00
|
|
|
static void prepare(void *backend_data, session_t *ps, const region_t *reg_paint) {
|
2018-10-04 05:46:18 +08:00
|
|
|
struct _xrender_data *xd = backend_data;
|
2019-02-25 07:29:48 +08:00
|
|
|
if (ps->o.vsync != VSYNC_NONE && ps->present_exists && xd->present_in_progress) {
|
|
|
|
// TODO don't block wait for present completion
|
2019-02-25 07:53:46 +08:00
|
|
|
xcb_present_generic_event_t *pev =
|
|
|
|
(void *)xcb_wait_for_special_event(ps->c, xd->present_event);
|
2019-02-25 07:29:48 +08:00
|
|
|
assert(pev->evtype == XCB_PRESENT_COMPLETE_NOTIFY);
|
2019-02-25 07:53:46 +08:00
|
|
|
xcb_present_complete_notify_event_t *pcev = (void *)pev;
|
|
|
|
// log_trace("Present complete: %d %ld", pcev->mode, pcev->msc);
|
|
|
|
if (pcev->mode == XCB_PRESENT_COMPLETE_MODE_FLIP) {
|
|
|
|
// We cannot use the pixmap we used anymore
|
|
|
|
xd->curr_back = 1 - xd->curr_back;
|
|
|
|
}
|
2019-02-25 07:29:48 +08:00
|
|
|
free(pev);
|
|
|
|
|
|
|
|
xd->present_in_progress = false;
|
2019-01-01 22:23:51 +08:00
|
|
|
}
|
2018-10-04 05:46:18 +08:00
|
|
|
|
2019-01-01 22:23:51 +08:00
|
|
|
// Paint the root pixmap (i.e. wallpaper)
|
2018-10-04 05:46:18 +08:00
|
|
|
// Limit the paint area
|
2019-02-25 07:53:46 +08:00
|
|
|
x_set_picture_clip_region(ps->c, xd->back[xd->curr_back], 0, 0, reg_paint);
|
2018-10-04 05:46:18 +08:00
|
|
|
|
|
|
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, xd->root_pict, XCB_NONE,
|
2019-02-25 07:53:46 +08:00
|
|
|
xd->back[xd->curr_back], 0, 0, 0, 0, 0, 0, ps->root_width,
|
|
|
|
ps->root_height);
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void present(void *backend_data, session_t *ps) {
|
|
|
|
struct _xrender_data *xd = backend_data;
|
|
|
|
|
2019-01-01 22:23:51 +08:00
|
|
|
if (ps->o.vsync != VSYNC_NONE && ps->present_exists) {
|
|
|
|
// Only reset the fence when we are sure we will trigger it again.
|
2019-01-21 00:53:39 +08:00
|
|
|
// To make sure rendering won't get stuck if user toggles vsync on the
|
|
|
|
// fly.
|
2019-02-25 07:53:46 +08:00
|
|
|
xcb_present_pixmap(ps->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);
|
2019-02-25 07:29:48 +08:00
|
|
|
xd->present_in_progress = true;
|
2019-01-01 22:23:51 +08:00
|
|
|
} else {
|
|
|
|
// compose() sets clip region, so clear it first to make
|
|
|
|
// sure we update the whole screen.
|
2019-02-25 07:53:46 +08:00
|
|
|
x_clear_picture_clip_region(ps->c, xd->back[xd->curr_back]);
|
2019-01-01 22:23:51 +08:00
|
|
|
|
|
|
|
// TODO buffer-age-like optimization might be possible here.
|
|
|
|
// but that will require a different backend API
|
2019-02-25 07:53:46 +08:00
|
|
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC,
|
|
|
|
xd->back[xd->curr_back], XCB_NONE, xd->target, 0, 0,
|
|
|
|
0, 0, 0, 0, ps->root_width, ps->root_height);
|
2019-01-01 22:23:51 +08:00
|
|
|
}
|
2018-10-04 05:46:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
struct backend_info xrender_backend = {
|
|
|
|
.init = init,
|
|
|
|
.deinit = deinit,
|
|
|
|
.blur = blur,
|
|
|
|
.present = present,
|
2019-01-01 22:23:51 +08:00
|
|
|
.prepare = prepare,
|
2018-10-04 05:46:18 +08:00
|
|
|
.compose = compose,
|
|
|
|
.root_change = root_change,
|
|
|
|
.render_win = render_win,
|
|
|
|
.prepare_win = prepare_win,
|
|
|
|
.release_win = release_win,
|
|
|
|
.is_win_transparent = default_is_win_transparent,
|
|
|
|
.is_frame_transparent = default_is_frame_transparent,
|
2019-01-28 03:45:40 +08:00
|
|
|
.max_buffer_age = 2,
|
2018-10-04 05:46:18 +08:00
|
|
|
};
|
2019-01-01 22:23:51 +08:00
|
|
|
|
|
|
|
// vim: set noet sw=8 ts=8:
|