Implement vsync for the new xrender backend
We use the Present extension for that, since it is the best option we have. Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
parent
6413ccbd71
commit
6d3ea3564b
|
@ -34,6 +34,11 @@ typedef struct backend_info {
|
||||||
/// Optional
|
/// Optional
|
||||||
void *(*root_change)(void *backend_data, session_t *ps);
|
void *(*root_change)(void *backend_data, session_t *ps);
|
||||||
|
|
||||||
|
/// Called when vsync is toggled after initialization. If vsync is enabled when init()
|
||||||
|
/// is called, these function won't be called
|
||||||
|
void (*vsync_start)(void *backend_data, session_t *ps);
|
||||||
|
void (*vsync_stop)(void *backend_data, session_t *ps);
|
||||||
|
|
||||||
// =========== Rendering ============
|
// =========== Rendering ============
|
||||||
|
|
||||||
/// Called before any compose() calls.
|
/// Called before any compose() calls.
|
||||||
|
@ -125,9 +130,12 @@ typedef struct backend_info {
|
||||||
/// Return if the frame window has transparent content. Guaranteed to
|
/// Return if the frame window has transparent content. Guaranteed to
|
||||||
/// only be called after render_win is called.
|
/// only be called after render_win is called.
|
||||||
///
|
///
|
||||||
/// Same logic as is_win_transparent applies here.
|
/// Same logic as is_win_transparent applies here.
|
||||||
bool (*is_frame_transparent)(void *backend_data, win *w, void *win_data)
|
bool (*is_frame_transparent)(void *backend_data, win *w, void *win_data)
|
||||||
__attribute__((nonnull(1, 2)));
|
__attribute__((nonnull(1, 2)));
|
||||||
|
|
||||||
|
// =========== Hooks ============
|
||||||
|
/// Let the backend hook into the event handling queue
|
||||||
} backend_info_t;
|
} backend_info_t;
|
||||||
|
|
||||||
extern backend_info_t xrender_backend;
|
extern backend_info_t xrender_backend;
|
||||||
|
|
|
@ -1,20 +1,28 @@
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include "common.h"
|
|
||||||
|
#include <xcb/present.h>
|
||||||
|
#include <xcb/sync.h>
|
||||||
|
|
||||||
#include "backend/backend.h"
|
#include "backend/backend.h"
|
||||||
#include "backend_common.h"
|
#include "backend_common.h"
|
||||||
|
#include "common.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "win.h"
|
#include "win.h"
|
||||||
|
|
||||||
#define auto __auto_type
|
#define auto __auto_type
|
||||||
|
|
||||||
typedef struct _xrender_data {
|
typedef struct _xrender_data {
|
||||||
/// The painting target drawable
|
/// The idle fence for the present extension
|
||||||
xcb_drawable_t target_draw;
|
xcb_sync_fence_t idle_fence;
|
||||||
|
/// The target window
|
||||||
|
xcb_window_t target_win;
|
||||||
/// The painting target, it is either the root or the overlay
|
/// The painting target, it is either the root or the overlay
|
||||||
xcb_render_picture_t target;
|
xcb_render_picture_t target;
|
||||||
/// A buffer of the image to paint
|
/// A back buffer
|
||||||
xcb_render_picture_t target_buffer;
|
xcb_render_picture_t back;
|
||||||
|
/// The corresponding pixmap to the back buffer
|
||||||
|
xcb_pixmap_t back_pixmap;
|
||||||
/// The original root window content, usually the wallpaper.
|
/// The original root window content, usually the wallpaper.
|
||||||
/// We save it so we don't loss the wallpaper when we paint over
|
/// We save it so we don't loss the wallpaper when we paint over
|
||||||
/// it.
|
/// it.
|
||||||
|
@ -87,8 +95,7 @@ static void compose(void *backend_data, session_t *ps, win *w, void *win_data, i
|
||||||
|
|
||||||
// Mask out the region we don't want shadow on
|
// Mask out the region we don't want shadow on
|
||||||
if (pixman_region32_not_empty(&ps->shadow_exclude_reg))
|
if (pixman_region32_not_empty(&ps->shadow_exclude_reg))
|
||||||
pixman_region32_subtract(®_tmp, ®_tmp,
|
pixman_region32_subtract(®_tmp, ®_tmp, &ps->shadow_exclude_reg);
|
||||||
&ps->shadow_exclude_reg);
|
|
||||||
|
|
||||||
// Might be worth while to crop the region to shadow border
|
// Might be worth while to crop the region to shadow border
|
||||||
pixman_region32_intersect_rect(®_tmp, ®_tmp, w->g.x + w->shadow_dx,
|
pixman_region32_intersect_rect(®_tmp, ®_tmp, w->g.x + w->shadow_dx,
|
||||||
|
@ -120,10 +127,10 @@ static void compose(void *backend_data, session_t *ps, win *w, void *win_data, i
|
||||||
|
|
||||||
// Detect if the region is empty before painting
|
// Detect if the region is empty before painting
|
||||||
if (pixman_region32_not_empty(®_tmp)) {
|
if (pixman_region32_not_empty(®_tmp)) {
|
||||||
x_set_picture_clip_region(ps, xd->target_buffer, 0, 0, ®_tmp);
|
x_set_picture_clip_region(ps, xd->back, 0, 0, ®_tmp);
|
||||||
xcb_render_composite(
|
xcb_render_composite(
|
||||||
ps->c, XCB_RENDER_PICT_OP_OVER, wd->shadow_pict, alpha_pict,
|
ps->c, XCB_RENDER_PICT_OP_OVER, wd->shadow_pict, alpha_pict,
|
||||||
xd->target_buffer, 0, 0, 0, 0, dst_x + w->shadow_dx,
|
xd->back, 0, 0, 0, 0, dst_x + w->shadow_dx,
|
||||||
dst_y + w->shadow_dy, w->shadow_width, w->shadow_height);
|
dst_y + w->shadow_dy, w->shadow_width, w->shadow_height);
|
||||||
}
|
}
|
||||||
pixman_region32_fini(®_tmp);
|
pixman_region32_fini(®_tmp);
|
||||||
|
@ -134,9 +141,9 @@ static void compose(void *backend_data, session_t *ps, win *w, void *win_data, i
|
||||||
// sure we get everything into the buffer
|
// sure we get everything into the buffer
|
||||||
x_clear_picture_clip_region(ps, wd->rendered_pict);
|
x_clear_picture_clip_region(ps, wd->rendered_pict);
|
||||||
|
|
||||||
x_set_picture_clip_region(ps, xd->target_buffer, 0, 0, reg_paint);
|
x_set_picture_clip_region(ps, xd->back, 0, 0, reg_paint);
|
||||||
xcb_render_composite(ps->c, op, wd->rendered_pict, alpha_pict, xd->target_buffer,
|
xcb_render_composite(ps->c, op, wd->rendered_pict, alpha_pict, xd->back, 0, 0, 0,
|
||||||
0, 0, 0, 0, dst_x, dst_y, w->widthb, w->heightb);
|
0, dst_x, dst_y, w->widthb, w->heightb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -175,16 +182,16 @@ blur(void *backend_data, session_t *ps, double opacity, const region_t *reg_pain
|
||||||
|
|
||||||
// The multipass blur implemented here is not correct, but this is what old
|
// The multipass blur implemented here is not correct, but this is what old
|
||||||
// compton did anyway. XXX
|
// compton did anyway. XXX
|
||||||
xcb_render_picture_t src_pict = xd->target_buffer, dst_pict = tmp_picture[0];
|
xcb_render_picture_t src_pict = xd->back, dst_pict = tmp_picture[0];
|
||||||
auto alpha_pict = xd->alpha_pict[(int)(opacity * 255)];
|
auto alpha_pict = xd->alpha_pict[(int)(opacity * 255)];
|
||||||
int current = 0;
|
int current = 0;
|
||||||
int src_x = reg->x1, src_y = reg->y1;
|
int src_x = reg->x1, src_y = reg->y1;
|
||||||
|
|
||||||
// For more than 1 pass, we do:
|
// For more than 1 pass, we do:
|
||||||
// target_buffer -(pass 1)-> tmp0 -(pass 2)-> tmp1 ...
|
// back -(pass 1)-> tmp0 -(pass 2)-> tmp1 ...
|
||||||
// -(pass n-1)-> tmp0 or tmp1 -(pass n)-> target_buffer
|
// -(pass n-1)-> tmp0 or tmp1 -(pass n)-> back
|
||||||
// For 1 pass, we do
|
// For 1 pass, we do
|
||||||
// target_buffer -(pass 1)-> tmp0 -(copy)-> target_buffer
|
// back -(pass 1)-> tmp0 -(copy)-> target_buffer
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; ps->o.blur_kerns[i]; i++) {
|
for (i = 0; ps->o.blur_kerns[i]; i++) {
|
||||||
assert(i < MAX_BLUR_PASS - 1);
|
assert(i < MAX_BLUR_PASS - 1);
|
||||||
|
@ -195,9 +202,9 @@ blur(void *backend_data, session_t *ps, double opacity, const region_t *reg_pain
|
||||||
// Copy from source picture to destination. The filter must
|
// Copy from source picture to destination. The filter must
|
||||||
// be applied on source picture, to get the nearby pixels outside the
|
// be applied on source picture, to get the nearby pixels outside the
|
||||||
// window.
|
// window.
|
||||||
xcb_render_set_picture_filter(
|
xcb_render_set_picture_filter(ps->c, src_pict, strlen(XRFILTER_CONVOLUTION),
|
||||||
ps->c, src_pict, strlen(XRFILTER_CONVOLUTION), XRFILTER_CONVOLUTION,
|
XRFILTER_CONVOLUTION, kwid * khei + 2,
|
||||||
kwid * khei + 2, convolution_blur);
|
convolution_blur);
|
||||||
|
|
||||||
if (ps->o.blur_kerns[i + 1] || i == 0) {
|
if (ps->o.blur_kerns[i + 1] || i == 0) {
|
||||||
// This is not the last pass, or this is the first pass
|
// This is not the last pass, or this is the first pass
|
||||||
|
@ -207,8 +214,8 @@ blur(void *backend_data, session_t *ps, double opacity, const region_t *reg_pain
|
||||||
} else {
|
} else {
|
||||||
// This is the last pass, and this is also not the first
|
// This is the last pass, and this is also not the first
|
||||||
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict,
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict,
|
||||||
alpha_pict, xd->target_buffer, 0, 0, 0, 0,
|
alpha_pict, xd->back, 0, 0, 0, 0, reg->x1,
|
||||||
reg->x1, reg->y1, width, height);
|
reg->y1, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
xrfilter_reset(ps, src_pict);
|
xrfilter_reset(ps, src_pict);
|
||||||
|
@ -223,8 +230,7 @@ blur(void *backend_data, session_t *ps, double opacity, const region_t *reg_pain
|
||||||
// There is only 1 pass
|
// There is only 1 pass
|
||||||
if (i == 1) {
|
if (i == 1) {
|
||||||
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict,
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict,
|
||||||
xd->target_buffer, 0, 0, 0, 0, reg->x1, reg->y1,
|
xd->back, 0, 0, 0, 0, reg->x1, reg->y1, width, height);
|
||||||
width, height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_render_free_picture(ps->c, tmp_picture[0]);
|
xcb_render_free_picture(ps->c, tmp_picture[0]);
|
||||||
|
@ -388,16 +394,26 @@ static void *init(session_t *ps) {
|
||||||
if (ps->overlay != XCB_NONE) {
|
if (ps->overlay != XCB_NONE) {
|
||||||
xd->target = x_create_picture_with_visual_and_pixmap(
|
xd->target = x_create_picture_with_visual_and_pixmap(
|
||||||
ps, ps->vis, ps->overlay, 0, NULL);
|
ps, ps->vis, ps->overlay, 0, NULL);
|
||||||
|
xd->target_win = ps->overlay;
|
||||||
} else {
|
} else {
|
||||||
xcb_render_create_picture_value_list_t pa = {
|
xcb_render_create_picture_value_list_t pa = {
|
||||||
.subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS,
|
.subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS,
|
||||||
};
|
};
|
||||||
xd->target = x_create_picture_with_visual_and_pixmap(
|
xd->target = x_create_picture_with_visual_and_pixmap(
|
||||||
ps, ps->vis, ps->root, XCB_RENDER_CP_SUBWINDOW_MODE, &pa);
|
ps, ps->vis, ps->root, XCB_RENDER_CP_SUBWINDOW_MODE, &pa);
|
||||||
|
xd->target_win = ps->root;
|
||||||
}
|
}
|
||||||
|
|
||||||
xd->target_buffer = x_create_picture_with_visual(
|
auto pictfmt = x_get_pictform_for_visual(ps, ps->vis);
|
||||||
ps, ps->root_width, ps->root_height, ps->vis, 0, NULL);
|
if (!pictfmt) {
|
||||||
|
log_fatal("Default visual is invalid");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
xd->back_pixmap =
|
||||||
|
x_create_pixmap(ps, pictfmt->depth, ps->root, ps->root_width, ps->root_height);
|
||||||
|
xd->back =
|
||||||
|
x_create_picture_with_pictfmt_and_pixmap(ps, pictfmt, xd->back_pixmap, 0, NULL);
|
||||||
|
|
||||||
xcb_pixmap_t root_pixmap = x_get_root_back_pixmap(ps);
|
xcb_pixmap_t root_pixmap = x_get_root_back_pixmap(ps);
|
||||||
if (root_pixmap == XCB_NONE) {
|
if (root_pixmap == XCB_NONE) {
|
||||||
|
@ -406,6 +422,20 @@ static void *init(session_t *ps) {
|
||||||
xd->root_pict = x_create_picture_with_visual_and_pixmap(
|
xd->root_pict = x_create_picture_with_visual_and_pixmap(
|
||||||
ps, ps->vis, root_pixmap, 0, NULL);
|
ps, ps->vis, root_pixmap, 0, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps->present_exists) {
|
||||||
|
xd->idle_fence = xcb_generate_id(ps->c);
|
||||||
|
// To make sure we won't get stuck waiting for the idle_fence, we maintain
|
||||||
|
// this invariant: the idle_fence is either triggered, or is in the
|
||||||
|
// process of being triggered (e.g. by xcb_present_pixmap)
|
||||||
|
auto e = xcb_request_check(
|
||||||
|
ps->c, xcb_sync_create_fence(ps->c, ps->root, xd->idle_fence, 1));
|
||||||
|
if (e) {
|
||||||
|
log_error("Cannot create a fence, vsync might not work");
|
||||||
|
xd->idle_fence = XCB_NONE;
|
||||||
|
free(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
return xd;
|
return xd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,29 +453,41 @@ static void *root_change(void *backend_data, session_t *ps) {
|
||||||
return init(ps);
|
return init(ps);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void paint_root(void *backend_data, session_t *ps, const region_t *reg_paint) {
|
static void prepare(void *backend_data, session_t *ps, const region_t *reg_paint) {
|
||||||
struct _xrender_data *xd = backend_data;
|
struct _xrender_data *xd = backend_data;
|
||||||
|
if (ps->o.vsync != VSYNC_NONE && ps->present_exists) {
|
||||||
|
xcb_sync_await_fence(ps->c, 1, &xd->idle_fence);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint the root pixmap (i.e. wallpaper)
|
||||||
// Limit the paint area
|
// Limit the paint area
|
||||||
x_set_picture_clip_region(ps, xd->target_buffer, 0, 0, reg_paint);
|
x_set_picture_clip_region(ps, xd->back, 0, 0, reg_paint);
|
||||||
|
|
||||||
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, xd->root_pict, XCB_NONE,
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, xd->root_pict, XCB_NONE,
|
||||||
xd->target_buffer, 0, 0, 0, 0, 0, 0, ps->root_width,
|
xd->back, 0, 0, 0, 0, 0, 0, ps->root_width, ps->root_height);
|
||||||
ps->root_height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void present(void *backend_data, session_t *ps) {
|
static void present(void *backend_data, session_t *ps) {
|
||||||
struct _xrender_data *xd = backend_data;
|
struct _xrender_data *xd = backend_data;
|
||||||
|
|
||||||
// compose() sets clip region, so clear it first to make
|
if (ps->o.vsync != VSYNC_NONE && ps->present_exists) {
|
||||||
// sure we update the whole screen.
|
// Only reset the fence when we are sure we will trigger it again.
|
||||||
x_clear_picture_clip_region(ps, xd->target_buffer);
|
// To make sure rendering won't get stuck if user toggles vsync on the fly.
|
||||||
|
xcb_sync_reset_fence(ps->c, xd->idle_fence);
|
||||||
|
xcb_present_pixmap(ps->c, xd->target_win, xd->back_pixmap, 0, XCB_NONE,
|
||||||
|
XCB_NONE, 0, 0, XCB_NONE, XCB_NONE, xd->idle_fence, 0,
|
||||||
|
0, 1, 0, 0, NULL);
|
||||||
|
} else {
|
||||||
|
// compose() sets clip region, so clear it first to make
|
||||||
|
// sure we update the whole screen.
|
||||||
|
x_clear_picture_clip_region(ps, xd->back);
|
||||||
|
|
||||||
// TODO buffer-age-like optimization might be possible here.
|
// TODO buffer-age-like optimization might be possible here.
|
||||||
// but that will require a different backend API
|
// but that will require a different backend API
|
||||||
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, xd->target_buffer, None,
|
xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, xd->back, None,
|
||||||
xd->target, 0, 0, 0, 0, 0, 0, ps->root_width,
|
xd->target, 0, 0, 0, 0, 0, 0, ps->root_width,
|
||||||
ps->root_height);
|
ps->root_height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct backend_info xrender_backend = {
|
struct backend_info xrender_backend = {
|
||||||
|
@ -453,7 +495,7 @@ struct backend_info xrender_backend = {
|
||||||
.deinit = deinit,
|
.deinit = deinit,
|
||||||
.blur = blur,
|
.blur = blur,
|
||||||
.present = present,
|
.present = present,
|
||||||
.prepare = paint_root,
|
.prepare = prepare,
|
||||||
.compose = compose,
|
.compose = compose,
|
||||||
.root_change = root_change,
|
.root_change = root_change,
|
||||||
.render_win = render_win,
|
.render_win = render_win,
|
||||||
|
@ -462,3 +504,5 @@ struct backend_info xrender_backend = {
|
||||||
.is_win_transparent = default_is_win_transparent,
|
.is_win_transparent = default_is_win_transparent,
|
||||||
.is_frame_transparent = default_is_frame_transparent,
|
.is_frame_transparent = default_is_frame_transparent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// vim: set noet sw=8 ts=8:
|
||||||
|
|
|
@ -2869,7 +2869,15 @@ session_init(session_t *ps_old, int argc, char **argv) {
|
||||||
|
|
||||||
ext_info = xcb_get_extension_data(ps->c, &xcb_present_id);
|
ext_info = xcb_get_extension_data(ps->c, &xcb_present_id);
|
||||||
if (ext_info && ext_info->present) {
|
if (ext_info && ext_info->present) {
|
||||||
ps->present_exists = true;
|
auto r =
|
||||||
|
xcb_present_query_version_reply(ps->c,
|
||||||
|
xcb_present_query_version(ps->c,
|
||||||
|
XCB_PRESENT_MAJOR_VERSION,
|
||||||
|
XCB_PRESENT_MINOR_VERSION),
|
||||||
|
NULL);
|
||||||
|
if (r) {
|
||||||
|
ps->present_exists = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query X Sync
|
// Query X Sync
|
||||||
|
|
Loading…
Reference in New Issue