diff --git a/src/backend/backend.c b/src/backend/backend.c index a1fb282..796e7be 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -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}, ®_paint, ®_visible); } else { ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1}, @@ -203,6 +204,24 @@ 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); + auto reg_bound_no_corner = + win_get_bounding_shape_global_without_corners_by_val(w); + 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); + + 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}, + ®_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 +315,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 +323,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(®_shadow, ®_shadow, ®_paint); - 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)) { @@ -341,9 +358,28 @@ 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(®_shadow, ®_shadow, + ®_bound_no_corner); + 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, ®_shadow, ®_visible); + ps->backend_data, w->shadow_image, shadow_coord, + inverted_mask, window_coord, ®_shadow, ®_visible); + if (inverted_mask) { + ps->backend_data->ops->set_image_property( + ps->backend_data, IMAGE_PROPERTY_INVERTED, + inverted_mask, (bool[]){false}); + } pixman_region32_fini(®_shadow); } @@ -406,7 +442,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, ®_paint_in_bound, ®_visible); } else { // For window image processing, we don't have to limit the process @@ -420,10 +456,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(®_bound_local); - pixman_region32_copy(®_bound_local, ®_bound); - pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); pixman_region32_init(®_visible_local); pixman_region32_intersect(®_visible_local, @@ -436,7 +468,6 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // region, not the clip region. pixman_region32_intersect( ®_visible_local, ®_visible_local, ®_bound_local); - pixman_region32_fini(®_bound_local); } auto new_img = ps->backend_data->ops->clone_image( @@ -446,14 +477,16 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, ®_frame, ®_visible_local, (double[]){w->frame_opacity}); pixman_region32_fini(®_frame); - ps->backend_data->ops->compose(ps->backend_data, new_img, w->g.x, - w->g.y, ®_paint_in_bound, - ®_visible); + ps->backend_data->ops->compose(ps->backend_data, new_img, + window_coord, NULL, window_coord, + ®_paint_in_bound, ®_visible); ps->backend_data->ops->release_image(ps->backend_data, new_img); pixman_region32_fini(®_visible_local); } skip: pixman_region32_fini(®_bound); + pixman_region32_fini(®_bound_local); + pixman_region32_fini(®_bound_no_corner); pixman_region32_fini(®_paint_in_bound); } pixman_region32_fini(®_paint); diff --git a/src/backend/backend.h b/src/backend/backend.h index 31732fb..027a4e2 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -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 diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 7fa53d3..6064791 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -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, diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 9ec3f33..0cd70c2 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -385,9 +385,12 @@ 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) { auto gd = (struct gl_data *)base; auto inner = (struct gl_texture *)img->inner; + auto mask_texture = + mask ? ((struct gl_texture *)mask->inner)->texture : gd->default_mask_texture; if (!img || !inner->texture) { log_error("Missing texture."); return; @@ -438,9 +441,24 @@ 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); } + glUniform1i(win_shader->uniform_mask_tex, 2); + glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x, + (float)mask_offset.y); + if (mask != NULL) { + glUniform1i(win_shader->uniform_mask_inverted, mask->color_inverted); + glUniform1f(win_shader->uniform_mask_corner_radius, + (GLfloat)mask->corner_radius); + } else { + glUniform1i(win_shader->uniform_mask_inverted, 0); + glUniform1f(win_shader->uniform_mask_corner_radius, 0); + } + + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, mask_texture); + // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", // x, y, width, height, dx, dy, ptex->width, ptex->height, z); @@ -500,11 +518,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 +533,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 +574,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 +599,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 +858,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(®_blur_resized); GLuint vao[2]; @@ -943,6 +966,11 @@ static bool gl_win_shader_from_stringv(const char **vshader_strv, bind_uniform(ret, border_width); bind_uniform(ret, time); + bind_uniform(ret, mask_tex); + bind_uniform(ret, mask_offset); + bind_uniform(ret, mask_inverted); + bind_uniform(ret, mask_corner_radius); + gl_check_err(); return true; @@ -1082,9 +1110,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); @@ -1579,6 +1643,11 @@ const char *win_shader_glsl = GLSL(330, uniform sampler2D tex; uniform sampler2D brightness; uniform float max_brightness; + + uniform sampler2D mask_tex; + uniform vec2 mask_offset; + uniform float mask_corner_radius; + uniform bool mask_inverted; // Signed distance field for rectangle center at (0, 0), with size of // half_size * 2 float rectangle_sdf(vec2 point, vec2 half_size) { @@ -1627,7 +1696,21 @@ const char *win_shader_glsl = GLSL(330, vec4 window_shader(); void main() { - gl_FragColor = window_shader(); + vec2 mask_size = textureSize(mask_tex, 0); + vec2 maskcoord = texcoord - mask_offset; + vec4 mask = texture2D(mask_tex, maskcoord / mask_size); + if (mask_corner_radius != 0) { + vec2 inner_size = mask_size - vec2(mask_corner_radius) * 2.0f; + float dist = rectangle_sdf(maskcoord - mask_size / 2.0f, + inner_size / 2.0f) - mask_corner_radius; + if (dist > 0.0f) { + mask.r = (1.0f - clamp(dist, 0.0f, 1.0f)); + } + } + if (mask_inverted) { + mask.rgb = 1.0 - mask.rgb; + } + gl_FragColor = window_shader() * mask.r; } ); @@ -1732,6 +1815,17 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); + gd->default_mask_texture = gl_new_texture(GL_TEXTURE_2D); + if (!gd->default_mask_texture) { + log_error("Failed to generate a default mask texture"); + return false; + } + + glBindTexture(GL_TEXTURE_2D, gd->default_mask_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, + (GLbyte[]){'\xff'}); + glBindTexture(GL_TEXTURE_2D, 0); + // Initialize shaders gd->default_shader = gl_create_window_shader(NULL, win_shader_default); if (!gd->default_shader) { diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 9336d74..ffd2639 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -40,6 +40,11 @@ typedef struct { GLint uniform_corner_radius; GLint uniform_border_width; GLint uniform_time; + + GLint uniform_mask_tex; + GLint uniform_mask_offset; + GLint uniform_mask_corner_radius; + GLint uniform_mask_inverted; } gl_win_shader_t; // Program and uniforms for brightness shader @@ -90,6 +95,8 @@ struct gl_data { GLuint back_texture, back_fbo; GLuint present_prog; + GLuint default_mask_texture; + /// Called when an gl_texture is decoupled from the texture it refers. Returns /// the decoupled user_data void *(*decouple_texture_user_data)(backend_t *base, void *user_data); @@ -117,8 +124,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 +138,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); diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index 8b3bf3d..e0cc368 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -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, diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 9e2236b..f74b08a 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -170,11 +170,60 @@ 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 xcb_render_picture_t process_mask(struct _xrender_data *xd, struct xrender_image *mask, + xcb_render_picture_t alpha_pict, bool *allocated) { + auto inner = (struct _xrender_image_data_inner *)mask->base.inner; + if (!mask->base.color_inverted && mask->base.corner_radius == 0) { + *allocated = false; + return inner->pict; + } + const auto tmpw = to_u16_checked(inner->width); + const auto tmph = to_u16_checked(inner->height); + *allocated = true; + x_clear_picture_clip_region(xd->base.c, inner->pict); + auto ret = x_create_picture_with_visual( + xd->base.c, xd->base.root, inner->width, inner->height, inner->visual, + XCB_RENDER_CP_REPEAT, + (xcb_render_create_picture_value_list_t[]){XCB_RENDER_REPEAT_PAD}); + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, + ret, 0, 0, 0, 0, 0, 0, tmpw, tmph); + // Remember: the mask has a 1-pixel border + if (mask->base.corner_radius != 0) { + if (mask->rounded_rectangle == NULL) { + mask->rounded_rectangle = make_rounded_corner_cache( + xd->base.c, xd->white_pixel, xd->base.root, inner->width - 2, + inner->height - 2, (int)mask->base.corner_radius); + } + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, + mask->rounded_rectangle->p, XCB_NONE, ret, 0, 0, 0, + 0, 1, 1, (uint16_t)(tmpw - 2), (uint16_t)(tmph - 2)); + } + + if (mask->base.color_inverted) { + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_XOR, xd->white_pixel, + XCB_NONE, ret, 0, 0, 0, 0, 0, 0, tmpw, tmph); + } + + if (alpha_pict != XCB_NONE) { + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, ret, alpha_pict, + ret, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width), + to_u16_checked(inner->height)); + } + + return ret; +} + +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) { const struct backend_image *img = &xrimg->base; - auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)]; + bool mask_allocated = false; + auto mask_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)]; + if (mask != NULL) { + mask_pict = process_mask( + xd, mask, img->opacity < 1.0 ? mask_pict : XCB_NONE, &mask_allocated); + } auto inner = (struct _xrender_image_data_inner *)img->inner; region_t reg; @@ -183,6 +232,9 @@ static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, const auto tmph = to_u16_checked(inner->height); const auto tmpew = to_u16_checked(img->ewidth); const auto tmpeh = to_u16_checked(img->eheight); + // Remember: the mask has a 1-pixel border + const auto mask_dst_x = to_i16_checked(dst.x - mask_dst.x + 1); + const auto mask_dst_y = to_i16_checked(dst.y - mask_dst.y + 1); const xcb_render_color_t dim_color = { .red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)}; @@ -207,11 +259,19 @@ 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), ®); + x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x), + to_i16_checked(-dst.y), ®); // 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); + + if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) { + // Clip tmp_pict with a rounded rectangle + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, + xrimg->rounded_rectangle->p, XCB_NONE, + tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); + } + if (img->color_inverted) { if (inner->has_alpha) { auto tmp_pict2 = x_create_picture_with_visual( @@ -247,38 +307,32 @@ static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, tmp_pict, dim_color, 1, &rect); } - if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) { - // Clip tmp_pict with a rounded rectangle - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, - xrimg->rounded_rectangle->p, XCB_NONE, - tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); - } - 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); + mask_pict, result, 0, 0, mask_dst_x, mask_dst_y, + 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); + xcb_render_composite(xd->base.c, op, inner->pict, mask_pict, result, 0, 0, + mask_dst_x, mask_dst_y, 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, }; @@ -288,13 +342,17 @@ static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, } } } + if (mask_allocated) { + xcb_render_free_picture(xd->base.c, mask_pict); + } pixman_region32_fini(®); } -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 +678,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 +961,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, diff --git a/src/utils.h b/src/utils.h index b9a81b4..a35bfa8 100644 --- a/src/utils.h +++ b/src/utils.h @@ -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. diff --git a/src/win.c b/src/win.c index f70be5d..d579c69 100644 --- a/src/win.c +++ b/src/win.c @@ -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); diff --git a/src/win.h b/src/win.h index 1039f52..ad5fe2f 100644 --- a/src/win.h +++ b/src/win.h @@ -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