diff --git a/src/backend/backend.c b/src/backend/backend.c index c073f062..ee8682d0 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -6,9 +6,9 @@ #include "common.h" #include "compiler.h" #include "config.h" +#include "log.h" #include "region.h" #include "win.h" -#include "log.h" backend_init_fn backend_list[NUM_BKEND] = { [BKEND_XRENDER] = backend_xrender_init, @@ -37,16 +37,19 @@ region_t get_damage(session_t *ps) { /// paint all windows void paint_all_new(session_t *ps, win *const t, bool ignore_damage) { - region_t region; + // All painting will be limited to the damage, if _some_ of + // the paints bleed out of the damage region, it will destroy + // part of the image we want to reuse + region_t reg_damage; if (!ignore_damage) { - region = get_damage(ps); + reg_damage = get_damage(ps); } else { - pixman_region32_init(®ion); - pixman_region32_copy(®ion, &ps->screen_reg); + pixman_region32_init(®_damage); + pixman_region32_copy(®_damage, &ps->screen_reg); } - if (!pixman_region32_not_empty(®ion)) { - pixman_region32_fini(®ion); + if (!pixman_region32_not_empty(®_damage)) { + pixman_region32_fini(®_damage); return; } @@ -54,27 +57,26 @@ void paint_all_new(session_t *ps, win *const t, bool ignore_damage) { static struct timespec last_paint = {0}; #endif - region_t reg_tmp; - const region_t *reg_paint; - pixman_region32_init(®_tmp); + // A hint to backend, the region that will be visible on screen + // backend can optimize based on this info + region_t reg_visible; + pixman_region32_init(®_visible); + pixman_region32_copy(®_visible, &ps->screen_reg); if (t) { // Calculate the region upon which the root window (wallpaper) is to be // painted based on the ignore region of the lowest window, if available - pixman_region32_subtract(®_tmp, ®ion, t->reg_ignore); - reg_paint = ®_tmp; - } else { - reg_paint = ®ion; + pixman_region32_subtract(®_visible, ®_visible, t->reg_ignore); } // TODO Bind root pixmap if (ps->backend_data->ops->prepare) { - ps->backend_data->ops->prepare(ps->backend_data, reg_paint); + ps->backend_data->ops->prepare(ps->backend_data, ®_visible); } if (ps->root_image) { ps->backend_data->ops->compose(ps->backend_data, ps->root_image, 0, 0, - reg_paint); + ®_damage, ®_visible); } // Windows are sorted from bottom to top @@ -83,28 +85,25 @@ void paint_all_new(session_t *ps, win *const t, bool ignore_damage) { // // Whether this is beneficial is to be determined XXX for (win *w = t; w; w = w->prev_trans) { - // Calculate the region based on the reg_ignore of the next (higher) - // window and the bounding region - // XXX XXX - pixman_region32_subtract(®_tmp, ®ion, w->reg_ignore); - - if (!pixman_region32_not_empty(®_tmp)) { - continue; - } + pixman_region32_subtract(®_visible, &ps->screen_reg, w->reg_ignore); + // The bounding shape of the window, in global/target coordinates + // reminder: bounding shape contains the WM frame auto reg_bound = win_get_bounding_shape_global_by_val(w); + // Draw shadow on target if (w->shadow) { + // Clip region for the shadow + // reg_shadow \in reg_damage auto reg_shadow = win_extents_by_val(w); - pixman_region32_intersect(®_shadow, ®_shadow, ®_tmp); - + pixman_region32_intersect(®_shadow, ®_shadow, ®_damage); if (!ps->o.wintype_option[w->window_type].full_shadow) { pixman_region32_subtract(®_shadow, ®_shadow, ®_bound); } // Mask out the region we don't want shadow on if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) { - pixman_region32_subtract(®_tmp, ®_tmp, + pixman_region32_subtract(®_shadow, ®_shadow, &ps->shadow_exclude_reg); } @@ -123,17 +122,18 @@ void paint_all_new(session_t *ps, win *const t, bool ignore_damage) { } assert(w->shadow_image); - ps->backend_data->ops->compose(ps->backend_data, w->shadow_image, - w->g.x + w->shadow_dx, - w->g.y + w->shadow_dy, ®_shadow); + ps->backend_data->ops->compose( + ps->backend_data, w->shadow_image, w->g.x + w->shadow_dx, + w->g.y + w->shadow_dy, ®_shadow, ®_visible); pixman_region32_fini(®_shadow); } - pixman_region32_intersect(®_tmp, ®_tmp, ®_bound); - pixman_region32_fini(®_bound); - if (!pixman_region32_not_empty(®_tmp)) { - continue; - } + // The clip region for the current window, in global/target coordinates + // reg_paint \in reg_damage + region_t reg_paint; + pixman_region32_init(®_paint); + pixman_region32_intersect(®_paint, ®_bound, ®_damage); + // Blur window background bool win_transparent = ps->backend_data->ops->is_image_transparent( ps->backend_data, w->win_image); @@ -142,35 +142,61 @@ void paint_all_new(session_t *ps, win *const t, bool ignore_damage) { (win_transparent || (ps->o.blur_background_frame && frame_transparent))) { // Minimize the region we try to blur, if the window // itself is not opaque, only the frame is. - if (win_is_solid(ps, w)) { - region_t reg_blur; - pixman_region32_init(®_blur); - win_get_region_noframe_local(w, ®_blur); + // TODO resize blur region to fix black line artifact + if (!win_is_solid(ps, w)) { + // We need to blur the bounding shape of the window + // (reg_paint = reg_bound \cap reg_damage) + ps->backend_data->ops->blur(ps->backend_data, w->opacity, + ®_paint, ®_visible); + } else if (frame_transparent && ps->o.blur_background_frame) { + // Window itself is solid, we only need to blur the frame region + auto reg_blur = win_get_region_frame_local_by_val(w); pixman_region32_translate(®_blur, w->g.x, w->g.y); - pixman_region32_subtract(®_blur, ®_tmp, ®_blur); + // make sure reg_blur \in reg_damage + pixman_region32_intersect(®_blur, ®_blur, ®_damage); ps->backend_data->ops->blur(ps->backend_data, w->opacity, - ®_blur); + ®_blur, ®_visible); pixman_region32_fini(®_blur); - } else { - ps->backend_data->ops->blur(ps->backend_data, w->opacity, - ®_tmp); } } // Draw window on target if (!w->invert_color && !w->dim && w->frame_opacity == 1 && w->opacity == 1) { - ps->backend_data->ops->compose(ps->backend_data, w->win_image, - w->g.x, w->g.y, ®_tmp); + ps->backend_data->ops->compose(ps->backend_data, w->win_image, w->g.x, + w->g.y, ®_paint, ®_visible); } else { - region_t reg_local; - pixman_region32_init(®_local); - pixman_region32_copy(®_local, ®_tmp); - pixman_region32_translate(®_local, -w->g.x, -w->g.y); + // For window image processing, we don't need to limit the process + // region to damage, since the window image data is independent from + // the target image data, which we want to protect. + + // The bounding shape, in window local coordinates + region_t reg_bound_local; + pixman_region32_init(®_bound_local); + pixman_region32_copy(®_bound_local, ®_bound); + pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); + + // The visible region, in window local coordinates + // Although we don't limit process region to damage, we provide + // that info in reg_visible as a hint. Since window image data + // outside of the damage region won't be painted onto target + region_t reg_visible_local; + pixman_region32_init(®_visible_local); + pixman_region32_intersect(®_visible_local, ®_visible, ®_damage); + pixman_region32_translate(®_visible_local, -w->g.x, -w->g.y); + // Data outside of the bounding shape won't be visible, but it is + // not necessary to limit the image operations to the bounding shape + // yet. So pass that as the visible region, not the clip region. + pixman_region32_intersect(®_visible_local, ®_visible_local, + ®_bound_local); + + // A region covers the entire window + region_t reg_win; + pixman_region32_init_rect(®_win, 0, 0, w->g.width, w->g.height); auto new_img = ps->backend_data->ops->copy( - ps->backend_data, w->win_image, ®_local); + ps->backend_data, w->win_image, ®_visible_local); if (w->invert_color) { - ps->backend_data->ops->image_op(ps->backend_data, - IMAGE_OP_INVERT_COLOR, - new_img, ®_local, NULL); + ps->backend_data->ops->image_op( + ps->backend_data, IMAGE_OP_INVERT_COLOR, new_img, + ®_win, ®_visible_local, NULL); } if (w->dim) { double dim_opacity = ps->o.inactive_dim; @@ -178,30 +204,32 @@ void paint_all_new(session_t *ps, win *const t, bool ignore_damage) { dim_opacity *= w->opacity; } ps->backend_data->ops->image_op( - ps->backend_data, IMAGE_OP_DIM, new_img, ®_local, - (double[]){dim_opacity}); + ps->backend_data, IMAGE_OP_DIM, new_img, ®_win, + ®_visible_local, (double[]){dim_opacity}); } if (w->frame_opacity != 1) { - auto reg_frame = win_get_region_noframe_local_by_val(w); - pixman_region32_subtract(®_frame, ®_local, ®_frame); + auto reg_frame = win_get_region_frame_local_by_val(w); ps->backend_data->ops->image_op( - ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, - ®_frame, (double[]){w->frame_opacity}); + ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, ®_frame, + ®_visible_local, (double[]){w->frame_opacity}); + pixman_region32_fini(®_frame); } if (w->opacity != 1) { ps->backend_data->ops->image_op( - ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, NULL, - (double[]){w->opacity}); + ps->backend_data, IMAGE_OP_APPLY_ALPHA_ALL, new_img, + NULL, ®_visible_local, (double[]){w->opacity}); } ps->backend_data->ops->compose(ps->backend_data, new_img, w->g.x, - w->g.y, ®_tmp); + w->g.y, ®_paint, ®_visible); ps->backend_data->ops->release_image(ps->backend_data, new_img); + pixman_region32_fini(®_win); + pixman_region32_fini(®_visible_local); + pixman_region32_fini(®_bound_local); } + pixman_region32_fini(®_bound); + pixman_region32_fini(®_paint); } - // Free up all temporary regions - pixman_region32_fini(®_tmp); - if (ps->backend_data->ops->present) { // Present the rendered scene // Vsync is done here diff --git a/src/backend/backend.h b/src/backend/backend.h index 90c8d657..8aef82cc 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -23,9 +23,15 @@ typedef struct backend_base { } backend_t; enum image_operations { + // Invert the color of the image IMAGE_OP_INVERT_COLOR, + // Dim the image, argument is the percentage IMAGE_OP_DIM, + // Multiply the alpha channel by the argument IMAGE_OP_APPLY_ALPHA, + // Same as APPLY_ALPHA, but `reg_op` is ignored and the operation applies to the + // full image + IMAGE_OP_APPLY_ALPHA_ALL, }; struct backend_operations { @@ -64,25 +70,29 @@ struct backend_operations { /// Optional? void (*prepare)(backend_t *backend_data, const region_t *reg_paint); - /// Paint the content of an imageonto the (possibly buffered) - /// target picture. Always called after render_win(). Maybe called - /// multiple times between render_win() and finish_render_win(). - /// The origin is the top left of the window, exclude the shadow, - /// (dst_x, dst_y) refers to where the origin should be in the target - /// buffer. + /** + * Paint the content of an image onto the (possibly buffered) + * target picture. + * + * @param backend_data the backend data + * @param image_data the image to paint + * @param dst_x, dst_y the top left corner of the image in the target + * @param reg_paint the clip region, in target coordinates + * @param reg_visibile the visible region, in target coordinates + */ void (*compose)(backend_t *backend_data, void *image_data, int dst_x, int dst_y, - const region_t *reg_paint); + const region_t *reg_paint, const region_t *reg_visible); /// Blur a given region on of the target. - bool (*blur)(backend_t *backend_data, double opacity, const region_t *) - __attribute__((nonnull(1, 3))); + bool (*blur)(backend_t *backend_data, double opacity, const region_t *reg_blur, + const region_t *reg_visible) attr_nonnull(1, 3, 4); /// Present the buffered target picture onto the screen. If target /// is not buffered, this should be NULL. Otherwise, it should always /// be non-NULL. /// /// Optional - void (*present)(backend_t *backend_data) __attribute__((nonnull(1))); + void (*present)(backend_t *backend_data) attr_nonnull(1); /** * Bind a X pixmap to the backend's internal image data structure. @@ -137,17 +147,22 @@ struct backend_operations { * Manipulate an image * * @param backend_data backend data - * @param op the operation to perform - * @param image_data an image data structure returned by the backend - * @param reg_paint the clip region, limit the region of the image to be - * manipulated - * @param args extra arguments, specific to each operation - * @return a new image data structure contains the same image as `image_data` + * @param op the operation to perform + * @param image_data an image data structure returned by the backend + * @param reg_op the clip region, define the part of the image to be + * operated on. + * @param reg_visible define the part of the image that will eventually + * be visible on screen. this is a hint to the backend + * for optimization purposes. + * @param args extra arguments, operation specific + * @return a new image data structure containing the result */ - void (*image_op)(backend_t *backend_data, enum image_operations op, - void *image_data, const region_t *reg_paint, void *args); + bool (*image_op)(backend_t *backend_data, enum image_operations op, void *image_data, + const region_t *reg_op, const region_t *reg_visible, void *args); - void *(*copy)(backend_t *base, const void *image_data, const region_t *reg_copy); + /// Create another instance of the `image_data`. All `image_op` calls on the + /// returned image should not affect the original image + void *(*copy)(backend_t *base, const void *image_data, const region_t *reg_visible); // =========== Hooks ============ /// Let the backend hook into the event handling queue diff --git a/src/backend/xrender.c b/src/backend/xrender.c index 48ce910c..d0b2d068 100644 --- a/src/backend/xrender.c +++ b/src/backend/xrender.c @@ -83,28 +83,42 @@ struct _xrender_image_data { bool owned; }; -static void -compose(backend_t *base, void *img_data, int dst_x, int dst_y, const region_t *reg_paint) { +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; struct _xrender_image_data *img = img_data; int op = (img->has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC); auto alpha_pict = xd->alpha_pict[(int)(img->opacity * 255.0)]; + region_t reg; + pixman_region32_init(®); + pixman_region32_intersect(®, (region_t *)reg_paint, (region_t *)reg_visible); // 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(base->c, img->pict); - x_set_picture_clip_region(base->c, xd->back[xd->curr_back], 0, 0, reg_paint); + x_set_picture_clip_region(base->c, xd->back[xd->curr_back], 0, 0, ®); xcb_render_composite(base->c, op, img->pict, alpha_pict, xd->back[xd->curr_back], 0, 0, 0, 0, dst_x, dst_y, img->width, img->height); + pixman_region32_fini(®); } -static bool blur(backend_t *backend_data, double opacity, const region_t *reg_paint) { +static bool blur(backend_t *backend_data, double opacity, const region_t *reg_blur, + const region_t *reg_visible) { struct _xrender_data *xd = (void *)backend_data; xcb_connection_t *c = xd->base.c; - 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; + 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; + } + + const pixman_box32_t *extent = pixman_region32_extents(®_op); + const int height = extent->y2 - extent->y1; + const int width = extent->x2 - extent->x1; + int src_x = extent->x1, src_y = extent->y1; static const char *filter0 = "Nearest"; // The "null" filter static const char *filter = "convolution"; @@ -116,26 +130,26 @@ static bool blur(backend_t *backend_data, double opacity, const region_t *reg_pa x_create_picture_with_visual(xd->base.c, xd->base.root, width, height, xd->default_visual, 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."); + pixman_region32_fini(®_op); return false; } + region_t clip; + pixman_region32_init(&clip); + pixman_region32_copy(&clip, ®_op); + pixman_region32_translate(&clip, -src_x, -src_y); 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); // The multipass blur implemented here is not correct, but this is what old // compton did anyway. XXX xcb_render_picture_t src_pict = xd->back[xd->curr_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; - x_set_picture_clip_region(c, src_pict, 0, 0, reg_paint); + x_set_picture_clip_region(c, src_pict, 0, 0, ®_op); // For more than 1 pass, we do: // back -(pass 1)-> tmp0 -(pass 2)-> tmp1 ... @@ -162,7 +176,7 @@ static bool blur(backend_t *backend_data, double opacity, const region_t *reg_pa // This is the last pass, and this is also not the first xcb_render_composite(c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, xd->back[xd->curr_back], 0, 0, 0, - 0, reg->x1, reg->y1, width, height); + 0, src_x, src_y, width, height); } // reset filter @@ -178,12 +192,13 @@ static bool blur(backend_t *backend_data, double opacity, const region_t *reg_pa // There is only 1 pass if (i == 1) { xcb_render_composite(c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, - xd->back[xd->curr_back], 0, 0, 0, 0, reg->x1, - reg->y1, width, height); + xd->back[xd->curr_back], 0, 0, 0, 0, extent->x1, extent->y1, + width, height); } xcb_render_free_picture(c, tmp_picture[0]); xcb_render_free_picture(c, tmp_picture[1]); + pixman_region32_fini(®_op); return true; } @@ -321,16 +336,30 @@ static bool is_image_transparent(backend_t *bd, void *image) { return img->has_alpha; } -static void image_op(backend_t *base, enum image_operations op, void *image, - const region_t *reg, void *arg) { +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 _xrender_image_data *img = image; + region_t reg; double dim_opacity; double alpha_multiplier; + if (op == IMAGE_OP_APPLY_ALPHA_ALL) { + alpha_multiplier = *(double *)arg; + img->opacity *= alpha_multiplier; + img->has_alpha = true; + return true; + } + + pixman_region32_init(®); + pixman_region32_intersect(®, (region_t *)reg_op, (region_t *)reg_visible); + if (!pixman_region32_not_empty(®)) { + pixman_region32_fini(®); + return true; + } + switch (op) { case IMAGE_OP_INVERT_COLOR: - assert(reg); - x_set_picture_clip_region(base->c, img->pict, 0, 0, reg); + x_set_picture_clip_region(base->c, img->pict, 0, 0, ®); if (img->has_alpha) { auto tmp_pict = x_create_picture_with_visual(base->c, base->root, img->width, @@ -355,8 +384,7 @@ static void image_op(backend_t *base, enum image_operations op, void *image, } break; case IMAGE_OP_DIM: - assert(reg); - x_set_picture_clip_region(base->c, img->pict, 0, 0, reg); + x_set_picture_clip_region(base->c, img->pict, 0, 0, ®); dim_opacity = *(double *)arg; xcb_render_color_t color = { @@ -378,19 +406,17 @@ static void image_op(backend_t *base, enum image_operations op, void *image, if (alpha_multiplier == 1) { break; } - if (!reg) { - img->opacity *= alpha_multiplier; - img->has_alpha = true; - break; - } auto alpha_pict = xd->alpha_pict[(int)(alpha_multiplier * 255)]; - x_set_picture_clip_region(base->c, img->pict, 0, 0, reg); + x_set_picture_clip_region(base->c, img->pict, 0, 0, ®); xcb_render_composite(base->c, XCB_RENDER_PICT_OP_IN, img->pict, XCB_NONE, alpha_pict, 0, 0, 0, 0, 0, 0, img->width, img->height); img->has_alpha = true; break; + case IMAGE_OP_APPLY_ALPHA_ALL: assert(false); } + pixman_region32_fini(®); + return true; } static void *copy(backend_t *base, const void *image, const region_t *reg) { diff --git a/src/win.c b/src/win.c index 4fdf0924..1bb7f66a 100644 --- a/src/win.c +++ b/src/win.c @@ -137,6 +137,29 @@ void win_get_region_noframe_local(const win *w, region_t *res) { gen_by_val(win_get_region_noframe_local) +void win_get_region_frame_local(const win *w, region_t *res) { + const margin_t extents = win_calc_frame_extents(w); + pixman_region32_fini(res); + pixman_region32_init_rects(res, (rect_t[]){ + // top + {.x1 = 0, .y1 = 0, .x2 = w->g.width, .y2 = extents.top}, + // bottom + {.x1 = 0, .y1 = w->g.height - extents.bottom, .x2 = w->g.width, .y2 = w->g.height}, + //left + {.x1 = 0, .y1 = 0, .x2 = extents.left, .y2 = w->g.height}, + // right + {.x1 = w->g.width - extents.right, .y1 = 0, .x2 = w->g.width, .y2 = w->g.height}, + }, 4); + + // limit the frame region to inside the window + region_t reg_win; + pixman_region32_init_rect(®_win, 0, 0, w->g.width, w->g.height); + pixman_region32_intersect(res, ®_win, res); + pixman_region32_fini(®_win); +} + +gen_by_val(win_get_region_frame_local) + /** * Add a window to damaged area. * @@ -1235,7 +1258,10 @@ void win_update_bounding_shape(session_t *ps, win *w) { free(rects); // Add border width because we are using a different origin. - // X thinks the top left of the inner window is the origin, + // X thinks the top left of the inner window is the origin + // (for the bounding shape, althought xcb_get_geometry thinks + // the outer top left (outer means outside of the window + // border) is the origin), // We think the top left of the border is the origin pixman_region32_translate(&br, w->g.border_width, w->g.border_width); diff --git a/src/win.h b/src/win.h index 1e855b5d..88f9b713 100644 --- a/src/win.h +++ b/src/win.h @@ -111,7 +111,8 @@ typedef enum { * X goes from left to right, Y goes downwards. * * Global: the origin is the top left corner of the Xorg screen. - * Local: the origin is the top left corner of the window, including border. + * Local: the origin is the top left corner of the window, border is + * considered part of the window. */ /// Structure representing a top-level window compton manages. @@ -354,6 +355,11 @@ void add_damage_from_win(session_t *ps, win *w); */ void win_get_region_noframe_local(const win *w, region_t *); region_t win_get_region_noframe_local_by_val(const win *w); + +/// Get the region for the frame of the window +void win_get_region_frame_local(const win *w, region_t *res); +/// Get the region for the frame of the window, by value +region_t win_get_region_frame_local_by_val(const win *w); /** * Retrieve frame extents from a window. */