backend: give backends more flexibility regarding shadow creation

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2022-08-25 05:21:19 +01:00
parent e942f253f7
commit 84407099a9
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
13 changed files with 100 additions and 32 deletions

View File

@ -16,6 +16,8 @@
typedef struct session session_t;
struct managed_win;
struct backend_shadow_context;
struct ev_loop;
struct backend_operations;
@ -213,10 +215,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

View File

@ -291,16 +291,16 @@ shadow_picture_err:
return false;
}
void *
default_backend_render_shadow(backend_t *backend_data, int width, int height,
const conv *kernel, double r, double g, double b, double a) {
xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root,
true, 1, r, g, b),
void *default_backend_render_shadow(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color) {
const conv *kernel = (void *)sctx;
xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root, true,
1, color.red, color.green, color.blue),
shadow = XCB_NONE;
xcb_render_picture_t pict = XCB_NONE;
if (!build_shadow(backend_data->c, backend_data->root, a, width, height, kernel,
shadow_pixel, &shadow, &pict)) {
if (!build_shadow(backend_data->c, backend_data->root, color.alpha, width, height,
kernel, shadow_pixel, &shadow, &pict)) {
return NULL;
}
@ -311,6 +311,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);

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,8 @@
#include <xcb/render.h>
#include <xcb/xcb.h>
#include <backend/backend.h>
#include "uthash_extra.h"
// FIXME shouldn't need this
@ -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.