From 6d3ea3564b37d760cf03c798137ddcbc08b0ff5f Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 1 Jan 2019 14:23:51 +0000 Subject: [PATCH] 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 --- src/backend/backend.h | 10 +++- src/backend/xrender.c | 120 +++++++++++++++++++++++++++++------------- src/compton.c | 10 +++- 3 files changed, 100 insertions(+), 40 deletions(-) diff --git a/src/backend/backend.h b/src/backend/backend.h index afc57857..22142b3c 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -34,6 +34,11 @@ typedef struct backend_info { /// Optional 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 ============ /// Called before any compose() calls. @@ -125,9 +130,12 @@ typedef struct backend_info { /// Return if the frame window has transparent content. Guaranteed to /// 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) __attribute__((nonnull(1, 2))); + + // =========== Hooks ============ + /// Let the backend hook into the event handling queue } backend_info_t; extern backend_info_t xrender_backend; diff --git a/src/backend/xrender.c b/src/backend/xrender.c index 977d3fc0..4a49d5c5 100644 --- a/src/backend/xrender.c +++ b/src/backend/xrender.c @@ -1,20 +1,28 @@ #include #include -#include "common.h" + +#include +#include + #include "backend/backend.h" #include "backend_common.h" +#include "common.h" #include "utils.h" #include "win.h" #define auto __auto_type typedef struct _xrender_data { - /// The painting target drawable - xcb_drawable_t target_draw; + /// The idle fence for the present extension + 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 xcb_render_picture_t target; - /// A buffer of the image to paint - xcb_render_picture_t target_buffer; + /// A back 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. /// We save it so we don't loss the wallpaper when we paint over /// 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 if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) - pixman_region32_subtract(®_tmp, ®_tmp, - &ps->shadow_exclude_reg); + pixman_region32_subtract(®_tmp, ®_tmp, &ps->shadow_exclude_reg); // Might be worth while to crop the region to shadow border 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 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( 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); } 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 x_clear_picture_clip_region(ps, wd->rendered_pict); - x_set_picture_clip_region(ps, xd->target_buffer, 0, 0, reg_paint); - xcb_render_composite(ps->c, op, wd->rendered_pict, alpha_pict, xd->target_buffer, - 0, 0, 0, 0, dst_x, dst_y, w->widthb, w->heightb); + x_set_picture_clip_region(ps, xd->back, 0, 0, reg_paint); + xcb_render_composite(ps->c, op, wd->rendered_pict, alpha_pict, xd->back, 0, 0, 0, + 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 // 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)]; int current = 0; int src_x = reg->x1, src_y = reg->y1; // For more than 1 pass, we do: - // target_buffer -(pass 1)-> tmp0 -(pass 2)-> tmp1 ... - // -(pass n-1)-> tmp0 or tmp1 -(pass n)-> target_buffer + // back -(pass 1)-> tmp0 -(pass 2)-> tmp1 ... + // -(pass n-1)-> tmp0 or tmp1 -(pass n)-> back // For 1 pass, we do - // target_buffer -(pass 1)-> tmp0 -(copy)-> target_buffer + // back -(pass 1)-> tmp0 -(copy)-> target_buffer int i; for (i = 0; ps->o.blur_kerns[i]; i++) { 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 // be applied on source picture, to get the nearby pixels outside the // window. - xcb_render_set_picture_filter( - ps->c, src_pict, strlen(XRFILTER_CONVOLUTION), XRFILTER_CONVOLUTION, - kwid * khei + 2, convolution_blur); + xcb_render_set_picture_filter(ps->c, src_pict, strlen(XRFILTER_CONVOLUTION), + XRFILTER_CONVOLUTION, kwid * khei + 2, + convolution_blur); if (ps->o.blur_kerns[i + 1] || i == 0) { // 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 { // This is the last pass, and this is also not the first 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, width, height); + alpha_pict, xd->back, 0, 0, 0, 0, reg->x1, + reg->y1, width, height); } 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 if (i == 1) { 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, - width, height); + xd->back, 0, 0, 0, 0, reg->x1, reg->y1, width, height); } xcb_render_free_picture(ps->c, tmp_picture[0]); @@ -388,16 +394,26 @@ static void *init(session_t *ps) { if (ps->overlay != XCB_NONE) { xd->target = x_create_picture_with_visual_and_pixmap( ps, ps->vis, ps->overlay, 0, NULL); + xd->target_win = ps->overlay; } else { xcb_render_create_picture_value_list_t pa = { .subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS, }; xd->target = x_create_picture_with_visual_and_pixmap( ps, ps->vis, ps->root, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); + xd->target_win = ps->root; } - xd->target_buffer = x_create_picture_with_visual( - ps, ps->root_width, ps->root_height, ps->vis, 0, NULL); + auto pictfmt = x_get_pictform_for_visual(ps, ps->vis); + 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); 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( 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; } @@ -423,29 +453,41 @@ static void *root_change(void *backend_data, session_t *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; + 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 - 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, - xd->target_buffer, 0, 0, 0, 0, 0, 0, ps->root_width, - ps->root_height); + xd->back, 0, 0, 0, 0, 0, 0, ps->root_width, ps->root_height); } static void present(void *backend_data, session_t *ps) { struct _xrender_data *xd = backend_data; - // compose() sets clip region, so clear it first to make - // sure we update the whole screen. - x_clear_picture_clip_region(ps, xd->target_buffer); + if (ps->o.vsync != VSYNC_NONE && ps->present_exists) { + // Only reset the fence when we are sure we will trigger it again. + // 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. - // but that will require a different backend API - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, xd->target_buffer, None, - xd->target, 0, 0, 0, 0, 0, 0, ps->root_width, - ps->root_height); + // TODO buffer-age-like optimization might be possible here. + // but that will require a different backend API + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, xd->back, None, + xd->target, 0, 0, 0, 0, 0, 0, ps->root_width, + ps->root_height); + } } struct backend_info xrender_backend = { @@ -453,7 +495,7 @@ struct backend_info xrender_backend = { .deinit = deinit, .blur = blur, .present = present, - .prepare = paint_root, + .prepare = prepare, .compose = compose, .root_change = root_change, .render_win = render_win, @@ -462,3 +504,5 @@ struct backend_info xrender_backend = { .is_win_transparent = default_is_win_transparent, .is_frame_transparent = default_is_frame_transparent, }; + +// vim: set noet sw=8 ts=8: diff --git a/src/compton.c b/src/compton.c index 7e0746ac..1dee1efd 100644 --- a/src/compton.c +++ b/src/compton.c @@ -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); 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