diff options
| author | allusive-dev <[email protected]> | 2023-09-19 17:47:33 +1000 |
|---|---|---|
| committer | allusive-dev <[email protected]> | 2023-09-19 17:47:33 +1000 |
| commit | a93aba600b1c5d019b680b9f4ff3fa85d5d43a60 (patch) | |
| tree | 77f8152222655657472a70e0bfa413a0495dd555 /src/backend/backend_common.c | |
| parent | reset (diff) | |
| download | compfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.tar.xz compfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.zip | |
Fixed broken files/code and other errors
Diffstat (limited to 'src/backend/backend_common.c')
| -rw-r--r-- | src/backend/backend_common.c | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c new file mode 100644 index 0000000..c0377d3 --- /dev/null +++ b/src/backend/backend_common.c @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui <[email protected]> +#include <math.h> +#include <string.h> +#include <xcb/render.h> +#include <xcb/xcb_image.h> +#include <xcb/xcb_renderutil.h> + +#include "backend/backend.h" +#include "backend/backend_common.h" +#include "common.h" +#include "config.h" +#include "kernel.h" +#include "log.h" +#include "utils.h" +#include "win.h" +#include "x.h" + +/** + * Generate a 1x1 <code>Picture</code> of a particular color. + */ +xcb_render_picture_t solid_picture(xcb_connection_t *c, xcb_drawable_t d, bool argb, + double a, double r, double g, double b) { + xcb_pixmap_t pixmap; + xcb_render_picture_t picture; + xcb_render_create_picture_value_list_t pa; + xcb_render_color_t col; + xcb_rectangle_t rect; + + pixmap = x_create_pixmap(c, argb ? 32 : 8, d, 1, 1); + if (!pixmap) + return XCB_NONE; + + pa.repeat = 1; + picture = x_create_picture_with_standard_and_pixmap( + c, argb ? XCB_PICT_STANDARD_ARGB_32 : XCB_PICT_STANDARD_A_8, pixmap, + XCB_RENDER_CP_REPEAT, &pa); + + if (!picture) { + xcb_free_pixmap(c, pixmap); + return XCB_NONE; + } + + col.alpha = (uint16_t)(a * 0xffff); + col.red = (uint16_t)(r * 0xffff); + col.green = (uint16_t)(g * 0xffff); + col.blue = (uint16_t)(b * 0xffff); + + rect.x = 0; + rect.y = 0; + rect.width = 1; + rect.height = 1; + + xcb_render_fill_rectangles(c, XCB_RENDER_PICT_OP_SRC, picture, col, 1, &rect); + xcb_free_pixmap(c, pixmap); + + return picture; +} + +xcb_image_t * +make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height) { + /* + * We classify shadows into 4 kinds of regions + * r = shadow radius + * (0, 0) is the top left of the window itself + * -r r width-r width+r + * -r +-----+---------+-----+ + * | 1 | 2 | 1 | + * r +-----+---------+-----+ + * | 2 | 3 | 2 | + * height-r +-----+---------+-----+ + * | 1 | 2 | 1 | + * height+r +-----+---------+-----+ + */ + xcb_image_t *ximage; + const double *shadow_sum = kernel->rsum; + assert(shadow_sum); + // We only support square kernels for shadow + assert(kernel->w == kernel->h); + int d = kernel->w; + int r = d / 2; + int swidth = width + r * 2, sheight = height + r * 2; + + assert(d % 2 == 1); + assert(d > 0); + + ximage = xcb_image_create_native(c, to_u16_checked(swidth), to_u16_checked(sheight), + XCB_IMAGE_FORMAT_Z_PIXMAP, 8, 0, 0, NULL); + if (!ximage) { + log_error("failed to create an X image"); + return 0; + } + + unsigned char *data = ximage->data; + long sstride = ximage->stride; + + // If the window body is smaller than the kernel, we do convolution directly + if (width < r * 2 && height < r * 2) { + for (int y = 0; y < sheight; y++) { + for (int x = 0; x < swidth; x++) { + double sum = sum_kernel_normalized( + kernel, d - x - 1, d - y - 1, width, height); + data[y * sstride + x] = (uint8_t)(sum * 255.0); + } + } + return ximage; + } + + if (height < r * 2) { + // Implies width >= r * 2 + // If the window height is smaller than the kernel, we divide + // the window like this: + // -r r width-r width+r + // +------+-------------+------+ + // | | | | + // +------+-------------+------+ + for (int y = 0; y < sheight; y++) { + for (int x = 0; x < r * 2; x++) { + double sum = sum_kernel_normalized(kernel, d - x - 1, + d - y - 1, d, height) * + 255.0; + data[y * sstride + x] = (uint8_t)sum; + data[y * sstride + swidth - x - 1] = (uint8_t)sum; + } + } + for (int y = 0; y < sheight; y++) { + double sum = + sum_kernel_normalized(kernel, 0, d - y - 1, d, height) * 255.0; + memset(&data[y * sstride + r * 2], (uint8_t)sum, + (size_t)(width - 2 * r)); + } + return ximage; + } + if (width < r * 2) { + // Similarly, for width smaller than kernel + for (int y = 0; y < r * 2; y++) { + for (int x = 0; x < swidth; x++) { + double sum = sum_kernel_normalized(kernel, d - x - 1, + d - y - 1, width, d) * + 255.0; + data[y * sstride + x] = (uint8_t)sum; + data[(sheight - y - 1) * sstride + x] = (uint8_t)sum; + } + } + for (int x = 0; x < swidth; x++) { + double sum = + sum_kernel_normalized(kernel, d - x - 1, 0, width, d) * 255.0; + for (int y = r * 2; y < height; y++) { + data[y * sstride + x] = (uint8_t)sum; + } + } + return ximage; + } + + // Implies: width >= r * 2 && height >= r * 2 + + // Fill part 3 + for (int y = r; y < height + r; y++) { + memset(data + sstride * y + r, (uint8_t)(255 * opacity), (size_t)width); + } + + // Part 1 + for (int y = 0; y < r * 2; y++) { + for (int x = 0; x < r * 2; x++) { + double tmpsum = shadow_sum[y * d + x] * opacity * 255.0; + data[y * sstride + x] = (uint8_t)tmpsum; + data[(sheight - y - 1) * sstride + x] = (uint8_t)tmpsum; + data[(sheight - y - 1) * sstride + (swidth - x - 1)] = (uint8_t)tmpsum; + data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum; + } + } + + // Part 2, top/bottom + for (int y = 0; y < r * 2; y++) { + double tmpsum = shadow_sum[d * y + d - 1] * opacity * 255.0; + memset(&data[y * sstride + r * 2], (uint8_t)tmpsum, (size_t)(width - r * 2)); + memset(&data[(sheight - y - 1) * sstride + r * 2], (uint8_t)tmpsum, + (size_t)(width - r * 2)); + } + + // Part 2, left/right + for (int x = 0; x < r * 2; x++) { + double tmpsum = shadow_sum[d * (d - 1) + x] * opacity * 255.0; + for (int y = r * 2; y < height; y++) { + data[y * sstride + x] = (uint8_t)tmpsum; + data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum; + } + } + + return ximage; +} + +/** + * Generate shadow <code>Picture</code> for a window. + */ +bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const int width, + const int height, const conv *kernel, xcb_render_picture_t shadow_pixel, + xcb_pixmap_t *pixmap, xcb_render_picture_t *pict) { + xcb_image_t *shadow_image = NULL; + xcb_pixmap_t shadow_pixmap = XCB_NONE, shadow_pixmap_argb = XCB_NONE; + xcb_render_picture_t shadow_picture = XCB_NONE, shadow_picture_argb = XCB_NONE; + xcb_gcontext_t gc = XCB_NONE; + + shadow_image = make_shadow(c, kernel, opacity, width, height); + if (!shadow_image) { + log_error("Failed to make shadow"); + return false; + } + + shadow_pixmap = x_create_pixmap(c, 8, d, shadow_image->width, shadow_image->height); + shadow_pixmap_argb = + x_create_pixmap(c, 32, d, shadow_image->width, shadow_image->height); + + if (!shadow_pixmap || !shadow_pixmap_argb) { + log_error("Failed to create shadow pixmaps"); + goto shadow_picture_err; + } + + shadow_picture = x_create_picture_with_standard_and_pixmap( + c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL); + shadow_picture_argb = x_create_picture_with_standard_and_pixmap( + c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL); + if (!shadow_picture || !shadow_picture_argb) { + goto shadow_picture_err; + } + + gc = x_new_id(c); + xcb_create_gc(c, gc, shadow_pixmap, 0, NULL); + + // We need to make room for protocol metadata in the request. The metadata should + // be 24 bytes plus padding, let's be generous and give it 1kb + auto maximum_image_size = xcb_get_maximum_request_length(c) * 4 - 1024; + auto maximum_row = + to_u16_checked(clamp(maximum_image_size / shadow_image->stride, 0, UINT16_MAX)); + if (maximum_row <= 0) { + // TODO(yshui) Upload image with XShm + log_error("X server request size limit is too restrictive, or the shadow " + "image is too wide for us to send a single row of the shadow " + "image. Shadow size: %dx%d", + width, height); + goto shadow_picture_err; + } + + for (uint32_t row = 0; row < shadow_image->height; row += maximum_row) { + auto batch_height = maximum_row; + if (batch_height > shadow_image->height - row) { + batch_height = to_u16_checked(shadow_image->height - row); + } + + uint32_t offset = row * shadow_image->stride / sizeof(*shadow_image->data); + xcb_put_image(c, (uint8_t)shadow_image->format, shadow_pixmap, gc, + shadow_image->width, batch_height, 0, to_i16_checked(row), + 0, shadow_image->depth, shadow_image->stride * batch_height, + shadow_image->data + offset); + } + + xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, shadow_pixel, shadow_picture, + shadow_picture_argb, 0, 0, 0, 0, 0, 0, shadow_image->width, + shadow_image->height); + + *pixmap = shadow_pixmap_argb; + *pict = shadow_picture_argb; + + xcb_free_gc(c, gc); + xcb_image_destroy(shadow_image); + xcb_free_pixmap(c, shadow_pixmap); + xcb_render_free_picture(c, shadow_picture); + + return true; + +shadow_picture_err: + if (shadow_image) { + xcb_image_destroy(shadow_image); + } + if (shadow_pixmap) { + xcb_free_pixmap(c, shadow_pixmap); + } + if (shadow_pixmap_argb) { + xcb_free_pixmap(c, shadow_pixmap_argb); + } + if (shadow_picture) { + xcb_render_free_picture(c, shadow_picture); + } + if (shadow_picture_argb) { + xcb_render_free_picture(c, shadow_picture_argb); + } + if (gc) { + xcb_free_gc(c, gc); + } + + 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), + 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)) { + return NULL; + } + + auto visual = x_get_visual_for_standard(backend_data->c, XCB_PICT_STANDARD_ARGB_32); + void *ret = backend_data->ops->bind_pixmap( + backend_data, shadow, x_get_visual_info(backend_data->c, visual), true); + xcb_render_free_picture(backend_data->c, pict); + return ret; +} + +static struct conv **generate_box_blur_kernel(struct box_blur_args *args, int *kernel_count) { + int r = args->size * 2 + 1; + assert(r > 0); + auto ret = ccalloc(2, struct conv *); + ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); + ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); + ret[0]->w = r; + ret[0]->h = 1; + ret[1]->w = 1; + ret[1]->h = r; + for (int i = 0; i < r; i++) { + ret[0]->data[i] = 1; + ret[1]->data[i] = 1; + } + *kernel_count = 2; + return ret; +} + +static struct conv ** +generate_gaussian_blur_kernel(struct gaussian_blur_args *args, int *kernel_count) { + int r = args->size * 2 + 1; + assert(r > 0); + auto ret = ccalloc(2, struct conv *); + ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); + ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); + ret[0]->w = r; + ret[0]->h = 1; + ret[1]->w = 1; + ret[1]->h = r; + for (int i = 0; i <= args->size; i++) { + ret[0]->data[i] = ret[0]->data[r - i - 1] = + 1.0 / (sqrt(2.0 * M_PI) * args->deviation) * + exp(-(args->size - i) * (args->size - i) / + (2 * args->deviation * args->deviation)); + ret[1]->data[i] = ret[1]->data[r - i - 1] = ret[0]->data[i]; + } + *kernel_count = 2; + return ret; +} + +/// Generate blur kernels for gaussian and box blur methods. Generated kernel is not +/// normalized, and the center element will always be 1. +struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count) { + switch (method) { + case BLUR_METHOD_BOX: return generate_box_blur_kernel(args, kernel_count); + case BLUR_METHOD_GAUSSIAN: + return generate_gaussian_blur_kernel(args, kernel_count); + default: break; + } + return NULL; +} + +/// Generate kernel parameters for dual-kawase blur method. Falls back on approximating +/// standard gauss radius if strength is zero or below. +struct dual_kawase_params *generate_dual_kawase_params(void *args) { + struct dual_kawase_blur_args *blur_args = args; + static const struct { + int iterations; /// Number of down- and upsample iterations + float offset; /// Sample offset in half-pixels + int min_radius; /// Approximate gauss-blur with at least this + /// radius and std-deviation + } strength_levels[20] = { + {.iterations = 1, .offset = 1.25f, .min_radius = 1}, // LVL 1 + {.iterations = 1, .offset = 2.25f, .min_radius = 6}, // LVL 2 + {.iterations = 2, .offset = 2.00f, .min_radius = 11}, // LVL 3 + {.iterations = 2, .offset = 3.00f, .min_radius = 17}, // LVL 4 + {.iterations = 2, .offset = 4.25f, .min_radius = 24}, // LVL 5 + {.iterations = 3, .offset = 2.50f, .min_radius = 32}, // LVL 6 + {.iterations = 3, .offset = 3.25f, .min_radius = 40}, // LVL 7 + {.iterations = 3, .offset = 4.25f, .min_radius = 51}, // LVL 8 + {.iterations = 3, .offset = 5.50f, .min_radius = 67}, // LVL 9 + {.iterations = 4, .offset = 3.25f, .min_radius = 83}, // LVL 10 + {.iterations = 4, .offset = 4.00f, .min_radius = 101}, // LVL 11 + {.iterations = 4, .offset = 5.00f, .min_radius = 123}, // LVL 12 + {.iterations = 4, .offset = 6.00f, .min_radius = 148}, // LVL 13 + {.iterations = 4, .offset = 7.25f, .min_radius = 178}, // LVL 14 + {.iterations = 4, .offset = 8.25f, .min_radius = 208}, // LVL 15 + {.iterations = 5, .offset = 4.50f, .min_radius = 236}, // LVL 16 + {.iterations = 5, .offset = 5.25f, .min_radius = 269}, // LVL 17 + {.iterations = 5, .offset = 6.25f, .min_radius = 309}, // LVL 18 + {.iterations = 5, .offset = 7.25f, .min_radius = 357}, // LVL 19 + {.iterations = 5, .offset = 8.50f, .min_radius = 417}, // LVL 20 + }; + + auto params = ccalloc(1, struct dual_kawase_params); + params->iterations = 0; + params->offset = 1.0f; + + if (blur_args->strength <= 0 && blur_args->size) { + // find highest level that approximates blur-strength with the selected + // gaussian blur-radius + int lvl = 1; + while (strength_levels[lvl - 1].min_radius < blur_args->size && lvl < 20) { + ++lvl; + } + blur_args->strength = lvl; + } + if (blur_args->strength <= 0) { + // default value + blur_args->strength = 5; + } + + assert(blur_args->strength > 0 && blur_args->strength <= 20); + params->iterations = strength_levels[blur_args->strength - 1].iterations; + params->offset = strength_levels[blur_args->strength - 1].offset; + + // Expand sample area to cover the smallest texture / highest selected iteration: + // - Smallest texture dimensions are halved `iterations`-times + // - Upsample needs pixels two-times `offset` away from the border + // - Plus one for interpolation differences + params->expand = (1 << params->iterations) * 2 * (int)ceil(params->offset) + 1; + + return params; +} + +void *default_clone_image(backend_t *base attr_unused, const void *image_data, + const region_t *reg_visible attr_unused) { + auto new_img = ccalloc(1, struct backend_image); + *new_img = *(struct backend_image *)image_data; + new_img->inner->refcount++; + return new_img; +} + +bool default_set_image_property(backend_t *base attr_unused, enum image_properties op, + void *image_data, void *arg) { + struct backend_image *tex = image_data; + int *iargs = arg; + bool *bargs = arg; + double *dargs = arg; + switch (op) { + case IMAGE_PROPERTY_INVERTED: tex->color_inverted = bargs[0]; break; + case IMAGE_PROPERTY_DIM_LEVEL: tex->dim = dargs[0]; break; + case IMAGE_PROPERTY_OPACITY: tex->opacity = dargs[0]; break; + case IMAGE_PROPERTY_EFFECTIVE_SIZE: + // texture is already set to repeat, so nothing else we need to do + tex->ewidth = iargs[0]; + tex->eheight = iargs[1]; + break; + case IMAGE_PROPERTY_MAX_BRIGHTNESS: tex->max_brightness = dargs[0]; break; + } + + return true; +} + +bool default_is_image_transparent(backend_t *base attr_unused, void *image_data) { + struct backend_image *img = image_data; + return img->opacity < 1 || img->inner->has_alpha; +} + +struct backend_image *default_new_backend_image(int w, int h) { + auto ret = ccalloc(1, struct backend_image); + ret->opacity = 1; + ret->dim = 0; + ret->max_brightness = 1; + ret->eheight = h; + ret->ewidth = w; + ret->color_inverted = false; + return ret; +} + +void init_backend_base(struct backend_base *base, session_t *ps) { + base->c = ps->c; + base->loop = ps->loop; + base->root = ps->root; + base->busy = false; + base->ops = NULL; +} |