Merge pull request #880 from yshui/mask

Fixes #783, replaces #831
This commit is contained in:
Yuxuan Shui
2022-09-16 15:13:52 +01:00
committed by GitHub
10 changed files with 377 additions and 86 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,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(&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 +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(&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 +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(&reg_shadow, &reg_shadow,
&reg_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, &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 +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,
&reg_paint_in_bound, &reg_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(&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 +468,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 +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, &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_bound_no_corner);
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,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(&reg_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) {

View File

@@ -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);

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,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), &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);
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(&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 +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,

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