From 84407099a99d44d0fc40ffa5525b9bf7bde37cfa Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 25 Aug 2022 05:21:19 +0100 Subject: [PATCH] backend: give backends more flexibility regarding shadow creation Signed-off-by: Yuxuan Shui --- src/backend/backend.h | 18 +++++++++++++++++- src/backend/backend_common.c | 27 ++++++++++++++++++++------- src/backend/backend_common.h | 10 +++++++--- src/backend/dummy/dummy.c | 2 ++ src/backend/gl/glx.c | 2 ++ src/backend/xrender/xrender.c | 2 ++ src/common.h | 4 ++-- src/kernel.c | 17 +++++++++++------ src/kernel.h | 2 +- src/picom.c | 29 +++++++++++++++++++++++++---- src/render.c | 8 +++++--- src/win.c | 7 +++---- src/win.h | 4 +++- 13 files changed, 100 insertions(+), 32 deletions(-) diff --git a/src/backend/backend.h b/src/backend/backend.h index b11ca65b..e0a51ca3 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -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,24 @@ struct backend_operations { void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned); + struct backend_shadow_context *(*create_shadow_context)(backend_t *backend_data, + double radius); + void (*destroy_shadow_context)(backend_t *backend_data, + struct backend_shadow_context *ctx); + /// Create a shadow image based on the parameters /// 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. + /// + /// 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 diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index 165a9dd4..eba5cb47 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -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,19 @@ default_backend_render_shadow(backend_t *backend_data, int width, int height, return ret; } +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); diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h index cf77ce8b..60286d1d 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -62,9 +62,13 @@ 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, - const conv *kernel, double r, double g, double b, double a); +void *default_backend_render_shadow(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); diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index c7e0e2cf..7e06face 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -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, diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index e0cc368f..26aaa49b 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -536,6 +536,8 @@ struct backend_operations glx_ops = { .is_image_transparent = default_is_image_transparent, .present = glx_present, .buffer_age = glx_buffer_age, + .create_shadow_context = default_create_shadow_context, + .destroy_shadow_context = default_destroy_shadow_context, .render_shadow = default_backend_render_shadow, .make_mask = gl_make_mask, .fill = gl_fill, diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index 08e804f1..2b7f8e17 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -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, diff --git a/src/common.h b/src/common.h index a68565b1..c06a30c2 100644 --- a/src/common.h +++ b/src/common.h @@ -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; diff --git a/src/kernel.c b/src/kernel.c index 51510452..547e25bc 100644 --- a/src/kernel.c +++ b/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,9 +126,9 @@ 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); diff --git a/src/kernel.h b/src/kernel.h index 251d127d..85997de3 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -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 diff --git a/src/picom.c b/src/picom.c index ce4f0d22..e1c1f1e7 100644 --- a/src/picom.c +++ b/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 diff --git a/src/render.c b/src/render.c index ac9b40ec..db627de3 100644 --- a/src/render.c +++ b/src/render.c @@ -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); diff --git a/src/win.c b/src/win.c index d579c69c..3f376a77 100644 --- a/src/win.c +++ b/src/win.c @@ -346,11 +346,10 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w } 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); + w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, sctx, c); if (!w->shadow_image) { log_error("Failed to bind shadow image, shadow will be disabled for " "%#010x (%s)", @@ -556,7 +555,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); } } diff --git a/src/win.h b/src/win.h index ad5fe2fc..fb5137c0 100644 --- a/src/win.h +++ b/src/win.h @@ -7,6 +7,8 @@ #include #include +#include + #include "uthash_extra.h" // FIXME shouldn't need this @@ -289,7 +291,7 @@ void win_process_update_flags(session_t *ps, struct managed_win *w); void win_process_image_flags(session_t *ps, 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.