backend: add the make_mask interface

This is used to create image masks that can be used to mask out
`compose` regions. For example, this can be used to mask out window body
so shadow won't be painted on them.

This could be more efficient than using rectangular regions for masking,
when there are a large number of rectangles; or more flexible, in the
case of window with rounded corners.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2022-08-24 07:35:49 +01:00
parent e0758eb5e5
commit 9b419504e8
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
10 changed files with 252 additions and 76 deletions

View File

@ -182,7 +182,8 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
}
if (ps->root_image) {
ps->backend_data->ops->compose(ps->backend_data, ps->root_image, 0, 0,
ps->backend_data->ops->compose(ps->backend_data, ps->root_image,
(coord_t){0}, NULL, (coord_t){0},
&reg_paint, &reg_visible);
} else {
ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
@ -203,6 +204,22 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
// 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);
region_t reg_bound_local;
pixman_region32_init(&reg_bound_local);
pixman_region32_copy(&reg_bound_local, &reg_bound);
pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
if (!w->mask_image) {
// TODO(yshui) only allocate a mask if the window is shaped or has
// rounded corners.
w->mask_image = ps->backend_data->ops->make_mask(
ps->backend_data,
(geometry_t){.width = w->g.width, .height = w->g.height},
&reg_bound_local);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS, w->mask_image,
(double[]){w->corner_radius});
}
// The clip region for the current window, in global/target coordinates
// reg_paint_in_bound \in reg_paint
@ -296,6 +313,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
}
}
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
// Draw shadow on target
if (w->shadow) {
assert(!(w->flags & WIN_FLAGS_SHADOW_NONE));
@ -303,9 +321,6 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
// reg_shadow \in reg_paint
auto reg_shadow = win_extents_by_val(w);
pixman_region32_intersect(&reg_shadow, &reg_shadow, &reg_paint);
if (!ps->o.wintype_option[w->window_type].full_shadow) {
pixman_region32_subtract(&reg_shadow, &reg_shadow, &reg_bound);
}
// Mask out the region we don't want shadow on
if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) {
@ -341,9 +356,27 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, w->shadow_image,
&w->opacity);
coord_t shadow_coord = {.x = w->g.x + w->shadow_dx,
.y = w->g.y + w->shadow_dy};
auto inverted_mask = NULL;
if (!ps->o.wintype_option[w->window_type].full_shadow) {
pixman_region32_subtract(&reg_shadow, &reg_shadow, &reg_bound);
if (w->mask_image) {
inverted_mask = w->mask_image;
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_INVERTED,
inverted_mask, (bool[]){true});
}
}
ps->backend_data->ops->compose(
ps->backend_data, w->shadow_image, w->g.x + w->shadow_dx,
w->g.y + w->shadow_dy, &reg_shadow, &reg_visible);
ps->backend_data, w->shadow_image, shadow_coord,
inverted_mask, window_coord, &reg_shadow, &reg_visible);
if (inverted_mask) {
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_INVERTED,
inverted_mask, (bool[]){false});
}
pixman_region32_fini(&reg_shadow);
}
@ -406,7 +439,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
// Draw window on target
if (w->frame_opacity == 1) {
ps->backend_data->ops->compose(ps->backend_data, w->win_image,
w->g.x, w->g.y,
window_coord, NULL, window_coord,
&reg_paint_in_bound, &reg_visible);
} else {
// For window image processing, we don't have to limit the process
@ -420,10 +453,6 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
region_t reg_visible_local;
{
// The bounding shape, in window local coordinates
region_t reg_bound_local;
pixman_region32_init(&reg_bound_local);
pixman_region32_copy(&reg_bound_local, &reg_bound);
pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
pixman_region32_init(&reg_visible_local);
pixman_region32_intersect(&reg_visible_local,
@ -436,7 +465,6 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
// region, not the clip region.
pixman_region32_intersect(
&reg_visible_local, &reg_visible_local, &reg_bound_local);
pixman_region32_fini(&reg_bound_local);
}
auto new_img = ps->backend_data->ops->clone_image(
@ -446,14 +474,15 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, &reg_frame,
&reg_visible_local, (double[]){w->frame_opacity});
pixman_region32_fini(&reg_frame);
ps->backend_data->ops->compose(ps->backend_data, new_img, w->g.x,
w->g.y, &reg_paint_in_bound,
&reg_visible);
ps->backend_data->ops->compose(ps->backend_data, new_img,
window_coord, NULL, window_coord,
&reg_paint_in_bound, &reg_visible);
ps->backend_data->ops->release_image(ps->backend_data, new_img);
pixman_region32_fini(&reg_visible_local);
}
skip:
pixman_region32_fini(&reg_bound);
pixman_region32_fini(&reg_bound_local);
pixman_region32_fini(&reg_paint_in_bound);
}
pixman_region32_fini(&reg_paint);

View File

@ -30,6 +30,15 @@ typedef struct backend_base {
// ...
} backend_t;
typedef struct geometry {
int width;
int height;
} geometry_t;
typedef struct coord {
int x, y;
} coord_t;
typedef void (*backend_ready_callback_t)(void *);
// This mimics OpenGL's ARB_robustness extension, which enables detection of GPU context
@ -44,8 +53,8 @@ enum device_status {
// When image properties are actually applied to the image, they are applied in a
// particular order:
//
// Color inversion -> Dimming -> Opacity multiply -> Limit maximum brightness
// (Corner radius could be applied in any order)
// Corner radius -> Color inversion -> Dimming -> Opacity multiply -> Limit maximum
// brightness
enum image_properties {
// Whether the color of the image is inverted
// 1 boolean, default: false
@ -158,16 +167,19 @@ struct backend_operations {
void (*prepare)(backend_t *backend_data, const region_t *reg_damage);
/**
* Paint the content of an image onto the rendering buffer
* Paint the content of an image onto the rendering buffer.
*
* @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 mask the mask image, the top left of the mask is aligned with
* the top left of the image
* @param reg_paint the clip region, in target coordinates
* @param reg_visible 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_visible);
void (*compose)(backend_t *backend_data, void *image_data, coord_t image_dst,
void *mask, coord_t mask_dst, const region_t *reg_paint,
const region_t *reg_visible);
/// Fill rectangle of the rendering buffer, mostly for debug purposes, optional.
void (*fill)(backend_t *backend_data, struct color, const region_t *clip);
@ -203,6 +215,18 @@ struct backend_operations {
void *(*render_shadow)(backend_t *backend_data, int width, int height,
const conv *kernel, double r, double g, double b, double a);
/// Create a mask image from region `reg`. This region can be used to create
/// shadow, or used as a mask for composing. When used as a mask, it should mask
/// out everything that is not inside the region used to create it.
///
/// Image properties might be set on masks too, at least the INVERTED and
/// CORNER_RADIUS properties must be supported. Inversion should invert the inside
/// and outside of the mask. Corner radius should exclude the corners from the
/// mask. Corner radius should be applied before the inversion.
///
/// Required.
void *(*make_mask)(backend_t *backend_data, geometry_t size, const region_t *reg);
// ============ Resource management ===========
/// Free resources associated with an image data structure

View File

@ -23,6 +23,8 @@ struct dummy_image {
struct dummy_data {
struct backend_base base;
struct dummy_image *images;
struct backend_image mask;
};
struct backend_base *dummy_init(struct session *ps attr_unused) {
@ -47,6 +49,9 @@ void dummy_deinit(struct backend_base *data) {
static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) {
auto dummy = (struct dummy_data *)base;
if (img == (struct dummy_image *)&dummy->mask) {
return;
}
struct dummy_image *tmp = NULL;
HASH_FIND_INT(dummy->images, &img->pixmap, tmp);
if (!tmp) {
@ -56,10 +61,13 @@ static void dummy_check_image(struct backend_base *base, const struct dummy_imag
assert(*tmp->refcount > 0);
}
void dummy_compose(struct backend_base *base, void *image, int dst_x attr_unused,
int dst_y attr_unused, const region_t *reg_paint attr_unused,
void dummy_compose(struct backend_base *base, void *image, coord_t dst attr_unused,
void *mask attr_unused, coord_t mask_dst attr_unused,
const region_t *reg_paint attr_unused,
const region_t *reg_visible attr_unused) {
auto dummy attr_unused = (struct dummy_data *)base;
dummy_check_image(base, image);
assert(mask == NULL || mask == &dummy->mask);
}
void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused,
@ -94,6 +102,9 @@ void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
void dummy_release_image(backend_t *base, void *image) {
auto dummy = (struct dummy_data *)base;
if (image == &dummy->mask) {
return;
}
auto img = (struct dummy_image *)image;
assert(*img->refcount > 0);
(*img->refcount)--;
@ -121,6 +132,11 @@ bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unu
return true;
}
void *dummy_make_mask(struct backend_base *base, geometry_t size attr_unused,
const region_t *reg attr_unused) {
return &(((struct dummy_data *)base)->mask);
}
bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused,
void *image, void *arg attr_unused) {
dummy_check_image(base, image);
@ -159,6 +175,7 @@ struct backend_operations dummy_ops = {
.blur = dummy_blur,
.bind_pixmap = dummy_bind_pixmap,
.render_shadow = default_backend_render_shadow,
.make_mask = dummy_make_mask,
.release_image = dummy_release_image,
.is_image_transparent = dummy_is_image_transparent,
.buffer_age = dummy_buffer_age,

View File

@ -385,7 +385,10 @@ static GLuint gl_average_texture_color(backend_t *base, struct backend_image *im
* @param reg_visible ignored
*/
static void _gl_compose(backend_t *base, struct backend_image *img, GLuint target,
GLint *coord, GLuint *indices, int nrects) {
struct backend_image *mask, coord_t mask_offset, GLint *coord,
GLuint *indices, int nrects) {
(void)mask;
(void)mask_offset;
auto gd = (struct gl_data *)base;
auto inner = (struct gl_texture *)img->inner;
if (!img || !inner->texture) {
@ -438,7 +441,7 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
glUniform1f(win_shader->uniform_time,
(float)ts.tv_sec * 1000.0f + (float)ts.tv_nsec / 1.0e6f);
(float)ts.tv_sec * 1000.0F + (float)ts.tv_nsec / 1.0e6F);
}
// log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n",
@ -500,11 +503,11 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
/// @param[in] y_inverted whether the texture is y inverted
/// @param[out] coord, indices output
static void
x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int texture_height,
x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, int texture_height,
int root_height, bool y_inverted, GLint *coord, GLuint *indices) {
dst_y = root_height - dst_y;
image_dst.y = root_height - image_dst.y;
if (y_inverted) {
dst_y -= texture_height;
image_dst.y -= texture_height;
}
for (int i = 0; i < nrects; i++) {
@ -515,7 +518,8 @@ x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int text
// Calculate texture coordinates
// (texture_x1, texture_y1), texture coord for the _bottom left_ corner
GLint texture_x1 = crect.x1 - dst_x, texture_y1 = crect.y2 - dst_y,
GLint texture_x1 = crect.x1 - image_dst.x,
texture_y1 = crect.y2 - image_dst.y,
texture_x2 = texture_x1 + (crect.x2 - crect.x1),
texture_y2 = texture_y1 + (crect.y1 - crect.y2);
@ -555,8 +559,9 @@ x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int text
}
// TODO(yshui) make use of reg_visible
void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y,
const region_t *reg_tgt, const region_t *reg_visible attr_unused) {
void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask,
coord_t mask_dst, const region_t *reg_tgt,
const region_t *reg_visible attr_unused) {
auto gd = (struct gl_data *)base;
struct backend_image *img = image_data;
auto inner = (struct gl_texture *)img->inner;
@ -579,9 +584,10 @@ void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y,
auto coord = ccalloc(nrects * 16, GLint);
auto indices = ccalloc(nrects * 6, GLuint);
x_rect_to_coords(nrects, rects, dst_x, dst_y, inner->height, gd->height,
coord_t mask_offset = {.x = mask_dst.x - image_dst.x, .y = mask_dst.y - image_dst.y};
x_rect_to_coords(nrects, rects, image_dst, inner->height, gd->height,
inner->y_inverted, coord, indices);
_gl_compose(base, img, gd->back_fbo, coord, indices, nrects);
_gl_compose(base, img, gd->back_fbo, mask, mask_offset, coord, indices, nrects);
free(indices);
free(coord);
@ -837,14 +843,16 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu
auto coord = ccalloc(nrects * 16, GLint);
auto indices = ccalloc(nrects * 6, GLuint);
x_rect_to_coords(nrects, rects, extent_resized->x1, extent_resized->y2,
x_rect_to_coords(nrects, rects,
(coord_t){.x = extent_resized->x1, .y = extent_resized->y2},
bctx->fb_height, gd->height, false, coord, indices);
auto coord_resized = ccalloc(nrects_resized * 16, GLint);
auto indices_resized = ccalloc(nrects_resized * 6, GLuint);
x_rect_to_coords(nrects_resized, rects_resized, extent_resized->x1,
extent_resized->y2, bctx->fb_height, bctx->fb_height, false,
coord_resized, indices_resized);
x_rect_to_coords(nrects_resized, rects_resized,
(coord_t){.x = extent_resized->x1, .y = extent_resized->y2},
bctx->fb_height, bctx->fb_height, false, coord_resized,
indices_resized);
pixman_region32_fini(&reg_blur_resized);
GLuint vao[2];
@ -1082,9 +1090,45 @@ void gl_fill(backend_t *base, struct color c, const region_t *clip) {
return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true);
}
void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg) {
auto tex = ccalloc(1, struct gl_texture);
auto img = default_new_backend_image(size.width, size.height);
tex->width = size.width;
tex->height = size.height;
tex->texture = gl_new_texture(GL_TEXTURE_2D);
tex->has_alpha = false;
tex->y_inverted = true;
img->inner = (struct backend_image_inner_base *)tex;
img->inner->refcount = 1;
glBindTexture(GL_TEXTURE_2D, tex->texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size.width, size.height, 0, GL_RED,
GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
GLuint fbo;
glBlendFunc(GL_ONE, GL_ZERO);
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
tex->texture, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
_gl_fill(base, (struct color){1, 1, 1, 1}, reg, fbo, size.height, false);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
return img;
}
static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
auto gd = (struct gl_data *)base;
gd->release_user_data(base, inner);
if (inner->user_data) {
gd->release_user_data(base, inner);
}
assert(inner->user_data == NULL);
glDeleteTextures(1, &inner->texture);

View File

@ -117,8 +117,8 @@ bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
/**
* @brief Render a region with texture data.
*/
void gl_compose(backend_t *, void *image_data, int dst_x, int dst_y,
const region_t *reg_tgt, const region_t *reg_visible);
void gl_compose(backend_t *, void *image_data, coord_t image_dst, void *mask,
coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible);
void gl_resize(struct gl_data *, int width, int height);
@ -131,6 +131,7 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
const region_t *reg_op, const region_t *reg_visible, void *arg);
void gl_release_image(backend_t *base, void *image_data);
void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg);
void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visible);

View File

@ -393,11 +393,10 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
}
log_trace("Binding pixmap %#010x", pixmap);
auto wd = ccalloc(1, struct backend_image);
wd->max_brightness = 1;
auto wd = default_new_backend_image(r->width, r->height);
auto inner = ccalloc(1, struct gl_texture);
inner->width = wd->ewidth = r->width;
inner->height = wd->eheight = r->height;
inner->width = r->width;
inner->height = r->height;
wd->inner = (struct backend_image_inner_base *)inner;
free(r);
@ -445,9 +444,6 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
inner->user_data = glxpixmap;
inner->texture = gl_new_texture(GL_TEXTURE_2D);
inner->has_alpha = fmt.alpha_size != 0;
wd->opacity = 1;
wd->color_inverted = false;
wd->dim = 0;
wd->inner->refcount = 1;
glBindTexture(GL_TEXTURE_2D, inner->texture);
glXBindTexImageEXT(gd->display, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
@ -541,6 +537,7 @@ struct backend_operations glx_ops = {
.present = glx_present,
.buffer_age = glx_buffer_age,
.render_shadow = default_backend_render_shadow,
.make_mask = gl_make_mask,
.fill = gl_fill,
.create_blur_context = gl_create_blur_context,
.destroy_blur_context = gl_destroy_blur_context,

View File

@ -170,9 +170,12 @@ make_rounded_corner_cache(xcb_connection_t *c, xcb_render_picture_t src,
return ret;
}
static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, int dst_x,
int dst_y, const region_t *reg_paint,
const region_t *reg_visible, xcb_render_picture_t result) {
static void
compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst,
struct xrender_image *mask, coord_t mask_dst, const region_t *reg_paint,
const region_t *reg_visible, xcb_render_picture_t result) {
(void)mask;
(void)mask_dst;
const struct backend_image *img = &xrimg->base;
auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)];
auto inner = (struct _xrender_image_data_inner *)img->inner;
@ -207,8 +210,8 @@ static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg,
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), &reg);
x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x),
to_i16_checked(-dst.y), &reg);
// 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);
@ -255,30 +258,30 @@ static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg,
}
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);
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);
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);
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),
.x = to_i16_checked(dst.x),
.y = to_i16_checked(dst.y),
.width = tmpew,
.height = tmpeh,
};
@ -291,10 +294,11 @@ static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg,
pixman_region32_fini(&reg);
}
static void compose(backend_t *base, void *img_data, int dst_x, int dst_y,
static void compose(backend_t *base, void *img_data, coord_t dst, void *mask, coord_t mask_dst,
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]);
return compose_impl(xd, img_data, dst, mask, mask_dst, reg_paint, reg_visible,
xd->back[2]);
}
static void fill(backend_t *base, struct color c, const region_t *clip) {
@ -620,6 +624,52 @@ new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) {
return new_inner;
}
static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) {
struct _xrender_data *xd = (void *)base;
// Give the mask a 1 pixel wide border to emulate the clamp to border behavior of
// OpenGL textures.
auto w16 = to_u16_checked(size.width + 2);
auto h16 = to_u16_checked(size.height + 2);
auto inner =
new_inner(base, size.width + 2, size.height + 2,
x_get_visual_for_standard(base->c, XCB_PICT_STANDARD_ARGB_32), 32);
xcb_render_change_picture(base->c, inner->pict, XCB_RENDER_CP_REPEAT,
(uint32_t[]){XCB_RENDER_REPEAT_PAD});
const rect_t *extent = pixman_region32_extents((region_t *)reg);
x_set_picture_clip_region(base->c, xd->back[2], 1, 1, reg);
xcb_render_fill_rectangles(
base->c, XCB_RENDER_PICT_OP_SRC, inner->pict,
(xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0xffff}, 1,
(xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1 + 1),
.y = to_i16_checked(extent->y1 + 1),
.width = to_u16_checked(extent->x2 - extent->x1),
.height = to_u16_checked(extent->y2 - extent->y1)}});
x_clear_picture_clip_region(xd->base.c, inner->pict);
// Paint the border transparent
xcb_render_fill_rectangles(
base->c, XCB_RENDER_PICT_OP_SRC, inner->pict,
(xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0}, 4,
(xcb_rectangle_t[]){{.x = 0, .y = 0, .width = w16, .height = 1},
{.x = 0, .y = 0, .width = 1, .height = h16},
{.x = 0, .y = (short)(h16 - 1), .width = w16, .height = 1},
{.x = (short)(w16 - 1), .y = 0, .width = 1, .height = h16}});
inner->refcount = 1;
auto img = ccalloc(1, struct xrender_image);
img->base.eheight = size.height + 2;
img->base.ewidth = size.width + 2;
img->base.border_width = 0;
img->base.color_inverted = false;
img->base.corner_radius = 0;
img->base.max_brightness = 1;
img->base.opacity = 1;
img->base.dim = 0;
img->base.inner = (struct backend_image_inner_base *)inner;
img->rounded_rectangle = NULL;
return img;
}
static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) {
if (img->inner->refcount == 1) {
return true;
@ -857,6 +907,7 @@ struct backend_operations xrender_ops = {
.bind_pixmap = bind_pixmap,
.release_image = release_image,
.render_shadow = default_backend_render_shadow,
.make_mask = make_mask,
//.prepare_win = prepare_win,
//.release_win = release_win,
.is_image_transparent = default_is_image_transparent,

View File

@ -82,39 +82,39 @@ safe_isnan(double a) {
#define to_int_checked(val) \
({ \
int64_t tmp = (val); \
ASSERT_IN_RANGE(tmp, INT_MIN, INT_MAX); \
(int)tmp; \
int64_t __to_tmp = (val); \
ASSERT_IN_RANGE(__to_tmp, INT_MIN, INT_MAX); \
(int)__to_tmp; \
})
#define to_char_checked(val) \
({ \
int64_t tmp = (val); \
ASSERT_IN_RANGE(tmp, CHAR_MIN, CHAR_MAX); \
(char)tmp; \
int64_t __to_tmp = (val); \
ASSERT_IN_RANGE(__to_tmp, CHAR_MIN, CHAR_MAX); \
(char)__to_tmp; \
})
#define to_u16_checked(val) \
({ \
auto tmp = (val); \
ASSERT_IN_RANGE(tmp, 0, UINT16_MAX); \
(uint16_t) tmp; \
auto __to_tmp = (val); \
ASSERT_IN_RANGE(__to_tmp, 0, UINT16_MAX); \
(uint16_t) __to_tmp; \
})
#define to_i16_checked(val) \
({ \
int64_t tmp = (val); \
ASSERT_IN_RANGE(tmp, INT16_MIN, INT16_MAX); \
(int16_t) tmp; \
int64_t __to_tmp = (val); \
ASSERT_IN_RANGE(__to_tmp, INT16_MIN, INT16_MAX); \
(int16_t) __to_tmp; \
})
#define to_u32_checked(val) \
({ \
auto tmp = (val); \
auto __to_tmp = (val); \
int64_t max attr_unused = UINT32_MAX; /* silence clang tautological \
comparison warning*/ \
ASSERT_IN_RANGE(tmp, 0, max); \
(uint32_t) tmp; \
ASSERT_IN_RANGE(__to_tmp, 0, max); \
(uint32_t) __to_tmp; \
})
/**
* Normalize an int value to a specific range.

View File

@ -314,6 +314,13 @@ static inline void win_release_shadow(backend_t *base, struct managed_win *w) {
}
}
static inline void win_release_mask(backend_t *base, struct managed_win *w) {
if (w->mask_image) {
base->ops->release_image(base, w->mask_image);
w->mask_image = NULL;
}
}
static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w) {
assert(!w->win_image);
auto pixmap = x_new_id(b->c);
@ -373,6 +380,8 @@ void win_release_images(struct backend_base *backend, struct managed_win *w) {
assert(!win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE));
win_release_shadow(backend, w);
}
win_release_mask(backend, w);
}
/// Returns true if the `prop` property is stale, as well as clears the stale flag.
@ -1205,6 +1214,7 @@ void win_on_win_size_change(session_t *ps, struct managed_win *w) {
// Invalidate the shadow we built
win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
win_release_mask(ps->backend_data, w);
ps->pending_updates = true;
free_paint(ps, &w->shadow_paint);
}
@ -1496,6 +1506,7 @@ struct win *fill_win(session_t *ps, struct win *w) {
// is mapped
.win_image = NULL,
.shadow_image = NULL,
.mask_image = NULL,
.prev_trans = NULL,
.shadow = false,
.clip_shadow_above = false,
@ -1927,6 +1938,7 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
// Window shape changed, we should free old wpaint and shadow pict
// log_trace("free out dated pict");
win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
win_release_mask(ps->backend_data, w);
ps->pending_updates = true;
free_paint(ps, &w->paint);

View File

@ -104,6 +104,7 @@ struct managed_win {
/// `state` is not UNMAPPED
void *win_image;
void *shadow_image;
void *mask_image;
/// Pointer to the next higher window to paint.
struct managed_win *prev_trans;
/// Number of windows above this window