Merge pull request #882 from yshui/shadow

This commit is contained in:
Yuxuan Shui 2022-09-16 15:15:15 +01:00 committed by GitHub
commit f2970bc697
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1619 additions and 1267 deletions

View File

@ -206,21 +206,9 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
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});
if (!w->mask_image && (w->bounding_shaped || w->corner_radius != 0)) {
win_bind_mask(ps->backend_data, w);
}
// The clip region for the current window, in global/target coordinates
@ -455,8 +443,12 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
// reg_visible as a hint. Since window image data outside of the
// damage region won't be painted onto target
region_t reg_visible_local;
region_t reg_bound_local;
{
// The bounding shape, in window local coordinates
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,
@ -483,10 +475,10 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
&reg_paint_in_bound, &reg_visible);
ps->backend_data->ops->release_image(ps->backend_data, new_img);
pixman_region32_fini(&reg_visible_local);
pixman_region32_fini(&reg_bound_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);
}

View File

@ -16,6 +16,8 @@
typedef struct session session_t;
struct managed_win;
struct backend_shadow_context;
struct ev_loop;
struct backend_operations;
@ -213,10 +215,31 @@ struct backend_operations {
void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap,
struct xvisual_info fmt, bool owned);
/// Create a shadow image based on the parameters
/// Create a shadow context for rendering shadows with radius `radius`.
/// Default implementation: default_backend_create_shadow_context
struct backend_shadow_context *(*create_shadow_context)(backend_t *backend_data,
double radius);
/// Destroy a shadow context
/// Default implementation: default_backend_destroy_shadow_context
void (*destroy_shadow_context)(backend_t *backend_data,
struct backend_shadow_context *ctx);
/// Create a shadow image based on the parameters. Resulting image should have a
/// size of `width + radisu * 2` x `height + radius * 2`. Radius is set when the
/// shadow context is created.
/// Default implementation: default_backend_render_shadow
///
/// Required.
void *(*render_shadow)(backend_t *backend_data, int width, int height,
const conv *kernel, double r, double g, double b, double a);
struct backend_shadow_context *ctx, struct color color);
/// Create a shadow by blurring a mask. `size` is the size of the blur. The
/// backend can use whichever blur method is the fastest. The shadow produced
/// shoule be consistent with `render_shadow`.
///
/// Optional.
void *(*shadow_from_mask)(backend_t *backend_data, void *mask,
struct backend_shadow_context *ctx, struct color color);
/// 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

View File

@ -291,16 +291,16 @@ shadow_picture_err:
return false;
}
void *
default_backend_render_shadow(backend_t *backend_data, int width, int height,
const conv *kernel, double r, double g, double b, double a) {
xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root,
true, 1, r, g, b),
void *default_backend_render_shadow(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color) {
const conv *kernel = (void *)sctx;
xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root, true,
1, color.red, color.green, color.blue),
shadow = XCB_NONE;
xcb_render_picture_t pict = XCB_NONE;
if (!build_shadow(backend_data->c, backend_data->root, a, width, height, kernel,
shadow_pixel, &shadow, &pict)) {
if (!build_shadow(backend_data->c, backend_data->root, color.alpha, width, height,
kernel, shadow_pixel, &shadow, &pict)) {
return NULL;
}
@ -311,6 +311,34 @@ default_backend_render_shadow(backend_t *backend_data, int width, int height,
return ret;
}
/// Implement render_shadow with shadow_from_mask
void *
backend_render_shadow_from_mask(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color) {
region_t reg;
pixman_region32_init_rect(&reg, 0, 0, (unsigned int)width, (unsigned int)height);
void *mask = backend_data->ops->make_mask(
backend_data, (geometry_t){.width = width, .height = height}, &reg);
pixman_region32_fini(&reg);
void *shadow = backend_data->ops->shadow_from_mask(backend_data, mask, sctx, color);
backend_data->ops->release_image(backend_data, mask);
return shadow;
}
struct backend_shadow_context *
default_create_shadow_context(backend_t *backend_data attr_unused, double radius) {
auto ret =
(struct backend_shadow_context *)gaussian_kernel_autodetect_deviation(radius);
sum_kernel_preprocess((conv *)ret);
return ret;
}
void default_destroy_shadow_context(backend_t *backend_data attr_unused,
struct backend_shadow_context *sctx) {
free_conv((conv *)sctx);
}
static struct conv **generate_box_blur_kernel(struct box_blur_args *args, int *kernel_count) {
int r = args->size * 2 + 1;
assert(r > 0);

View File

@ -62,9 +62,18 @@ bool default_is_win_transparent(void *, win *, void *);
/// caveat as `default_is_win_transparent` applies.
bool default_is_frame_transparent(void *, win *, void *);
void *default_backend_render_shadow(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color);
/// Implement `render_shadow` with `shadow_from_mask`.
void *
default_backend_render_shadow(backend_t *backend_data, int width, int height,
const conv *kernel, double r, double g, double b, double a);
backend_render_shadow_from_mask(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color);
struct backend_shadow_context *
default_create_shadow_context(backend_t *backend_data, double radius);
void default_destroy_shadow_context(backend_t *backend_data,
struct backend_shadow_context *sctx);
void init_backend_base(struct backend_base *base, session_t *ps);

View File

@ -175,6 +175,8 @@ struct backend_operations dummy_ops = {
.fill = dummy_fill,
.blur = dummy_blur,
.bind_pixmap = dummy_bind_pixmap,
.create_shadow_context = default_create_shadow_context,
.destroy_shadow_context = default_destroy_shadow_context,
.render_shadow = default_backend_render_shadow,
.make_mask = dummy_make_mask,
.release_image = dummy_release_image,

900
src/backend/gl/blur.c Normal file
View File

@ -0,0 +1,900 @@
#include <locale.h>
#include <stdbool.h>
#include <backend/backend.h>
#include <backend/backend_common.h>
#include "gl_common.h"
struct gl_blur_context {
enum blur_method method;
gl_blur_shader_t *blur_shader;
/// Temporary textures used for blurring
GLuint *blur_textures;
int blur_texture_count;
/// Temporary fbos used for blurring
GLuint *blur_fbos;
int blur_fbo_count;
/// Cached dimensions of each blur_texture. They are the same size as the target,
/// so they are always big enough without resizing.
/// Turns out calling glTexImage to resize is expensive, so we avoid that.
struct texture_size {
int width;
int height;
} * texture_sizes;
/// Cached dimensions of the offscreen framebuffer. It's the same size as the
/// target but is expanded in either direction by resize_width / resize_height.
int fb_width, fb_height;
/// How much do we need to resize the damaged region for blurring.
int resize_width, resize_height;
int npasses;
};
/**
* Blur contents in a particular region.
*/
bool gl_kernel_blur(double opacity, struct gl_blur_context *bctx, const rect_t *extent,
struct backend_image *mask, coord_t mask_dst, const GLuint vao[2],
const int vao_nelems[2], GLuint source_texture,
geometry_t source_size, GLuint target_fbo, GLuint default_mask) {
int dst_y_fb_coord = bctx->fb_height - extent->y2;
int curr = 0;
for (int i = 0; i < bctx->npasses; ++i) {
const gl_blur_shader_t *p = &bctx->blur_shader[i];
assert(p->prog);
assert(bctx->blur_textures[curr]);
// The origin to use when sampling from the source texture
GLint texorig_x = extent->x1, texorig_y = dst_y_fb_coord;
GLint tex_width, tex_height;
GLuint src_texture;
if (i == 0) {
src_texture = source_texture;
tex_width = source_size.width;
tex_height = source_size.height;
} else {
src_texture = bctx->blur_textures[curr];
auto src_size = bctx->texture_sizes[curr];
tex_width = src_size.width;
tex_height = src_size.height;
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, src_texture);
glUseProgram(p->prog);
glUniform2f(p->uniform_pixel_norm, 1.0F / (GLfloat)tex_width,
1.0F / (GLfloat)tex_height);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, default_mask);
glUniform1i(p->uniform_mask_tex, 1);
glUniform2f(p->uniform_mask_offset, 0.0F, 0.0F);
glUniform1i(p->uniform_mask_inverted, 0);
glUniform1f(p->uniform_mask_corner_radius, 0.0F);
// The number of indices in the selected vertex array
GLsizei nelems;
if (i < bctx->npasses - 1) {
assert(bctx->blur_fbos[0]);
assert(bctx->blur_textures[!curr]);
// not last pass, draw into framebuffer, with resized regions
glBindVertexArray(vao[1]);
nelems = vao_nelems[1];
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[0]);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, bctx->blur_textures[!curr], 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
if (!gl_check_fb_complete(GL_FRAMEBUFFER)) {
return false;
}
glUniform1f(p->uniform_opacity, 1.0F);
} else {
// last pass, draw directly into the back buffer, with origin
// regions. And apply mask if requested
if (mask) {
auto inner = (struct gl_texture *)mask->inner;
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, inner->texture);
glUniform1i(p->uniform_mask_inverted, mask->color_inverted);
glUniform1f(p->uniform_mask_corner_radius,
(float)mask->corner_radius);
glUniform2f(
p->uniform_mask_offset, (float)(mask_dst.x),
(float)(bctx->fb_height - mask_dst.y - inner->height));
}
glBindVertexArray(vao[0]);
nelems = vao_nelems[0];
glBindFramebuffer(GL_FRAMEBUFFER, target_fbo);
glUniform1f(p->uniform_opacity, (float)opacity);
}
glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y);
glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
// XXX use multiple draw calls is probably going to be slow than
// just simply blur the whole area.
curr = !curr;
}
return true;
}
bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rect_t *extent,
struct backend_image *mask, coord_t mask_dst, const GLuint vao[2],
const int vao_nelems[2], GLuint source_texture,
geometry_t source_size, GLuint target_fbo, GLuint default_mask) {
int dst_y_fb_coord = bctx->fb_height - extent->y2;
int iterations = bctx->blur_texture_count;
int scale_factor = 1;
// Kawase downsample pass
const gl_blur_shader_t *down_pass = &bctx->blur_shader[0];
assert(down_pass->prog);
glUseProgram(down_pass->prog);
glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
for (int i = 0; i < iterations; ++i) {
// Scale output width / height by half in each iteration
scale_factor <<= 1;
GLuint src_texture;
int tex_width, tex_height;
if (i == 0) {
// first pass: copy from back buffer
src_texture = source_texture;
tex_width = source_size.width;
tex_height = source_size.height;
} else {
// copy from previous pass
src_texture = bctx->blur_textures[i - 1];
auto src_size = bctx->texture_sizes[i - 1];
tex_width = src_size.width;
tex_height = src_size.height;
}
assert(src_texture);
assert(bctx->blur_fbos[i]);
glBindTexture(GL_TEXTURE_2D, src_texture);
glBindVertexArray(vao[1]);
auto nelems = vao_nelems[1];
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glUniform1f(down_pass->scale_loc, (GLfloat)scale_factor);
glUniform2f(down_pass->uniform_pixel_norm, 1.0F / (GLfloat)tex_width,
1.0F / (GLfloat)tex_height);
glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
}
// Kawase upsample pass
const gl_blur_shader_t *up_pass = &bctx->blur_shader[1];
assert(up_pass->prog);
glUseProgram(up_pass->prog);
glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
for (int i = iterations - 1; i >= 0; --i) {
// Scale output width / height back by two in each iteration
scale_factor >>= 1;
const GLuint src_texture = bctx->blur_textures[i];
assert(src_texture);
// Calculate normalized half-width/-height of a src pixel
auto src_size = bctx->texture_sizes[i];
int tex_width = src_size.width;
int tex_height = src_size.height;
// The number of indices in the selected vertex array
GLsizei nelems;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, src_texture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, default_mask);
glUniform1i(up_pass->uniform_mask_tex, 1);
glUniform2f(up_pass->uniform_mask_offset, 0.0F, 0.0F);
glUniform1i(up_pass->uniform_mask_inverted, 0);
glUniform1f(up_pass->uniform_mask_corner_radius, 0.0F);
if (i > 0) {
assert(bctx->blur_fbos[i - 1]);
// not last pass, draw into next framebuffer
glBindVertexArray(vao[1]);
nelems = vao_nelems[1];
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glUniform1f(up_pass->uniform_opacity, (GLfloat)1);
} else {
// last pass, draw directly into the back buffer
if (mask) {
auto inner = (struct gl_texture *)mask->inner;
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, inner->texture);
glUniform1i(up_pass->uniform_mask_inverted,
mask->color_inverted);
glUniform1f(up_pass->uniform_mask_corner_radius,
(float)mask->corner_radius);
glUniform2f(
up_pass->uniform_mask_offset, (float)(mask_dst.x),
(float)(bctx->fb_height - mask_dst.y - inner->height));
}
glBindVertexArray(vao[0]);
nelems = vao_nelems[0];
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target_fbo);
glUniform1f(up_pass->uniform_opacity, (GLfloat)opacity);
}
glUniform1f(up_pass->scale_loc, (GLfloat)scale_factor);
glUniform2f(up_pass->uniform_pixel_norm, 1.0F / (GLfloat)tex_width,
1.0F / (GLfloat)tex_height);
glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
}
return true;
}
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask,
coord_t mask_dst, const region_t *reg_blur,
const region_t *reg_visible attr_unused, GLuint source_texture,
geometry_t source_size, GLuint target_fbo, GLuint default_mask) {
bool ret = false;
if (source_size.width != bctx->fb_width || source_size.height != bctx->fb_height) {
// Resize the temporary textures used for blur in case the root
// size changed
bctx->fb_width = source_size.width;
bctx->fb_height = source_size.height;
for (int i = 0; i < bctx->blur_texture_count; ++i) {
auto tex_size = bctx->texture_sizes + i;
if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
// Use smaller textures for each iteration (quarter of the
// previous texture)
tex_size->width = 1 + ((bctx->fb_width - 1) >> (i + 1));
tex_size->height = 1 + ((bctx->fb_height - 1) >> (i + 1));
} else {
tex_size->width = bctx->fb_width;
tex_size->height = bctx->fb_height;
}
glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width,
tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
// Attach texture to FBO target
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
bctx->blur_textures[i], 0);
if (!gl_check_fb_complete(GL_FRAMEBUFFER)) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return false;
}
}
}
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
// Remainder: regions are in Xorg coordinates
auto reg_blur_resized =
resize_region(reg_blur, bctx->resize_width, bctx->resize_height);
const rect_t *extent = pixman_region32_extents((region_t *)reg_blur),
*extent_resized = pixman_region32_extents(&reg_blur_resized);
int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1;
if (width == 0 || height == 0) {
return true;
}
int nrects, nrects_resized;
const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects),
*rects_resized =
pixman_region32_rectangles(&reg_blur_resized, &nrects_resized);
if (!nrects || !nrects_resized) {
return true;
}
auto coord = ccalloc(nrects * 16, GLint);
auto indices = ccalloc(nrects * 6, GLuint);
auto extent_height = extent_resized->y2 - extent_resized->y1;
x_rect_to_coords(
nrects, rects, (coord_t){.x = extent_resized->x1, .y = extent_resized->y1},
extent_height, bctx->fb_height, source_size.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,
(coord_t){.x = extent_resized->x1, .y = extent_resized->y1},
extent_height, bctx->fb_height, bctx->fb_height, false,
coord_resized, indices_resized);
pixman_region32_fini(&reg_blur_resized);
GLuint vao[2];
glGenVertexArrays(2, vao);
GLuint bo[4];
glGenBuffers(4, bo);
glBindVertexArray(vao[0]);
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
indices, GL_STATIC_DRAW);
glEnableVertexAttribArray(vert_coord_loc);
glEnableVertexAttribArray(vert_in_texcoord_loc);
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
glBindVertexArray(vao[1]);
glBindBuffer(GL_ARRAY_BUFFER, bo[2]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]);
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16,
coord_resized, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
(long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized,
GL_STATIC_DRAW);
glEnableVertexAttribArray(vert_coord_loc);
glEnableVertexAttribArray(vert_in_texcoord_loc);
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
int vao_nelems[2] = {nrects * 6, nrects_resized * 6};
if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
ret = gl_dual_kawase_blur(opacity, bctx, extent_resized, mask, mask_dst,
vao, vao_nelems, source_texture, source_size,
target_fbo, default_mask);
} else {
ret = gl_kernel_blur(opacity, bctx, extent_resized, mask, mask_dst, vao,
vao_nelems, source_texture, source_size, target_fbo,
default_mask);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDeleteBuffers(4, bo);
glBindVertexArray(0);
glDeleteVertexArrays(2, vao);
glUseProgram(0);
free(indices);
free(coord);
free(indices_resized);
free(coord_resized);
gl_check_err();
return ret;
}
bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst,
const region_t *reg_blur, const region_t *reg_visible attr_unused) {
auto gd = (struct gl_data *)base;
auto bctx = (struct gl_blur_context *)ctx;
return gl_blur_impl(opacity, bctx, mask, mask_dst, reg_blur, reg_visible,
gd->back_texture,
(geometry_t){.width = gd->width, .height = gd->height},
gd->back_fbo, gd->default_mask_texture);
}
static inline void gl_free_blur_shader(gl_blur_shader_t *shader) {
if (shader->prog) {
glDeleteProgram(shader->prog);
}
shader->prog = 0;
}
void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) {
auto bctx = (struct gl_blur_context *)ctx;
// Free GLSL shaders/programs
for (int i = 0; i < bctx->npasses; ++i) {
gl_free_blur_shader(&bctx->blur_shader[i]);
}
free(bctx->blur_shader);
if (bctx->blur_texture_count && bctx->blur_textures) {
glDeleteTextures(bctx->blur_texture_count, bctx->blur_textures);
free(bctx->blur_textures);
}
if (bctx->blur_texture_count && bctx->texture_sizes) {
free(bctx->texture_sizes);
}
if (bctx->blur_fbo_count && bctx->blur_fbos) {
glDeleteFramebuffers(bctx->blur_fbo_count, bctx->blur_fbos);
free(bctx->blur_fbos);
}
bctx->blur_texture_count = 0;
bctx->blur_fbo_count = 0;
free(bctx);
gl_check_err();
}
/**
* Initialize GL blur filters.
*/
bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection,
enum blur_method method, void *args) {
bool success = false;
auto ctx = (struct gl_blur_context *)blur_context;
struct conv **kernels;
int nkernels;
ctx->method = BLUR_METHOD_KERNEL;
if (method == BLUR_METHOD_KERNEL) {
nkernels = ((struct kernel_blur_args *)args)->kernel_count;
kernels = ((struct kernel_blur_args *)args)->kernels;
} else {
kernels = generate_blur_kernel(method, args, &nkernels);
}
if (!nkernels) {
ctx->method = BLUR_METHOD_NONE;
return true;
}
// Specify required textures and FBOs
ctx->blur_texture_count = 2;
ctx->blur_fbo_count = 1;
ctx->blur_shader = ccalloc(max2(2, nkernels), gl_blur_shader_t);
char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
// Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
// Thanks to hiciu for reporting.
setlocale(LC_NUMERIC, "C");
// clang-format off
static const char *FRAG_SHADER_BLUR = GLSL(330,
%s\n // other extension pragmas
uniform sampler2D tex_src;
uniform vec2 pixel_norm;
uniform float opacity;
in vec2 texcoord;
out vec4 out_color;
float mask_factor();
void main() {
vec2 uv = texcoord * pixel_norm;
vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
%s //body of the convolution
out_color = sum / float(%.7g) * opacity * mask_factor();
}
);
static const char *FRAG_SHADER_BLUR_ADD = QUOTE(
sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%.7g, %.7g));
);
// clang-format on
const char *shader_add = FRAG_SHADER_BLUR_ADD;
char *extension = strdup("");
for (int i = 0; i < nkernels; i++) {
auto kern = kernels[i];
// Build shader
int width = kern->w, height = kern->h;
int nele = width * height;
// '%.7g' is at most 14 characters, inserted 3 times
size_t body_len = (strlen(shader_add) + 42) * (uint)nele;
char *shader_body = ccalloc(body_len, char);
char *pc = shader_body;
// Make use of the linear interpolation hardware by sampling 2 pixels with
// one texture access by sampling between both pixels based on their
// relative weight. Easiest done in a single dimension as 2D bilinear
// filtering would raise additional constraints on the kernels. Therefore
// only use interpolation along the larger dimension.
double sum = 0.0;
if (width > height) {
// use interpolation in x dimension (width)
for (int j = 0; j < height; ++j) {
for (int k = 0; k < width; k += 2) {
double val1, val2;
val1 = kern->data[j * width + k];
val2 = (k + 1 < width)
? kern->data[j * width + k + 1]
: 0;
double combined_weight = val1 + val2;
if (combined_weight == 0) {
continue;
}
sum += combined_weight;
double offset_x =
k + (val2 / combined_weight) - (width / 2);
double offset_y = j - (height / 2);
pc += snprintf(
pc, body_len - (ulong)(pc - shader_body),
shader_add, combined_weight, offset_x, offset_y);
assert(pc < shader_body + body_len);
}
}
} else {
// use interpolation in y dimension (height)
for (int j = 0; j < height; j += 2) {
for (int k = 0; k < width; ++k) {
double val1, val2;
val1 = kern->data[j * width + k];
val2 = (j + 1 < height)
? kern->data[(j + 1) * width + k]
: 0;
double combined_weight = val1 + val2;
if (combined_weight == 0) {
continue;
}
sum += combined_weight;
double offset_x = k - (width / 2);
double offset_y =
j + (val2 / combined_weight) - (height / 2);
pc += snprintf(
pc, body_len - (ulong)(pc - shader_body),
shader_add, combined_weight, offset_x, offset_y);
assert(pc < shader_body + body_len);
}
}
}
auto pass = ctx->blur_shader + i;
size_t shader_len = strlen(FRAG_SHADER_BLUR) + strlen(extension) +
strlen(shader_body) + 10 /* sum */ +
1 /* null terminator */;
char *shader_str = ccalloc(shader_len, char);
auto real_shader_len = snprintf(shader_str, shader_len, FRAG_SHADER_BLUR,
extension, shader_body, sum);
CHECK(real_shader_len >= 0);
CHECK((size_t)real_shader_len < shader_len);
free(shader_body);
// Build program
pass->prog = gl_create_program_from_strv(
(const char *[]){vertex_shader, NULL},
(const char *[]){shader_str, masking_glsl, NULL});
free(shader_str);
if (!pass->prog) {
log_error("Failed to create GLSL program.");
success = false;
goto out;
}
glBindFragDataLocation(pass->prog, 0, "out_color");
// Get uniform addresses
bind_uniform(pass, pixel_norm);
bind_uniform(pass, opacity);
bind_uniform(pass, mask_tex);
bind_uniform(pass, mask_offset);
bind_uniform(pass, mask_inverted);
bind_uniform(pass, mask_corner_radius);
log_info("Uniform locations: %d %d %d %d %d", pass->uniform_mask_tex,
pass->uniform_mask_offset, pass->uniform_mask_inverted,
pass->uniform_mask_corner_radius, pass->uniform_opacity);
pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig");
// Setup projection matrix
glUseProgram(pass->prog);
int pml = glGetUniformLocationChecked(pass->prog, "projection");
glUniformMatrix4fv(pml, 1, false, projection);
glUseProgram(0);
ctx->resize_width += kern->w / 2;
ctx->resize_height += kern->h / 2;
}
if (nkernels == 1) {
// Generate an extra null pass so we don't need special code path for
// the single pass case
auto pass = &ctx->blur_shader[1];
pass->prog = gl_create_program_from_strv(
(const char *[]){vertex_shader, NULL},
(const char *[]){copy_with_mask_frag, masking_glsl, NULL});
pass->uniform_pixel_norm = -1;
pass->uniform_opacity = -1;
pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig");
bind_uniform(pass, mask_tex);
bind_uniform(pass, mask_offset);
bind_uniform(pass, mask_inverted);
bind_uniform(pass, mask_corner_radius);
// Setup projection matrix
glUseProgram(pass->prog);
int pml = glGetUniformLocationChecked(pass->prog, "projection");
glUniformMatrix4fv(pml, 1, false, projection);
glUseProgram(0);
ctx->npasses = 2;
} else {
ctx->npasses = nkernels;
}
success = true;
out:
if (method != BLUR_METHOD_KERNEL) {
// We generated the blur kernels, so we need to free them
for (int i = 0; i < nkernels; i++) {
free(kernels[i]);
}
free(kernels);
}
free(extension);
// Restore LC_NUMERIC
setlocale(LC_NUMERIC, lc_numeric_old);
free(lc_numeric_old);
return success;
}
bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection,
enum blur_method method, void *args) {
bool success = false;
auto ctx = (struct gl_blur_context *)blur_context;
ctx->method = method;
auto blur_params = generate_dual_kawase_params(args);
// Specify required textures and FBOs
ctx->blur_texture_count = blur_params->iterations;
ctx->blur_fbo_count = blur_params->iterations;
ctx->resize_width += blur_params->expand;
ctx->resize_height += blur_params->expand;
ctx->npasses = 2;
ctx->blur_shader = ccalloc(ctx->npasses, gl_blur_shader_t);
char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
// Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
// Thanks to hiciu for reporting.
setlocale(LC_NUMERIC, "C");
// Dual-kawase downsample shader / program
auto down_pass = ctx->blur_shader;
{
// clang-format off
static const char *FRAG_SHADER_DOWN = GLSL(330,
uniform sampler2D tex_src;
uniform float scale = 1.0;
uniform vec2 pixel_norm;
in vec2 texcoord;
out vec4 out_color;
void main() {
vec2 offset = %.7g * pixel_norm;
vec2 uv = texcoord * pixel_norm * (2.0 / scale);
vec4 sum = texture2D(tex_src, uv) * 4.0;
sum += texture2D(tex_src, uv - vec2(0.5, 0.5) * offset);
sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset);
sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset);
sum += texture2D(tex_src, uv - vec2(0.5, -0.5) * offset);
out_color = sum / 8.0;
}
);
// clang-format on
// Build shader
size_t shader_len =
strlen(FRAG_SHADER_DOWN) + 10 /* offset */ + 1 /* null terminator */;
char *shader_str = ccalloc(shader_len, char);
auto real_shader_len =
snprintf(shader_str, shader_len, FRAG_SHADER_DOWN, blur_params->offset);
CHECK(real_shader_len >= 0);
CHECK((size_t)real_shader_len < shader_len);
// Build program
down_pass->prog = gl_create_program_from_str(vertex_shader, shader_str);
free(shader_str);
if (!down_pass->prog) {
log_error("Failed to create GLSL program.");
success = false;
goto out;
}
glBindFragDataLocation(down_pass->prog, 0, "out_color");
// Get uniform addresses
bind_uniform(down_pass, pixel_norm);
down_pass->texorig_loc =
glGetUniformLocationChecked(down_pass->prog, "texorig");
down_pass->scale_loc =
glGetUniformLocationChecked(down_pass->prog, "scale");
// Setup projection matrix
glUseProgram(down_pass->prog);
int pml = glGetUniformLocationChecked(down_pass->prog, "projection");
glUniformMatrix4fv(pml, 1, false, projection);
glUseProgram(0);
}
// Dual-kawase upsample shader / program
auto up_pass = ctx->blur_shader + 1;
{
// clang-format off
static const char *FRAG_SHADER_UP = GLSL(330,
uniform sampler2D tex_src;
uniform float scale = 1.0;
uniform vec2 pixel_norm;
uniform float opacity;
in vec2 texcoord;
out vec4 out_color;
float mask_factor();
void main() {
vec2 offset = %.7g * pixel_norm;
vec2 uv = texcoord * pixel_norm / (2 * scale);
vec4 sum = texture2D(tex_src, uv + vec2(-1.0, 0.0) * offset);
sum += texture2D(tex_src, uv + vec2(-0.5, 0.5) * offset) * 2.0;
sum += texture2D(tex_src, uv + vec2(0.0, 1.0) * offset);
sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset) * 2.0;
sum += texture2D(tex_src, uv + vec2(1.0, 0.0) * offset);
sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset) * 2.0;
sum += texture2D(tex_src, uv + vec2(0.0, -1.0) * offset);
sum += texture2D(tex_src, uv + vec2(-0.5, -0.5) * offset) * 2.0;
out_color = sum / 12.0 * opacity * mask_factor();
}
);
// clang-format on
// Build shader
size_t shader_len =
strlen(FRAG_SHADER_UP) + 10 /* offset */ + 1 /* null terminator */;
char *shader_str = ccalloc(shader_len, char);
auto real_shader_len =
snprintf(shader_str, shader_len, FRAG_SHADER_UP, blur_params->offset);
CHECK(real_shader_len >= 0);
CHECK((size_t)real_shader_len < shader_len);
// Build program
up_pass->prog = gl_create_program_from_strv(
(const char *[]){vertex_shader, NULL},
(const char *[]){shader_str, masking_glsl, NULL});
free(shader_str);
if (!up_pass->prog) {
log_error("Failed to create GLSL program.");
success = false;
goto out;
}
glBindFragDataLocation(up_pass->prog, 0, "out_color");
// Get uniform addresses
bind_uniform(up_pass, pixel_norm);
bind_uniform(up_pass, opacity);
bind_uniform(up_pass, mask_tex);
bind_uniform(up_pass, mask_offset);
bind_uniform(up_pass, mask_inverted);
bind_uniform(up_pass, mask_corner_radius);
up_pass->texorig_loc =
glGetUniformLocationChecked(up_pass->prog, "texorig");
up_pass->scale_loc = glGetUniformLocationChecked(up_pass->prog, "scale");
// Setup projection matrix
glUseProgram(up_pass->prog);
int pml = glGetUniformLocationChecked(up_pass->prog, "projection");
glUniformMatrix4fv(pml, 1, false, projection);
glUseProgram(0);
}
success = true;
out:
free(blur_params);
if (!success) {
ctx = NULL;
}
// Restore LC_NUMERIC
setlocale(LC_NUMERIC, lc_numeric_old);
free(lc_numeric_old);
return success;
}
void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) {
bool success;
auto gd = (struct gl_data *)base;
auto ctx = ccalloc(1, struct gl_blur_context);
if (!method || method >= BLUR_METHOD_INVALID) {
ctx->method = BLUR_METHOD_NONE;
return ctx;
}
// Set projection matrix to gl viewport dimensions so we can use screen
// coordinates for all vertices
// Note: OpenGL matrices are column major
GLint viewport_dimensions[2];
glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0},
{0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0},
{0, 0, 0, 0},
{-1, -1, 0, 1}};
if (method == BLUR_METHOD_DUAL_KAWASE) {
success = gl_create_dual_kawase_blur_context(ctx, projection_matrix[0],
method, args);
} else {
success =
gl_create_kernel_blur_context(ctx, projection_matrix[0], method, args);
}
if (!success || ctx->method == BLUR_METHOD_NONE) {
goto out;
}
// Texture size will be defined by gl_blur
ctx->blur_textures = ccalloc(ctx->blur_texture_count, GLuint);
ctx->texture_sizes = ccalloc(ctx->blur_texture_count, struct texture_size);
glGenTextures(ctx->blur_texture_count, ctx->blur_textures);
for (int i = 0; i < ctx->blur_texture_count; ++i) {
glBindTexture(GL_TEXTURE_2D, ctx->blur_textures[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
// Generate FBO and textures when needed
ctx->blur_fbos = ccalloc(ctx->blur_fbo_count, GLuint);
glGenFramebuffers(ctx->blur_fbo_count, ctx->blur_fbos);
for (int i = 0; i < ctx->blur_fbo_count; ++i) {
if (!ctx->blur_fbos[i]) {
log_error("Failed to generate framebuffer objects for blur");
success = false;
goto out;
}
}
out:
if (!success) {
gl_destroy_blur_context(&gd->base, ctx);
ctx = NULL;
}
gl_check_err();
return ctx;
}
void gl_get_blur_size(void *blur_context, int *width, int *height) {
auto ctx = (struct gl_blur_context *)blur_context;
*width = ctx->resize_width;
*height = ctx->resize_height;
}

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
#define CASESTRRET(s) \
case s: return #s
struct gl_blur_context;
static inline GLint glGetUniformLocationChecked(GLuint p, const char *name) {
auto ret = glGetUniformLocation(p, name);
@ -52,6 +53,11 @@ typedef struct {
GLuint prog;
} gl_brightness_shader_t;
typedef struct {
GLuint prog;
GLint uniform_color;
} gl_shadow_shader_t;
// Program and uniforms for blur shader
typedef struct {
GLuint prog;
@ -97,6 +103,7 @@ struct gl_data {
gl_win_shader_t *default_shader;
gl_brightness_shader_t brightness_shader;
gl_fill_shader_t fill_shader;
gl_shadow_shader_t shadow_shader;
GLuint back_texture, back_fbo;
GLuint present_prog;
@ -117,9 +124,14 @@ typedef struct session session_t;
#define GL_PROG_MAIN_INIT \
{ .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, }
void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
int extent_height, int texture_height, int root_height,
bool y_inverted, GLint *coord, GLuint *indices);
GLuint gl_create_shader(GLenum shader_type, const char *shader_str);
GLuint gl_create_program(const GLuint *const shaders, int nshaders);
GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str);
GLuint gl_create_program_from_strv(const char **vert_shaders, const char **frag_shaders);
void *gl_create_window_shader(backend_t *backend_data, const char *source);
void gl_destroy_window_shader(backend_t *backend_data, void *shader);
uint64_t gl_get_shader_attributes(backend_t *backend_data, void *shader);
@ -149,8 +161,16 @@ void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visi
bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst,
const region_t *reg_blur, const region_t *reg_visible);
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask,
coord_t mask_dst, const region_t *reg_blur,
const region_t *reg_visible attr_unused, GLuint source_texture,
geometry_t source_size, GLuint target_fbo, GLuint default_mask);
void *gl_create_blur_context(backend_t *base, enum blur_method, void *args);
void gl_destroy_blur_context(backend_t *base, void *ctx);
struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius);
void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx);
void *gl_shadow_from_mask(backend_t *base, void *mask,
struct backend_shadow_context *sctx, struct color color);
void gl_get_blur_size(void *blur_context, int *width, int *height);
void gl_fill(backend_t *base, struct color, const region_t *clip);
@ -258,3 +278,13 @@ static inline bool gl_has_extension(const char *ext) {
log_info("Missing GL extension %s.", ext);
return false;
}
static const GLuint vert_coord_loc = 0;
static const GLuint vert_in_texcoord_loc = 1;
#define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__
#define QUOTE(...) #__VA_ARGS__
extern const char vertex_shader[], copy_with_mask_frag[], masking_glsl[], dummy_frag[],
fill_frag[], fill_vert[], interpolating_frag[], interpolating_vert[], win_shader_glsl[],
win_shader_default[], present_vertex_shader[], shadow_colorization_frag[];

View File

@ -536,7 +536,10 @@ struct backend_operations glx_ops = {
.is_image_transparent = default_is_image_transparent,
.present = glx_present,
.buffer_age = glx_buffer_age,
.render_shadow = default_backend_render_shadow,
.create_shadow_context = gl_create_shadow_context,
.destroy_shadow_context = gl_destroy_shadow_context,
.render_shadow = backend_render_shadow_from_mask,
.shadow_from_mask = gl_shadow_from_mask,
.make_mask = gl_make_mask,
.fill = gl_fill,
.create_blur_context = gl_create_blur_context,

187
src/backend/gl/shaders.c Normal file
View File

@ -0,0 +1,187 @@
#include "gl_common.h"
// clang-format off
const char dummy_frag[] = GLSL(330,
uniform sampler2D tex;
in vec2 texcoord;
void main() {
gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0);
}
);
const char copy_with_mask_frag[] = GLSL(330,
uniform sampler2D tex;
in vec2 texcoord;
float mask_factor();
void main() {
gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0) * mask_factor();
}
);
const char fill_frag[] = GLSL(330,
uniform vec4 color;
void main() {
gl_FragColor = color;
}
);
const char fill_vert[] = GLSL(330,
layout(location = 0) in vec2 in_coord;
uniform mat4 projection;
void main() {
gl_Position = projection * vec4(in_coord, 0, 1);
}
);
const char interpolating_frag[] = GLSL(330,
uniform sampler2D tex;
in vec2 texcoord;
void main() {
gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1);
}
);
const char interpolating_vert[] = GLSL(330,
uniform mat4 projection;
uniform vec2 texsize;
layout(location = 0) in vec2 in_coord;
layout(location = 1) in vec2 in_texcoord;
out vec2 texcoord;
void main() {
gl_Position = projection * vec4(in_coord, 0, 1);
texcoord = in_texcoord / texsize;
}
);
const char masking_glsl[] = GLSL(330,
uniform sampler2D mask_tex;
uniform vec2 mask_offset;
uniform float mask_corner_radius;
uniform bool mask_inverted;
in vec2 texcoord;
float mask_rectangle_sdf(vec2 point, vec2 half_size) {
vec2 d = abs(point) - half_size;
return length(max(d, 0.0));
}
float mask_factor() {
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 = mask_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;
}
return mask.r;
}
);
const char win_shader_glsl[] = GLSL(330,
uniform float opacity;
uniform float dim;
uniform float corner_radius;
uniform float border_width;
uniform bool invert_color;
in vec2 texcoord;
uniform sampler2D tex;
uniform sampler2D brightness;
uniform float max_brightness;
// Signed distance field for rectangle center at (0, 0), with size of
// half_size * 2
float rectangle_sdf(vec2 point, vec2 half_size) {
vec2 d = abs(point) - half_size;
return length(max(d, 0.0));
}
vec4 default_post_processing(vec4 c) {
vec4 border_color = texture(tex, vec2(0.0, 0.5));
if (invert_color) {
c = vec4(c.aaa - c.rgb, c.a);
border_color = vec4(border_color.aaa - border_color.rgb, border_color.a);
}
c = vec4(c.rgb * (1.0 - dim), c.a) * opacity;
border_color = vec4(border_color.rgb * (1.0 - dim), border_color.a) * opacity;
vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb;
// Ref: https://en.wikipedia.org/wiki/Relative_luminance
float brightness = rgb_brightness.r * 0.21 +
rgb_brightness.g * 0.72 +
rgb_brightness.b * 0.07;
if (brightness > max_brightness) {
c.rgb = c.rgb * (max_brightness / brightness);
border_color.rgb = border_color.rgb * (max_brightness / brightness);
}
// Rim color is the color of the outer rim of the window, if there is no
// border, it's the color of the window itself, otherwise it's the border.
// Using mix() to avoid a branch here.
vec4 rim_color = mix(c, border_color, clamp(border_width, 0.0f, 1.0f));
vec2 outer_size = vec2(textureSize(tex, 0));
vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f;
float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f,
inner_size / 2.0f) - corner_radius;
if (rect_distance > 0.0f) {
c = (1.0f - clamp(rect_distance, 0.0f, 1.0f)) * rim_color;
} else {
float factor = clamp(rect_distance + border_width, 0.0f, 1.0f);
c = (1.0f - factor) * c + factor * border_color;
}
return c;
}
vec4 window_shader();
float mask_factor();
void main() {
gl_FragColor = window_shader() * mask_factor();
}
);
const char win_shader_default[] = GLSL(330,
in vec2 texcoord;
uniform sampler2D tex;
vec4 default_post_processing(vec4 c);
vec4 window_shader() {
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
return default_post_processing(c);
}
);
const char present_vertex_shader[] = GLSL(330,
uniform mat4 projection;
layout(location = 0) in vec2 coord;
out vec2 texcoord;
void main() {
gl_Position = projection * vec4(coord, 0, 1);
texcoord = coord;
}
);
const char vertex_shader[] = GLSL(330,
uniform mat4 projection;
uniform float scale = 1.0;
uniform vec2 texorig;
layout(location = 0) in vec2 coord;
layout(location = 1) in vec2 in_texcoord;
out vec2 texcoord;
void main() {
gl_Position = projection * vec4(coord, 0, scale);
texcoord = in_texcoord + texorig;
}
);
const char shadow_colorization_frag[] = GLSL(330,
uniform vec4 color;
uniform sampler2D tex;
in vec2 texcoord;
out vec4 out_color;
void main() {
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
out_color = c.r * color;
}
);
// clang-format on

View File

@ -3,5 +3,5 @@ srcs += [ files('backend_common.c', 'xrender/xrender.c', 'dummy/dummy.c', 'backe
# enable opengl
if get_option('opengl')
srcs += [ files('gl/gl_common.c', 'gl/glx.c') ]
srcs += [ files('gl/gl_common.c', 'gl/glx.c', 'gl/blur.c', 'gl/shaders.c') ]
endif

View File

@ -968,6 +968,8 @@ struct backend_operations xrender_ops = {
.fill = fill,
.bind_pixmap = bind_pixmap,
.release_image = release_image,
.create_shadow_context = default_create_shadow_context,
.destroy_shadow_context = default_destroy_shadow_context,
.render_shadow = default_backend_render_shadow,
.make_mask = make_mask,
//.prepare_win = prepare_win,

View File

@ -300,8 +300,8 @@ typedef struct session {
xcb_render_picture_t cshadow_picture;
/// 1x1 white Picture.
xcb_render_picture_t white_picture;
/// Gaussian map of shadow.
struct conv *gaussian_map;
/// Backend shadow context.
struct backend_shadow_context *shadow_context;
// for shadow precomputation
/// A region in which shadow is not painted on.
region_t shadow_exclude_reg;

View File

@ -90,15 +90,20 @@ conv *gaussian_kernel(double r, int size) {
/// Estimate the element of the sum of the first row in a gaussian kernel with standard
/// deviation `r` and size `size`,
static inline double estimate_first_row_sum(double size, double r) {
// `factor` is integral of gaussian from -size to size
double factor = erf(size / r / sqrt(2));
// `a` is gaussian at (size, 0)
double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r;
// The sum of the whole kernel is normalized to 1, i.e. each element is divided by
// factor sqaured. So the sum of the first row is a * factor / factor^2 = a /
// factor
return a / factor;
}
/// Pick a suitable gaussian kernel radius for a given kernel size. The returned radius
/// is the maximum possible radius (<= size*2) that satisfies no sum of the rows in
/// the kernel are less than `row_limit` (up to certain precision).
static inline double gaussian_kernel_std_for_size(int size, double row_limit) {
/// Pick a suitable gaussian kernel standard deviation for a given kernel size. The
/// returned radius is the maximum possible radius (<= size*2) that satisfies no sum of
/// the rows in the kernel are less than `row_limit` (up to certain precision).
static inline double gaussian_kernel_std_for_size(double size, double row_limit) {
assert(size > 0);
if (row_limit >= 1.0 / 2.0 / size) {
return size * 2;
@ -121,14 +126,14 @@ static inline double gaussian_kernel_std_for_size(int size, double row_limit) {
/// transparent, so the transition from shadow to the background is smooth.
///
/// @param[in] shadow_radius the radius of the shadow
conv *gaussian_kernel_autodetect_deviation(int shadow_radius) {
conv *gaussian_kernel_autodetect_deviation(double shadow_radius) {
assert(shadow_radius >= 0);
int size = shadow_radius * 2 + 1;
int size = (int)(shadow_radius * 2 + 1);
if (shadow_radius == 0) {
return gaussian_kernel(0, size);
}
double std = gaussian_kernel_std_for_size(shadow_radius, 1.0 / 256.0);
double std = gaussian_kernel_std_for_size(shadow_radius, 0.5 / 256.0);
return gaussian_kernel(std, size);
}

View File

@ -27,7 +27,7 @@ conv *gaussian_kernel(double r, int size);
/// transparent.
///
/// @param[in] shadow_radius the radius of the shadow
conv *gaussian_kernel_autodetect_deviation(int shadow_radius);
conv *gaussian_kernel_autodetect_deviation(double shadow_radius);
/// preprocess kernels to make shadow generation faster
/// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive

View File

@ -451,6 +451,11 @@ static void destroy_backend(session_t *ps) {
ps->backend_data, ps->backend_blur_context);
ps->backend_blur_context = NULL;
}
if (ps->shadow_context) {
ps->backend_data->ops->destroy_shadow_context(ps->backend_data,
ps->shadow_context);
ps->shadow_context = NULL;
}
ps->backend_data->ops->deinit(ps->backend_data);
ps->backend_data = NULL;
}
@ -504,6 +509,12 @@ static bool initialize_backend(session_t *ps) {
return false;
}
ps->backend_data->ops = backend_list[ps->o.backend];
ps->shadow_context = ps->backend_data->ops->create_shadow_context(
ps->backend_data, ps->o.shadow_radius);
if (!ps->shadow_context) {
log_fatal("Failed to initialize shadow context, aborting...");
goto err;
}
if (!initialize_blur(ps)) {
log_fatal("Failed to prepare for background blur, aborting...");
@ -553,6 +564,11 @@ static bool initialize_backend(session_t *ps) {
// The old backends binds pixmap lazily, nothing to do here
return true;
err:
if (ps->shadow_context) {
ps->backend_data->ops->destroy_shadow_context(ps->backend_data,
ps->shadow_context);
ps->shadow_context = NULL;
}
ps->backend_data->ops->deinit(ps->backend_data);
ps->backend_data = NULL;
quit(ps);
@ -1675,7 +1691,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
.black_picture = XCB_NONE,
.cshadow_picture = XCB_NONE,
.white_picture = XCB_NONE,
.gaussian_map = NULL,
.shadow_context = NULL,
#ifdef CONFIG_VSYNC_DRM
.drm_fd = -1,
@ -1929,8 +1945,11 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
}
}
ps->gaussian_map = gaussian_kernel_autodetect_deviation(ps->o.shadow_radius);
sum_kernel_preprocess(ps->gaussian_map);
if (ps->o.legacy_backends) {
ps->shadow_context =
(void *)gaussian_kernel_autodetect_deviation(ps->o.shadow_radius);
sum_kernel_preprocess((conv *)ps->shadow_context);
}
rebuild_shadow_exclude_reg(ps);
@ -2408,7 +2427,9 @@ static void session_destroy(session_t *ps) {
// Flush all events
x_sync(ps->c);
ev_io_stop(ps->loop, &ps->xiow);
free_conv(ps->gaussian_map);
if (ps->o.legacy_backends) {
free_conv((conv *)ps->shadow_context);
}
destroy_atoms(ps->atoms);
#ifdef DEBUG_XRC

View File

@ -649,8 +649,9 @@ static bool get_root_tile(session_t *ps) {
static void paint_root(session_t *ps, const region_t *reg_paint) {
// If there is no root tile pixmap, try getting one.
// If that fails, give up.
if (!ps->root_tile_paint.pixmap && !get_root_tile(ps))
if (!ps->root_tile_paint.pixmap && !get_root_tile(ps)) {
return;
}
paint_region(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint,
ps->root_tile_paint.pict);
@ -669,7 +670,7 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit
xcb_render_picture_t shadow_picture = XCB_NONE, shadow_picture_argb = XCB_NONE;
xcb_gcontext_t gc = XCB_NONE;
shadow_image = make_shadow(ps->c, ps->gaussian_map, opacity, width, height);
shadow_image = make_shadow(ps->c, (conv *)ps->shadow_context, opacity, width, height);
if (!shadow_image) {
log_error("failed to make shadow");
return XCB_NONE;
@ -689,8 +690,9 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit
ps->c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL);
shadow_picture_argb = x_create_picture_with_standard_and_pixmap(
ps->c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL);
if (!shadow_picture || !shadow_picture_argb)
if (!shadow_picture || !shadow_picture_argb) {
goto shadow_picture_err;
}
gc = x_new_id(ps->c);
xcb_create_gc(ps->c, gc, shadow_pixmap, 0, NULL);

322
src/win.c
View File

@ -345,14 +345,33 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w
return true;
}
bool win_bind_mask(struct backend_base *b, struct managed_win *w) {
auto reg_bound_local = win_get_bounding_shape_global_by_val(w);
pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
w->mask_image = b->ops->make_mask(
b, (geometry_t){.width = w->g.width, .height = w->g.height}, &reg_bound_local);
if (!w->mask_image) {
return false;
}
b->ops->set_image_property(b, IMAGE_PROPERTY_CORNER_RADIUS, w->mask_image,
(double[]){w->corner_radius});
return true;
}
bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c,
struct conv *kernel) {
struct backend_shadow_context *sctx) {
assert(!w->shadow_image);
assert(w->shadow);
w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, kernel, c.red,
c.green, c.blue, c.alpha);
if ((w->corner_radius == 0 && w->bounding_shaped == false) ||
b->ops->shadow_from_mask == NULL) {
w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, sctx, c);
} else {
win_bind_mask(b, w);
w->shadow_image = b->ops->shadow_from_mask(b, w->mask_image, sctx, c);
}
if (!w->shadow_image) {
log_error("Failed to bind shadow image, shadow will be disabled for "
log_error("Failed to bind shadow image, shadow will be disabled "
"for "
"%#010x (%s)",
w->base.id, w->name);
win_set_flags(w, WIN_FLAGS_SHADOW_NONE);
@ -366,10 +385,9 @@ bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color
}
void win_release_images(struct backend_base *backend, struct managed_win *w) {
// We don't want to decide what we should do if the image we want to release is
// stale (do we clear the stale flags or not?)
// But if we are not releasing any images anyway, we don't care about the stale
// flags.
// We don't want to decide what we should do if the image we want to
// release is stale (do we clear the stale flags or not?) But if we are
// not releasing any images anyway, we don't care about the stale flags.
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE));
@ -384,13 +402,15 @@ void win_release_images(struct backend_base *backend, struct managed_win *w) {
win_release_mask(backend, w);
}
/// Returns true if the `prop` property is stale, as well as clears the stale flag.
/// Returns true if the `prop` property is stale, as well as clears the stale
/// flag.
static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop);
/// Returns true if any of the properties are stale, as well as clear all the stale flags.
/// Returns true if any of the properties are stale, as well as clear all the
/// stale flags.
static void win_clear_all_properties_stale(struct managed_win *w);
/// Fetch new window properties from the X server, and run appropriate updates. Might set
/// WIN_FLAGS_FACTOR_CHANGED
/// Fetch new window properties from the X server, and run appropriate updates.
/// Might set WIN_FLAGS_FACTOR_CHANGED
static void win_update_properties(session_t *ps, struct managed_win *w) {
if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_WINDOW_TYPE)) {
win_update_wintype(ps, w);
@ -441,8 +461,8 @@ static void win_update_properties(session_t *ps, struct managed_win *w) {
/// Handle non-image flags. This phase might set IMAGES_STALE flags
void win_process_update_flags(session_t *ps, struct managed_win *w) {
// Whether the window was visible before we process the mapped flag. i.e. is the
// window just mapped.
// Whether the window was visible before we process the mapped flag. i.e.
// is the window just mapped.
bool was_visible = win_is_real_visible(w);
log_trace("Processing flags for window %#010x (%s), was visible: %d", w->base.id,
w->name, was_visible);
@ -457,8 +477,8 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
return;
}
// Check client first, because later property updates need accurate client window
// information
// Check client first, because later property updates need accurate client
// window information
if (win_check_flags_all(w, WIN_FLAGS_CLIENT_STALE)) {
win_recheck_client(ps, w);
win_clear_flags(w, WIN_FLAGS_CLIENT_STALE);
@ -467,15 +487,15 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
bool damaged = false;
if (win_check_flags_any(w, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) {
if (was_visible) {
// Mark the old extents of this window as damaged. The new extents
// will be marked damaged below, after the window extents are
// updated.
// Mark the old extents of this window as damaged. The new
// extents will be marked damaged below, after the window
// extents are updated.
//
// If the window is just mapped, we don't need to mark the old
// extent as damaged. (It's possible that the window was in fading
// and is interrupted by being mapped. In that case, the fading
// window will be added to damage by map_win_start, so we don't
// need to do it here)
// If the window is just mapped, we don't need to mark the
// old extent as damaged. (It's possible that the window
// was in fading and is interrupted by being mapped. In
// that case, the fading window will be added to damage by
// map_win_start, so we don't need to do it here)
add_damage_from_win(ps, w);
}
@ -502,7 +522,8 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
win_clear_flags(w, WIN_FLAGS_PROPERTY_STALE);
}
// Factor change flags could be set by previous stages, so must be handled last
// Factor change flags could be set by previous stages, so must be handled
// last
if (win_check_flags_all(w, WIN_FLAGS_FACTOR_CHANGED)) {
win_on_factor_change(ps, w);
win_clear_flags(w, WIN_FLAGS_FACTOR_CHANGED);
@ -534,9 +555,10 @@ void win_process_image_flags(session_t *ps, struct managed_win *w) {
}
if (win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)) {
// Check to make sure the window is still mapped, otherwise we
// won't be able to rebind pixmap after releasing it, yet we might
// still need the pixmap for rendering.
// Check to make sure the window is still mapped,
// otherwise we won't be able to rebind pixmap after
// releasing it, yet we might still need the pixmap for
// rendering.
assert(w->state != WSTATE_UNMAPPING && w->state != WSTATE_DESTROYING);
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
// Must release images first, otherwise breaks
@ -556,7 +578,7 @@ void win_process_image_flags(session_t *ps, struct managed_win *w) {
.green = ps->o.shadow_green,
.blue = ps->o.shadow_blue,
.alpha = ps->o.shadow_opacity},
ps->gaussian_map);
ps->shadow_context);
}
}
@ -616,7 +638,8 @@ int win_update_name(session_t *ps, struct managed_win *w) {
}
if (!(wid_get_text_prop(ps, w->client_win, ps->atoms->a_NET_WM_NAME, &strlst, &nstr))) {
log_debug("(%#010x): _NET_WM_NAME unset, falling back to WM_NAME.",
log_debug("(%#010x): _NET_WM_NAME unset, falling back to "
"WM_NAME.",
w->client_win);
if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_NAME, &strlst, &nstr)) {
@ -738,8 +761,8 @@ winmode_t win_calc_mode(const struct managed_win *w) {
if (win_has_alpha(w)) {
if (w->client_win == XCB_NONE) {
// This is a window not managed by the WM, and it has alpha,
// so it's transparent. No need to check WM frame.
// This is a window not managed by the WM, and it has
// alpha, so it's transparent. No need to check WM frame.
return WMODE_TRANS;
}
// The WM window has alpha
@ -749,12 +772,12 @@ winmode_t win_calc_mode(const struct managed_win *w) {
return WMODE_TRANS;
}
if (win_has_frame(w)) {
// The client window doesn't have alpha, but we have a WM frame
// window, which has alpha.
// The client window doesn't have alpha, but we have a WM
// frame window, which has alpha.
return WMODE_FRAME_TRANS;
}
// Although the WM window has alpha, the frame window has 0 size, so
// consider the window solid
// Although the WM window has alpha, the frame window has 0 size,
// so consider the window solid
}
if (w->frame_opacity != 1.0 && win_has_frame(w)) {
@ -770,8 +793,9 @@ winmode_t win_calc_mode(const struct managed_win *w) {
*
* The priority of opacity settings are:
*
* inactive_opacity_override (if set, and unfocused) > _NET_WM_WINDOW_OPACITY (if set) >
* opacity-rules (if matched) > window type default opacity > active/inactive opacity
* inactive_opacity_override (if set, and unfocused) > _NET_WM_WINDOW_OPACITY (if
* set) > opacity-rules (if matched) > window type default opacity >
* active/inactive opacity
*
* @param ps current session
* @param w struct _win object representing the window
@ -796,7 +820,8 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
} else if (!safe_isnan(ps->o.wintype_option[w->window_type].opacity)) {
opacity = ps->o.wintype_option[w->window_type].opacity;
} else {
// Respect active_opacity only when the window is physically focused
// Respect active_opacity only when the window is physically
// focused
if (win_is_focused_raw(ps, w))
opacity = ps->o.active_opacity;
else if (!w->focused)
@ -832,7 +857,8 @@ bool win_should_dim(session_t *ps, const struct managed_win *w) {
* Determine if a window should fade on opacity change.
*/
bool win_should_fade(session_t *ps, const struct managed_win *w) {
// To prevent it from being overwritten by last-paint value if the window is
// To prevent it from being overwritten by last-paint value if the window
// is
if (w->fade_force != UNSET) {
return w->fade_force;
}
@ -876,7 +902,8 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new
log_debug("Updating shadow property of window %#010x (%s) to %d", w->base.id,
w->name, shadow_new);
// We don't handle property updates of non-visible windows until they are mapped.
// We don't handle property updates of non-visible windows until they are
// mapped.
assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING &&
w->state != WSTATE_UNMAPPING);
@ -898,10 +925,10 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new
// Note: because the release and creation of the shadow images are
// delayed. When multiple shadow changes happen in a row, without
// rendering phase between them, there could be a stale shadow image
// attached to the window even if w->shadow was previously false. And vice
// versa. So we check the STALE flag before asserting the existence of the
// shadow image.
// rendering phase between them, there could be a stale shadow
// image attached to the window even if w->shadow was previously
// false. And vice versa. So we check the STALE flag before
// asserting the existence of the shadow image.
if (w->shadow) {
// Mark the new extents as damaged if the shadow is added
assert(!w->shadow_image ||
@ -911,7 +938,8 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new
win_extents(w, &extents);
add_damage_from_win(ps, w);
} else {
// Mark the old extents as damaged if the shadow is removed
// Mark the old extents as damaged if the shadow is
// removed
assert(w->shadow_image ||
win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) ||
ps->o.legacy_backends);
@ -920,11 +948,12 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new
// Delayed update of shadow image
// By setting WIN_FLAGS_SHADOW_STALE, we ask win_process_flags to
// re-create or release the shaodw in based on whether w->shadow is set.
// re-create or release the shaodw in based on whether w->shadow
// is set.
win_set_flags(w, WIN_FLAGS_SHADOW_STALE);
// Only set pending_updates if we are redirected. Otherwise change of a
// shadow won't have influence on whether we should redirect.
// Only set pending_updates if we are redirected. Otherwise change
// of a shadow won't have influence on whether we should redirect.
ps->pending_updates = true;
}
@ -1056,8 +1085,9 @@ win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_backgrou
w->blur_background = blur_background_new;
// This damage might not be absolutely necessary (e.g. when the window is opaque),
// but blur_background changes should be rare, so this should be fine.
// This damage might not be absolutely necessary (e.g. when the window is
// opaque), but blur_background changes should be rare, so this should be
// fine.
add_damage_from_win(ps, w);
}
@ -1069,8 +1099,8 @@ win_set_fg_shader(session_t *ps, struct managed_win *w, struct shader_info *shad
w->fg_shader = shader_new;
// A different shader might change how the window is drawn, these changes should
// be rare however, so this should be fine.
// A different shader might change how the window is drawn, these changes
// should be rare however, so this should be fine.
add_damage_from_win(ps, w);
}
@ -1089,7 +1119,8 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w)
log_debug("Blur background disabled by wintypes");
blur_background_new = false;
} else if (c2_match(ps, w, ps->o.blur_background_blacklist, NULL)) {
log_debug("Blur background disabled by blur-background-exclude");
log_debug("Blur background disabled by "
"blur-background-exclude");
blur_background_new = false;
}
}
@ -1169,8 +1200,8 @@ void win_update_opacity_rule(session_t *ps, struct managed_win *w) {
*/
void win_on_factor_change(session_t *ps, struct managed_win *w) {
log_debug("Window %#010x (%s) factor change", w->base.id, w->name);
// Focus needs to be updated first, as other rules might depend on the focused
// state of the window
// Focus needs to be updated first, as other rules might depend on the
// focused state of the window
win_update_focused(ps, w);
win_determine_shadow(ps, w);
@ -1208,7 +1239,8 @@ void win_on_win_size_change(session_t *ps, struct managed_win *w) {
w->shadow_width = w->widthb + ps->o.shadow_radius * 2;
w->shadow_height = w->heightb + ps->o.shadow_radius * 2;
// We don't handle property updates of non-visible windows until they are mapped.
// We don't handle property updates of non-visible windows until they are
// mapped.
assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING &&
w->state != WSTATE_UNMAPPING);
@ -1437,21 +1469,21 @@ struct win *add_win_top(session_t *ps, xcb_window_t id) {
return add_win(ps, id, &ps->window_stack);
}
/// Insert a new window above window with id `below`, if there is no window, add to top
/// New window will be in unmapped state
/// Insert a new window above window with id `below`, if there is no window, add
/// to top New window will be in unmapped state
struct win *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below) {
struct win *w = NULL;
HASH_FIND_INT(ps->windows, &below, w);
if (!w) {
if (!list_is_empty(&ps->window_stack)) {
// `below` window is not found even if the window stack is not
// empty
// `below` window is not found even if the window stack is
// not empty
return NULL;
}
return add_win_top(ps, id);
} else {
// we found something from the hash table, so if the stack is empty,
// we are in an inconsistent state.
// we found something from the hash table, so if the stack is
// empty, we are in an inconsistent state.
assert(!list_is_empty(&ps->window_stack));
return add_win(ps, id, w->stack_neighbour.prev);
}
@ -1478,7 +1510,8 @@ struct win *fill_win(session_t *ps, struct win *w) {
.in_openclose = true, // set to false after first map is done,
// true here because window is just created
.reg_ignore_valid = false, // set to true when damaged
.flags = WIN_FLAGS_IMAGES_NONE, // updated by property/attributes/etc
.flags = WIN_FLAGS_IMAGES_NONE, // updated by
// property/attributes/etc
// change
.stale_props = NULL,
.stale_props_capacity = 0,
@ -1559,8 +1592,9 @@ struct win *fill_win(session_t *ps, struct win *w) {
auto duplicated_win = find_managed_win(ps, w->id);
if (duplicated_win) {
log_debug("Window %#010x (recorded name: %s) added multiple times", w->id,
duplicated_win->name);
log_debug("Window %#010x (recorded name: %s) added multiple "
"times",
w->id, duplicated_win->name);
return &duplicated_win->base;
}
@ -1572,14 +1606,15 @@ struct win *fill_win(session_t *ps, struct win *w) {
// Failed to get window attributes or geometry probably means
// the window is gone already. Unviewable means the window is
// already reparented elsewhere.
// BTW, we don't care about Input Only windows, except for stacking
// proposes, so we need to keep track of them still.
// BTW, we don't care about Input Only windows, except for
// stacking proposes, so we need to keep track of them still.
free(a);
return w;
}
if (a->_class == XCB_WINDOW_CLASS_INPUT_ONLY) {
// No need to manage this window, but we still keep it on the window stack
// No need to manage this window, but we still keep it on the
// window stack
w->managed = false;
free(a);
return w;
@ -1649,8 +1684,8 @@ struct win *fill_win(session_t *ps, struct win *w) {
assert(replaced == w);
free(w);
// Set all the stale flags on this new window, so it's properties will get updated
// when it's mapped
// Set all the stale flags on this new window, so it's properties will get
// updated when it's mapped
win_set_flags(new, WIN_FLAGS_CLIENT_STALE | WIN_FLAGS_SIZE_STALE |
WIN_FLAGS_POSITION_STALE | WIN_FLAGS_PROPERTY_STALE |
WIN_FLAGS_FACTOR_CHANGED);
@ -1686,8 +1721,8 @@ static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_wind
// gets mapped before parent, or when the window is a waypoint
clear_cache_win_leaders(ps);
// Update the old and new window group and active_leader if the window
// could affect their state.
// Update the old and new window group and active_leader if the
// window could affect their state.
xcb_window_t cache_leader = win_get_leader(ps, w);
if (win_is_focused_raw(ps, w) && cache_leader_old != cache_leader) {
ps->active_leader = cache_leader;
@ -1734,7 +1769,8 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int
if (!(w->cache_leader = w->leader))
w->cache_leader = w->client_win;
// If the leader of this window isn't itself, look for its ancestors
// If the leader of this window isn't itself, look for its
// ancestors
if (w->cache_leader && w->cache_leader != w->client_win) {
auto wp = find_toplevel(ps, w->cache_leader);
if (wp) {
@ -1882,7 +1918,8 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
w->bounding_shaped = win_bounding_shaped(ps, w->base.id);
}
// We don't handle property updates of non-visible windows until they are mapped.
// We don't handle property updates of non-visible windows until they are
// mapped.
assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING &&
w->state != WSTATE_UNMAPPING);
@ -1923,8 +1960,8 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
// We think the top left of the border is the origin
pixman_region32_translate(&br, w->g.border_width, w->g.border_width);
// Intersect the bounding region we got with the window rectangle, to
// make sure the bounding region is not bigger than the window
// Intersect the bounding region we got with the window rectangle,
// to make sure the bounding region is not bigger than the window
// rectangle
pixman_region32_intersect(&w->bounding_shape, &w->bounding_shape, &br);
pixman_region32_fini(&br);
@ -1981,7 +2018,8 @@ void win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t
for (int i = 0; i < 4; i++) {
if (prop.c32[i] > (uint32_t)INT_MAX) {
log_warn("Your window manager sets a absurd "
"_NET_FRAME_EXTENTS value (%u), ignoring it.",
"_NET_FRAME_EXTENTS value (%u), "
"ignoring it.",
prop.c32[i]);
memset(extents, 0, sizeof(extents));
break;
@ -2081,11 +2119,12 @@ static void destroy_win_finish(session_t *ps, struct win *w) {
auto mw = (struct managed_win *)w;
if (mw->state != WSTATE_UNMAPPED) {
// Only UNMAPPED state has window resources freed, otherwise
// we need to call unmap_win_finish to free them.
// XXX actually we unmap_win_finish only frees the rendering
// resources, we still need to call free_win_res. will fix
// later.
// Only UNMAPPED state has window resources freed,
// otherwise we need to call unmap_win_finish to free
// them.
// XXX actually we unmap_win_finish only frees the
// rendering resources, we still need to call free_win_res.
// will fix later.
unmap_win_finish(ps, mw);
}
@ -2108,11 +2147,12 @@ static void destroy_win_finish(session_t *ps, struct win *w) {
}
if (mw == ps->active_win) {
// Usually, the window cannot be the focused at destruction.
// FocusOut should be generated before the window is destroyed. We
// do this check just to be completely sure we don't have dangling
// references.
log_debug("window %#010x (%s) is destroyed while being focused",
// Usually, the window cannot be the focused at
// destruction. FocusOut should be generated before the
// window is destroyed. We do this check just to be
// completely sure we don't have dangling references.
log_debug("window %#010x (%s) is destroyed while being "
"focused",
w->id, mw->name);
ps->active_win = NULL;
}
@ -2145,11 +2185,13 @@ static inline void restack_win(session_t *ps, struct win *w, struct list_node *n
}
if (mw) {
// This invalidates all reg_ignore below the new stack position of `w`
// This invalidates all reg_ignore below the new stack position of
// `w`
mw->reg_ignore_valid = false;
rc_region_unref(&mw->reg_ignore);
// This invalidates all reg_ignore below the old stack position of `w`
// This invalidates all reg_ignore below the old stack position of
// `w`
auto next_w = win_stack_find_next_managed(ps, &w->stack_neighbour);
if (next_w) {
next_w->reg_ignore_valid = false;
@ -2231,39 +2273,42 @@ bool destroy_win_start(session_t *ps, struct win *w) {
log_debug("Destroying %#010x \"%s\", managed = %d", w->id,
(w->managed ? mw->name : NULL), w->managed);
// Delete destroyed window from the hash table, even though the window might still
// be rendered for a while. We need to make sure future window with the same
// window id won't confuse us. Keep the window in the window stack if it's managed
// and mapped, since we might still need to render it (e.g. fading out). Window
// will be removed from the stack when it finishes destroying.
// Delete destroyed window from the hash table, even though the window
// might still be rendered for a while. We need to make sure future window
// with the same window id won't confuse us. Keep the window in the window
// stack if it's managed and mapped, since we might still need to render
// it (e.g. fading out). Window will be removed from the stack when it
// finishes destroying.
HASH_DEL(ps->windows, w);
if (!w->managed || mw->state == WSTATE_UNMAPPED) {
// Window is already unmapped, or is an unmanged window, just destroy it
// Window is already unmapped, or is an unmanged window, just
// destroy it
destroy_win_finish(ps, w);
return true;
}
if (w->managed) {
// Clear IMAGES_STALE flags since the window is destroyed: Clear
// PIXMAP_STALE as there is no pixmap available anymore, so STALE doesn't
// make sense.
// XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed window
// doesn't work leading to an inconsistent state where the shadow is
// refreshed but the flags are stuck in STALE.
// Do this before changing the window state to destroying
// PIXMAP_STALE as there is no pixmap available anymore, so STALE
// doesn't make sense.
// XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed
// window doesn't work leading to an inconsistent state where the
// shadow is refreshed but the flags are stuck in STALE. Do this
// before changing the window state to destroying
win_clear_flags(mw, WIN_FLAGS_IMAGES_STALE);
// If size/shape/position information is stale, win_process_update_flags
// will update them and add the new window extents to damage. Since the
// window has been destroyed, we cannot get the complete information at
// this point, so we just add what we currently have to the damage.
// If size/shape/position information is stale,
// win_process_update_flags will update them and add the new
// window extents to damage. Since the window has been destroyed,
// we cannot get the complete information at this point, so we
// just add what we currently have to the damage.
if (win_check_flags_any(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) {
add_damage_from_win(ps, mw);
}
// Clear some flags about stale window information. Because now the window
// is destroyed, we can't update them anyway.
// Clear some flags about stale window information. Because now
// the window is destroyed, we can't update them anyway.
win_clear_flags(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE |
WIN_FLAGS_PROPERTY_STALE |
WIN_FLAGS_FACTOR_CHANGED | WIN_FLAGS_CLIENT_STALE);
@ -2310,7 +2355,8 @@ void unmap_win_start(session_t *ps, struct managed_win *w) {
// Clear the pending map as this window is now unmapped
win_clear_flags(w, WIN_FLAGS_MAPPED);
} else {
log_warn("Trying to unmapping an already unmapped window %#010x "
log_warn("Trying to unmapping an already unmapped window "
"%#010x "
"\"%s\"",
w->base.id, w->name);
assert(false);
@ -2334,10 +2380,10 @@ void unmap_win_start(session_t *ps, struct managed_win *w) {
#endif
if (!ps->redirected || !was_damaged) {
// If we are not redirected, we skip fading because we aren't rendering
// anything anyway.
// If the window wasn't ever damaged, it shouldn't be painted either. But
// a fading out window is always painted, so we have to skip fading here.
// If we are not redirected, we skip fading because we aren't
// rendering anything anyway. If the window wasn't ever damaged,
// it shouldn't be painted either. But a fading out window is
// always painted, so we have to skip fading here.
CHECK(!win_skip_fading(ps, w));
}
}
@ -2396,14 +2442,16 @@ void win_update_screen(int nscreens, region_t *screens, struct managed_win *w) {
if (e->x1 <= w->g.x && e->y1 <= w->g.y && e->x2 >= w->g.x + w->widthb &&
e->y2 >= w->g.y + w->heightb) {
w->xinerama_scr = i;
log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen %d "
log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen "
"%d "
"(%dx%d+%dx%d)",
w->base.id, w->name, w->g.x, w->g.y, w->widthb, w->heightb,
i, e->x1, e->y1, e->x2 - e->x1, e->y2 - e->y1);
return;
}
}
log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any screen",
log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any "
"screen",
w->base.id, w->name, w->g.x, w->g.y, w->g.width, w->g.height);
}
@ -2428,23 +2476,24 @@ void map_win_start(session_t *ps, struct managed_win *w) {
if (w->state == WSTATE_UNMAPPING) {
CHECK(!win_skip_fading(ps, w));
// We skipped the unmapping process, the window was rendered, now it is
// not anymore. So we need to mark the then unmapping window as damaged.
// We skipped the unmapping process, the window was rendered, now
// it is not anymore. So we need to mark the then unmapping window
// as damaged.
//
// Solves problem when, for example, a window is unmapped then mapped in a
// different location
// Solves problem when, for example, a window is unmapped then
// mapped in a different location
add_damage_from_win(ps, w);
assert(w);
}
assert(w->state == WSTATE_UNMAPPED);
// Rant: window size could change after we queried its geometry here and before
// we get its pixmap. Later, when we get back to the event processing loop, we
// will get the notification about size change from Xserver and try to refresh the
// pixmap, while the pixmap is actually already up-to-date (i.e. the notification
// is stale). There is basically no real way to prevent this, aside from grabbing
// the server.
// Rant: window size could change after we queried its geometry here and
// before we get its pixmap. Later, when we get back to the event
// processing loop, we will get the notification about size change from
// Xserver and try to refresh the pixmap, while the pixmap is actually
// already up-to-date (i.e. the notification is stale). There is basically
// no real way to prevent this, aside from grabbing the server.
// XXX Can we assume map_state is always viewable?
w->a.map_state = XCB_MAP_STATE_VIEWABLE;
@ -2464,9 +2513,9 @@ void map_win_start(session_t *ps, struct managed_win *w) {
w->opacity, w->opacity_target);
// Cannot set w->ever_damaged = false here, since window mapping could be
// delayed, so a damage event might have already arrived before this function
// is called. But this should be unnecessary in the first place, since
// ever_damaged is set to false in unmap_win_finish anyway.
// delayed, so a damage event might have already arrived before this
// function is called. But this should be unnecessary in the first place,
// since ever_damaged is set to false in unmap_win_finish anyway.
// Sets the WIN_FLAGS_IMAGES_STALE flag so later in the critical section
// the window's image will be bound
@ -2601,17 +2650,17 @@ struct managed_win *find_toplevel(session_t *ps, xcb_window_t id) {
* @return struct _win object of the found window, NULL if not found
*/
struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wid) {
// TODO(yshui) this should probably be an "update tree", then find_toplevel.
// current approach is a bit more "racy", as the server state might be ahead of
// our state
// TODO(yshui) this should probably be an "update tree", then
// find_toplevel. current approach is a bit more "racy", as the server
// state might be ahead of our state
struct win *w = NULL;
// We traverse through its ancestors to find out the frame
// Using find_win here because if we found a unmanaged window we know about, we
// can stop early.
// Using find_win here because if we found a unmanaged window we know
// about, we can stop early.
while (wid && wid != ps->root && !(w = find_win(ps, wid))) {
// xcb_query_tree probably fails if you run picom when X is somehow
// initializing (like add it in .xinitrc). In this case
// xcb_query_tree probably fails if you run picom when X is
// somehow initializing (like add it in .xinitrc). In this case
// just leave it alone.
auto reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, wid), NULL);
if (reply == NULL) {
@ -2777,7 +2826,8 @@ bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *
}
/**
* Check if a window is focused, without using any focus rules or forced focus settings
* Check if a window is focused, without using any focus rules or forced focus
* settings
*/
bool win_is_focused_raw(const session_t *ps, const struct managed_win *w) {
return w->a.map_state == XCB_MAP_STATE_VIEWABLE && ps->active_win == w;

View File

@ -7,6 +7,8 @@
#include <xcb/render.h>
#include <xcb/xcb.h>
#include <backend/backend.h>
#include "uthash_extra.h"
// FIXME shouldn't need this
@ -287,9 +289,10 @@ struct managed_win {
/// section
void win_process_update_flags(session_t *ps, struct managed_win *w);
void win_process_image_flags(session_t *ps, struct managed_win *w);
bool win_bind_mask(struct backend_base *b, struct managed_win *w);
/// Bind a shadow to the window, with color `c` and shadow kernel `kernel`
bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c,
struct conv *kernel);
struct backend_shadow_context *kernel);
/// Start the unmap of a window. We cannot unmap immediately since we might need to fade
/// the window out.