mirror of https://github.com/yshui/picom.git
Merge pull request #882 from yshui/shadow
This commit is contained in:
commit
f2970bc697
|
@ -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(®_bound_local);
|
||||
pixman_region32_copy(®_bound_local, ®_bound);
|
||||
pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y);
|
||||
|
||||
if (!w->mask_image) {
|
||||
// TODO(yshui) only allocate a mask if the window is shaped or has
|
||||
// rounded corners.
|
||||
w->mask_image = ps->backend_data->ops->make_mask(
|
||||
ps->backend_data,
|
||||
(geometry_t){.width = w->g.width, .height = w->g.height},
|
||||
®_bound_local);
|
||||
ps->backend_data->ops->set_image_property(
|
||||
ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS, w->mask_image,
|
||||
(double[]){w->corner_radius});
|
||||
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(®_bound_local);
|
||||
pixman_region32_copy(®_bound_local, ®_bound);
|
||||
pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y);
|
||||
|
||||
pixman_region32_init(®_visible_local);
|
||||
pixman_region32_intersect(®_visible_local,
|
||||
|
@ -483,10 +475,10 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
|
|||
®_paint_in_bound, ®_visible);
|
||||
ps->backend_data->ops->release_image(ps->backend_data, new_img);
|
||||
pixman_region32_fini(®_visible_local);
|
||||
pixman_region32_fini(®_bound_local);
|
||||
}
|
||||
skip:
|
||||
pixman_region32_fini(®_bound);
|
||||
pixman_region32_fini(®_bound_local);
|
||||
pixman_region32_fini(®_bound_no_corner);
|
||||
pixman_region32_fini(®_paint_in_bound);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(®, 0, 0, (unsigned int)width, (unsigned int)height);
|
||||
void *mask = backend_data->ops->make_mask(
|
||||
backend_data, (geometry_t){.width = width, .height = height}, ®);
|
||||
pixman_region32_fini(®);
|
||||
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(®_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(®_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(®_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
|
@ -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[];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
19
src/kernel.c
19
src/kernel.c
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
29
src/picom.c
29
src/picom.c
|
@ -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
|
||||
|
|
|
@ -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
322
src/win.c
|
@ -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(®_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}, ®_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;
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue