backend: split image_op into image_op and set_image_property

Currently there is some inconsistency in how image_op is implemented
across backends. The glx backend applies some of the image operations
lazily, and not always in the order the operations were made; while the
xrender backend applies the operations eagerly. This can lead to
different render result in some cases.

Instead of trying to preserving the order of operations, which would be
unnecessary, we re-model the API to better reflect the implementation.
We make it clear that setting the property doesn't change the image
data, and properties are only applied during composition and in a
specific order.

This makes sure the render result looks consistent across backends.
Should also improve the performance of the xrender backend, even if only
slightly.

Also distill out the property management code so they can be shared.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2021-06-14 03:22:13 +01:00
parent 21dfe20794
commit 2a60836a9b
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
10 changed files with 491 additions and 278 deletions

View File

@ -308,9 +308,9 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
} else {
auto new_img = ps->backend_data->ops->clone_image(
ps->backend_data, w->shadow_image, &reg_visible);
ps->backend_data->ops->image_op(
ps->backend_data, IMAGE_OP_APPLY_ALPHA_ALL, new_img,
NULL, &reg_visible, (double[]){w->opacity});
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, new_img,
&w->opacity);
ps->backend_data->ops->compose(
ps->backend_data, new_img, w->g.x + w->shadow_dx,
w->g.y + w->shadow_dy, &reg_shadow, &reg_visible);
@ -321,9 +321,9 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
// Set max brightness
if (ps->o.max_brightness < 1.0) {
ps->backend_data->ops->image_op(
ps->backend_data, IMAGE_OP_MAX_BRIGHTNESS, w->win_image, NULL,
&reg_visible, &ps->o.max_brightness);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_MAX_BRIGHTNESS, w->win_image,
&ps->o.max_brightness);
}
// Draw window on target
@ -363,18 +363,17 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
auto new_img = ps->backend_data->ops->clone_image(
ps->backend_data, w->win_image, &reg_visible_local);
if (w->invert_color) {
ps->backend_data->ops->image_op(
ps->backend_data, IMAGE_OP_INVERT_COLOR_ALL, new_img,
NULL, &reg_visible_local, NULL);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_INVERTED, new_img, NULL);
}
if (w->dim) {
double dim_opacity = ps->o.inactive_dim;
if (!ps->o.inactive_dim_fixed) {
dim_opacity *= w->opacity;
}
ps->backend_data->ops->image_op(
ps->backend_data, IMAGE_OP_DIM_ALL, new_img, NULL,
&reg_visible_local, (double[]){dim_opacity});
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_DIM_LEVEL, new_img,
&dim_opacity);
}
if (w->frame_opacity != 1) {
auto reg_frame = win_get_region_frame_local_by_val(w);
@ -384,9 +383,9 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
pixman_region32_fini(&reg_frame);
}
if (w->opacity != 1) {
ps->backend_data->ops->image_op(
ps->backend_data, IMAGE_OP_APPLY_ALPHA_ALL, new_img,
NULL, &reg_visible_local, (double[]){w->opacity});
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, new_img,
&w->opacity);
}
ps->backend_data->ops->compose(ps->backend_data, new_img, w->g.x,
w->g.y, &reg_paint_in_bound,

View File

@ -32,23 +32,36 @@ typedef struct backend_base {
typedef void (*backend_ready_callback_t)(void *);
// When image properties are actually applied to the image, they are applied in a
// particular order:
//
// Color inversion -> Dimming -> Opacity multiply -> Limit maximum brightness
enum image_properties {
// Whether the color of the image is inverted
// 1 boolean, default: false
IMAGE_PROPERTY_INVERTED,
// How much the image is dimmed
// 1 double, default: 0
IMAGE_PROPERTY_DIM_LEVEL,
// Image opacity, i.e. an alpha value multiplied to the alpha channel
// 1 double, default: 1
IMAGE_PROPERTY_OPACITY,
// The effective size of the image, the image will be tiled to fit.
// 2 int, default: the actual size of the image
IMAGE_PROPERTY_EFFECTIVE_SIZE,
// Limit how bright image can be. The image brightness is estimated by averaging
// the pixels in the image, and dimming will be applied to scale the average
// brightness down to the max brightness value.
// 1 double, default: 1
IMAGE_PROPERTY_MAX_BRIGHTNESS,
};
enum image_operations {
// Invert the color of the entire image, `reg_op` ignored
IMAGE_OP_INVERT_COLOR_ALL,
// Dim the entire image, argument is the percentage. `reg_op` ignored
IMAGE_OP_DIM_ALL,
// Apply the image properties, reset the image properties to their defaults
// afterwards.
IMAGE_OP_BAKE_PROPERTIES,
// Multiply the alpha channel by the argument
IMAGE_OP_APPLY_ALPHA,
// Same as APPLY_ALPHA, but `reg_op` is ignored and the operation applies to the
// full image
IMAGE_OP_APPLY_ALPHA_ALL,
// Change the effective size of the image, without touching the backing image
// itself. When the image is used, the backing image should be tiled to fill its
// effective size. `reg_op` and `reg_visible` is ignored. `arg` is two integers,
// width and height, in that order.
IMAGE_OP_RESIZE_TILE,
// Limit how bright image can be
IMAGE_OP_MAX_BRIGHTNESS,
};
struct gaussian_blur_args {
@ -203,7 +216,20 @@ struct backend_operations {
* they were originally applied. This might lead to inconsistencies.*/
/**
* Manipulate an image
* Change image properties
*
* @param backend_data backend data
* @param prop the property to change
* @param image_data an image data structure returned by the backend
* @param args property value
* @return whether the operation is successful
*/
bool (*set_image_property)(backend_t *backend_data, enum image_properties prop,
void *image_data, void *args);
/**
* Manipulate an image. Image properties are untouched by and have no effects on
* operations other than BAKE.
*
* @param backend_data backend data
* @param op the operation to perform
@ -214,13 +240,14 @@ struct backend_operations {
* be visible on target. this is a hint to the backend
* for optimization purposes.
* @param args extra arguments, operation specific
* @return a new image data structure containing the result
* @return whether the operation is successful
*/
bool (*image_op)(backend_t *backend_data, enum image_operations op, void *image_data,
const region_t *reg_op, const region_t *reg_visible, void *args);
/**
* Read the color of the pixel at given position of the given image
* Read the color of the pixel at given position of the given image. Image
* properties have no effect. BAKE them first before reading the pixels.
*
* @param backend_data backend_data
* @param image_data an image data structure previously returned by the
@ -233,7 +260,7 @@ struct backend_operations {
struct color *output);
/// Create another instance of the `image_data`. All `image_op` and
/// `image_set_property` calls on the returned image should not affect the
/// `set_image_property` calls on the returned image should not affect the
/// original image
void *(*clone_image)(backend_t *base, const void *image_data,
const region_t *reg_visible);

View File

@ -426,6 +426,51 @@ struct dual_kawase_params *generate_dual_kawase_params(void *args) {
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;

View File

@ -7,6 +7,7 @@
#include <stdbool.h>
#include "backend.h"
#include "config.h"
#include "region.h"
@ -25,6 +26,22 @@ struct dual_kawase_params {
int expand;
};
struct backend_image_inner_base {
int refcount;
bool has_alpha;
};
struct backend_image {
// Backend dependent inner image data
struct backend_image_inner_base *inner;
double opacity;
double dim;
double max_brightness;
// Effective size of the image
int ewidth, eheight;
bool color_inverted;
};
bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width,
int height, const conv *kernel, xcb_render_picture_t shadow_pixel,
xcb_pixmap_t *pixmap, xcb_render_picture_t *pict);
@ -51,3 +68,9 @@ void init_backend_base(struct backend_base *base, session_t *ps);
struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count);
struct dual_kawase_params *generate_dual_kawase_params(void *args);
void *default_clone_image(backend_t *base, const void *image_data, const region_t *reg);
bool default_is_image_transparent(backend_t *base attr_unused, void *image_data);
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
void *image_data, void *arg);
struct backend_image *default_new_backend_image(int w, int h);

View File

@ -121,6 +121,12 @@ bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unu
return true;
}
bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused,
void *image, void *arg attr_unused) {
dummy_check_image(base, image);
return true;
}
void *dummy_clone_image(struct backend_base *base, const void *image,
const region_t *reg_visible attr_unused) {
auto img = (const struct dummy_image *)image;
@ -160,6 +166,7 @@ struct backend_operations dummy_ops = {
.image_op = dummy_image_op,
.clone_image = dummy_clone_image,
.set_image_property = dummy_set_image_property,
.create_blur_context = dummy_create_blur_context,
.destroy_blur_context = dummy_destroy_blur_context,
.get_blur_size = dummy_get_blur_size,

View File

@ -271,25 +271,26 @@ _gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destina
* Returned texture must not be deleted, since it's owned by the gl_image. It will be
* deleted when the gl_image is released.
*/
static GLuint gl_average_texture_color(backend_t *base, struct gl_image *img) {
static GLuint gl_average_texture_color(backend_t *base, struct backend_image *img) {
auto gd = (struct gl_data *)base;
auto inner = (struct gl_texture *)img;
// Prepare textures which will be used for destination and source of rendering
// during downscaling.
const int texture_count = ARR_SIZE(img->inner->auxiliary_texture);
if (!img->inner->auxiliary_texture[0]) {
assert(!img->inner->auxiliary_texture[1]);
glGenTextures(texture_count, img->inner->auxiliary_texture);
const int texture_count = ARR_SIZE(inner->auxiliary_texture);
if (!inner->auxiliary_texture[0]) {
assert(!inner->auxiliary_texture[1]);
glGenTextures(texture_count, inner->auxiliary_texture);
glActiveTexture(GL_TEXTURE0);
for (int i = 0; i < texture_count; i++) {
glBindTexture(GL_TEXTURE_2D, img->inner->auxiliary_texture[i]);
glBindTexture(GL_TEXTURE_2D, inner->auxiliary_texture[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR,
(GLint[]){0, 0, 0, 0});
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, img->inner->width,
img->inner->height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, inner->width,
inner->height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL);
}
}
@ -302,7 +303,7 @@ static GLuint gl_average_texture_color(backend_t *base, struct gl_image *img) {
// Enable shaders
glUseProgram(gd->brightness_shader.prog);
glUniform2f(glGetUniformLocationChecked(gd->brightness_shader.prog, "texsize"),
(GLfloat)img->inner->width, (GLfloat)img->inner->height);
(GLfloat)inner->width, (GLfloat)inner->height);
// Prepare vertex attributes
GLuint vao;
@ -327,8 +328,8 @@ static GLuint gl_average_texture_color(backend_t *base, struct gl_image *img) {
// Do actual recursive render to 1x1 texture
GLuint result_texture = _gl_average_texture_color(
base, img->inner->texture, img->inner->auxiliary_texture[0],
img->inner->auxiliary_texture[1], fbo, img->inner->width, img->inner->height);
base, inner->texture, inner->auxiliary_texture[0],
inner->auxiliary_texture[1], fbo, inner->width, inner->height);
// Cleanup vertex attributes
glDisableVertexAttribArray(vert_coord_loc);
@ -365,10 +366,11 @@ static GLuint gl_average_texture_color(backend_t *base, struct gl_image *img) {
* @param reg_tgt the clip region, in Xorg coordinate system
* @param reg_visible ignored
*/
static void _gl_compose(backend_t *base, struct gl_image *img, GLuint target,
static void _gl_compose(backend_t *base, struct backend_image *img, GLuint target,
GLint *coord, GLuint *indices, int nrects) {
auto gd = (struct gl_data *)base;
if (!img || !img->inner->texture) {
auto inner = (struct gl_texture *)img->inner;
if (!img || !inner->texture) {
log_error("Missing texture.");
return;
}
@ -406,7 +408,7 @@ static void _gl_compose(backend_t *base, struct gl_image *img, GLuint target,
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, brightness);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, img->inner->texture);
glBindTexture(GL_TEXTURE_2D, inner->texture);
GLuint vao;
glGenVertexArrays(1, &vao);
@ -516,7 +518,8 @@ x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int text
void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y,
const region_t *reg_tgt, const region_t *reg_visible attr_unused) {
auto gd = (struct gl_data *)base;
struct gl_image *img = image_data;
struct backend_image *img = image_data;
auto inner = (struct gl_texture *)img->inner;
// Painting
int nrects;
@ -536,8 +539,8 @@ void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y,
auto coord = ccalloc(nrects * 16, GLint);
auto indices = ccalloc(nrects * 6, GLuint);
x_rect_to_coords(nrects, rects, dst_x, dst_y, img->inner->height, gd->height,
img->inner->y_inverted, coord, indices);
x_rect_to_coords(nrects, rects, dst_x, dst_y, inner->height, gd->height,
inner->y_inverted, coord, indices);
_gl_compose(base, img, gd->back_fbo, coord, indices, nrects);
free(indices);
@ -1067,33 +1070,26 @@ void gl_fill(backend_t *base, struct color c, const region_t *clip) {
return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true);
}
void gl_release_image(backend_t *base, void *image_data) {
struct gl_image *wd = image_data;
static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
auto gd = (struct gl_data *)base;
wd->inner->refcount--;
assert(wd->inner->refcount >= 0);
if (wd->inner->refcount > 0) {
free(wd);
return;
}
gd->release_user_data(base, inner);
assert(inner->user_data == NULL);
gd->release_user_data(base, wd->inner);
assert(wd->inner->user_data == NULL);
glDeleteTextures(1, &wd->inner->texture);
glDeleteTextures(2, wd->inner->auxiliary_texture);
free(wd->inner);
free(wd);
glDeleteTextures(1, &inner->texture);
glDeleteTextures(2, inner->auxiliary_texture);
free(inner);
gl_check_err();
}
void *gl_clone(backend_t *base attr_unused, const void *image_data,
const region_t *reg_visible attr_unused) {
const struct gl_image *img = image_data;
auto new_img = ccalloc(1, struct gl_image);
*new_img = *img;
new_img->inner->refcount++;
return new_img;
void gl_release_image(backend_t *base, void *image_data) {
struct backend_image *wd = image_data;
auto inner = (struct gl_texture *)wd->inner;
inner->refcount--;
assert(inner->refcount >= 0);
if (inner->refcount == 0) {
gl_release_image_inner(base, inner);
}
free(wd);
}
static inline void gl_free_blur_shader(gl_blur_shader_t *shader) {
@ -1750,24 +1746,58 @@ GLuint gl_new_texture(GLenum target) {
return texture;
}
/// Decouple `img` from the image it references, also applies all the lazy operations
static inline void gl_image_decouple(backend_t *base, struct gl_image *img) {
/// Actually duplicate a texture into a new one, if this texture is shared
static inline void gl_image_decouple(backend_t *base, struct backend_image *img) {
if (img->inner->refcount == 1) {
return;
}
auto gd = (struct gl_data *)base;
auto inner = (struct gl_texture *)img->inner;
auto new_tex = ccalloc(1, struct gl_texture);
new_tex->texture = gl_new_texture(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, new_tex->texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img->inner->width, img->inner->height, 0,
GL_BGRA, GL_UNSIGNED_BYTE, NULL);
new_tex->y_inverted = true;
new_tex->height = img->inner->height;
new_tex->width = img->inner->width;
new_tex->height = inner->height;
new_tex->width = inner->width;
new_tex->refcount = 1;
new_tex->user_data = gd->decouple_texture_user_data(base, img->inner->user_data);
new_tex->user_data = gd->decouple_texture_user_data(base, inner->user_data);
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
inner->texture, 0);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glBindTexture(GL_TEXTURE_2D, new_tex->texture);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, new_tex->width, new_tex->height, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
img->inner = (struct backend_image_inner_base *)new_tex;
inner->refcount--;
}
/// Decouple `img` from the image it references, also applies all the lazy operations
static inline void gl_image_bake(backend_t *base, struct backend_image *img) {
if (!img->color_inverted && img->opacity == 1 && img->max_brightness == 1 &&
img->dim == 0) {
// Nothing to bake
return;
}
auto gd = (struct gl_data *)base;
auto new_tex = ccalloc(1, struct gl_texture);
auto inner = (struct gl_texture *)img->inner;
new_tex->texture = gl_new_texture(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, new_tex->texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, inner->width, inner->height, 0, GL_BGRA,
GL_UNSIGNED_BYTE, NULL);
new_tex->y_inverted = true;
new_tex->height = inner->height;
new_tex->width = inner->width;
new_tex->refcount = 1;
new_tex->user_data = gd->decouple_texture_user_data(base, inner->user_data);
GLuint fbo;
glGenFramebuffers(1, &fbo);
@ -1786,16 +1816,16 @@ static inline void gl_image_decouple(backend_t *base, struct gl_image *img) {
0, 0, // texture coord
// top right
img->inner->width, 0, // vertex coord
img->inner->width, 0, // texture coord
inner->width, 0, // vertex coord
inner->width, 0, // texture coord
// bottom right
img->inner->width, img->inner->height,
img->inner->width, img->inner->height,
inner->width, inner->height,
inner->width, inner->height,
// bottom left
0, img->inner->height,
0, img->inner->height,
0, inner->height,
0, inner->height,
};
// clang-format on
@ -1803,8 +1833,11 @@ static inline void gl_image_decouple(backend_t *base, struct gl_image *img) {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
img->inner->refcount--;
img->inner = new_tex;
inner->refcount--;
if (inner->refcount == 0) {
gl_release_image_inner(base, inner);
}
img->inner = (struct backend_image_inner_base *)new_tex;
// Clear lazy operation flags
img->color_inverted = false;
@ -1814,16 +1847,17 @@ static inline void gl_image_decouple(backend_t *base, struct gl_image *img) {
gl_check_err();
}
static void gl_image_apply_alpha(backend_t *base, struct gl_image *img,
static void gl_image_apply_alpha(backend_t *base, struct backend_image *img,
const region_t *reg_op, double alpha) {
// Result color = 0 (GL_ZERO) + alpha (GL_CONSTANT_ALPHA) * original color
auto inner = (struct gl_texture *)img->inner;
glBlendFunc(GL_ZERO, GL_CONSTANT_ALPHA);
glBlendColor(0, 0, 0, (GLclampf)alpha);
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
img->inner->texture, 0);
inner->texture, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
_gl_fill(base, (struct color){0, 0, 0, 0}, reg_op, fbo, 0, false);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
@ -1883,40 +1917,28 @@ void gl_present(backend_t *base, const region_t *region) {
free(indices);
}
/// stub for backend_operations::image_op
bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
const region_t *reg_op, const region_t *reg_visible attr_unused, void *arg) {
struct gl_image *tex = image_data;
int *iargs = arg;
struct backend_image *tex = image_data;
switch (op) {
case IMAGE_OP_INVERT_COLOR_ALL: tex->color_inverted = true; break;
case IMAGE_OP_DIM_ALL:
tex->dim = 1.0 - (1.0 - tex->dim) * (1.0 - *(double *)arg);
break;
case IMAGE_OP_APPLY_ALPHA_ALL: tex->opacity *= *(double *)arg; break;
case IMAGE_OP_APPLY_ALPHA:
gl_image_decouple(base, tex);
assert(tex->inner->refcount == 1);
gl_image_apply_alpha(base, tex, reg_op, *(double *)arg);
break;
case IMAGE_OP_RESIZE_TILE:
// texture is already set to repeat, so nothing else we need to do
tex->ewidth = iargs[0];
tex->eheight = iargs[1];
break;
case IMAGE_OP_MAX_BRIGHTNESS: tex->max_brightness = *(double *)arg; break;
case IMAGE_OP_BAKE_PROPERTIES: gl_image_bake(base, tex); break;
}
return true;
}
bool gl_read_pixel(backend_t *base, void *image_data, int x, int y, struct color *output) {
struct gl_image *tex = image_data;
gl_image_decouple(base, tex);
assert(tex->inner->refcount == 1);
bool gl_read_pixel(backend_t *base attr_unused, void *image_data, int x, int y,
struct color *output) {
struct backend_image *tex = image_data;
auto inner = (struct gl_texture *)tex->inner;
GLfloat color[4];
glReadPixels(x, tex->inner->y_inverted ? tex->inner->height - y : y, 1, 1,
GL_RGBA, GL_FLOAT, color);
glReadPixels(x, inner->y_inverted ? inner->height - y : y, 1, 1, GL_RGBA,
GL_FLOAT, color);
output->alpha = color[3];
output->red = color[0];
output->green = color[1];
@ -1926,8 +1948,3 @@ bool gl_read_pixel(backend_t *base, void *image_data, int x, int y, struct color
gl_clear_err();
return ret;
}
bool gl_is_image_transparent(backend_t *base attr_unused, void *image_data) {
struct gl_image *img = image_data;
return img->has_alpha;
}

View File

@ -44,8 +44,10 @@ typedef struct {
GLint color_loc;
} gl_fill_shader_t;
/// @brief Wrapper of a binded GLX texture.
struct gl_texture {
int refcount;
bool has_alpha;
GLuint texture;
int width, height;
bool y_inverted;
@ -55,17 +57,6 @@ struct gl_texture {
void *user_data;
};
/// @brief Wrapper of a binded GLX texture.
typedef struct gl_image {
struct gl_texture *inner;
double opacity;
double dim;
double max_brightness;
int ewidth, eheight;
bool has_alpha;
bool color_inverted;
} gl_image_t;
struct gl_data {
backend_t base;
// If we are using proprietary NVIDIA driver
@ -123,7 +114,6 @@ void *gl_create_blur_context(backend_t *base, enum blur_method, void *args);
void gl_destroy_blur_context(backend_t *base, void *ctx);
void gl_get_blur_size(void *blur_context, int *width, int *height);
bool gl_is_image_transparent(backend_t *base, void *image_data);
void gl_fill(backend_t *base, struct color, const region_t *clip);
void gl_present(backend_t *base, const region_t *);

View File

@ -388,11 +388,12 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
}
log_trace("Binding pixmap %#010x", pixmap);
auto wd = ccalloc(1, struct gl_image);
auto wd = ccalloc(1, struct backend_image);
wd->max_brightness = 1;
wd->inner = ccalloc(1, struct gl_texture);
wd->inner->width = wd->ewidth = r->width;
wd->inner->height = wd->eheight = r->height;
auto inner = ccalloc(1, struct gl_texture);
inner->width = wd->ewidth = r->width;
inner->height = wd->eheight = r->height;
wd->inner = (struct backend_image_inner_base *)inner;
free(r);
auto fbcfg = glx_find_fbconfig(gd->display, gd->screen, fmt);
@ -420,7 +421,7 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
0,
};
wd->inner->y_inverted = fbcfg->y_inverted;
inner->y_inverted = fbcfg->y_inverted;
glxpixmap = cmalloc(struct _glx_pixmap);
glxpixmap->pixmap = pixmap;
@ -436,14 +437,14 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
log_trace("GLXPixmap %#010lx", glxpixmap->glpixmap);
// Create texture
wd->inner->user_data = glxpixmap;
wd->inner->texture = gl_new_texture(GL_TEXTURE_2D);
inner->user_data = glxpixmap;
inner->texture = gl_new_texture(GL_TEXTURE_2D);
inner->has_alpha = fmt.alpha_size != 0;
wd->opacity = 1;
wd->color_inverted = false;
wd->dim = 0;
wd->has_alpha = fmt.alpha_size != 0;
wd->inner->refcount = 1;
glBindTexture(GL_TEXTURE_2D, wd->inner->texture);
glBindTexture(GL_TEXTURE_2D, inner->texture);
glXBindTexImageEXT(gd->display, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
@ -526,10 +527,11 @@ struct backend_operations glx_ops = {
.release_image = gl_release_image,
.compose = gl_compose,
.image_op = gl_image_op,
.set_image_property = default_set_image_property,
.read_pixel = gl_read_pixel,
.clone_image = gl_clone,
.clone_image = default_clone_image,
.blur = gl_blur,
.is_image_transparent = gl_is_image_transparent,
.is_image_transparent = default_is_image_transparent,
.present = glx_present,
.buffer_age = glx_buffer_age,
.render_shadow = default_backend_render_shadow,

View File

@ -70,41 +70,136 @@ struct _xrender_blur_context {
int x_blur_kernel_count;
};
struct _xrender_image_data {
struct _xrender_image_data_inner {
// struct backend_image_inner_base
int refcount;
bool has_alpha;
// Pixmap that the client window draws to,
// it will contain the content of client window.
xcb_pixmap_t pixmap;
// A Picture links to the Pixmap
xcb_render_picture_t pict;
int width, height;
// The effective size of the image
int ewidth, eheight;
bool has_alpha;
double opacity;
xcb_visualid_t visual;
uint8_t depth;
// Whether we own this image, e.g. we allocated it;
// or not, e.g. this is a named pixmap of a X window.
bool owned;
};
static void compose_impl(struct _xrender_data *xd, const struct backend_image *img,
int dst_x, int dst_y, const region_t *reg_paint,
const region_t *reg_visible, xcb_render_picture_t result) {
auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)];
auto inner = (struct _xrender_image_data_inner *)img->inner;
region_t reg;
bool has_alpha = inner->has_alpha || img->opacity != 1;
const auto tmpw = to_u16_checked(inner->width);
const auto tmph = to_u16_checked(inner->height);
const auto tmpew = to_u16_checked(img->ewidth);
const auto tmpeh = to_u16_checked(img->eheight);
const xcb_render_color_t dim_color = {
.red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)};
// Clip region of rendered_pict might be set during rendering, clear it to
// make sure we get everything into the buffer
x_clear_picture_clip_region(xd->base.c, inner->pict);
pixman_region32_init(&reg);
pixman_region32_intersect(&reg, (region_t *)reg_paint, (region_t *)reg_visible);
x_set_picture_clip_region(xd->base.c, result, 0, 0, &reg);
if ((img->color_inverted || img->dim != 0) && has_alpha) {
// Apply image properties using a temporary image, because the source
// image is transparent. Otherwise the properties can be applied directly
// on the target image.
auto tmp_pict =
x_create_picture_with_visual(xd->base.c, xd->base.root, inner->width,
inner->height, inner->visual, 0, NULL);
// Set clip region translated to source coordinate
x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst_x),
to_i16_checked(-dst_y), &reg);
// Copy source -> tmp
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict,
XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
if (img->color_inverted) {
if (inner->has_alpha) {
auto tmp_pict2 = x_create_picture_with_visual(
xd->base.c, xd->base.root, tmpw, tmph, inner->visual,
0, NULL);
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC,
tmp_pict, XCB_NONE, tmp_pict2, 0, 0,
0, 0, 0, 0, tmpw, tmph);
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
xd->white_pixel, XCB_NONE, tmp_pict,
0, 0, 0, 0, 0, 0, tmpw, tmph);
xcb_render_composite(
xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, tmp_pict2,
XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
xcb_render_free_picture(xd->base.c, tmp_pict2);
} else {
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
xd->white_pixel, XCB_NONE, tmp_pict,
0, 0, 0, 0, 0, 0, tmpw, tmph);
}
}
if (img->dim != 0) {
// Dim the actually content of window
xcb_rectangle_t rect = {
.x = 0,
.y = 0,
.width = tmpw,
.height = tmph,
};
xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER,
tmp_pict, dim_color, 1, &rect);
}
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict,
alpha_pict, result, 0, 0, 0, 0, to_i16_checked(dst_x),
to_i16_checked(dst_y), tmpew, tmpeh);
xcb_render_free_picture(xd->base.c, tmp_pict);
} else {
uint8_t op = (has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC);
xcb_render_composite(xd->base.c, op, inner->pict, alpha_pict, result, 0,
0, 0, 0, to_i16_checked(dst_x),
to_i16_checked(dst_y), tmpew, tmpeh);
if (img->dim != 0 || img->color_inverted) {
// Apply properties, if we reach here, then has_alpha == false
assert(!has_alpha);
if (img->color_inverted) {
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
xd->white_pixel, XCB_NONE, result, 0,
0, 0, 0, to_i16_checked(dst_x),
to_i16_checked(dst_y), tmpew, tmpeh);
}
if (img->dim != 0) {
// Dim the actually content of window
xcb_rectangle_t rect = {
.x = to_i16_checked(dst_x),
.y = to_i16_checked(dst_y),
.width = tmpew,
.height = tmpeh,
};
xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER,
result, dim_color, 1, &rect);
}
}
}
pixman_region32_fini(&reg);
}
static void compose(backend_t *base, void *img_data, int dst_x, int dst_y,
const region_t *reg_paint, const region_t *reg_visible) {
struct _xrender_data *xd = (void *)base;
struct _xrender_image_data *img = img_data;
uint8_t op = (img->has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC);
auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)];
region_t reg;
pixman_region32_init(&reg);
pixman_region32_intersect(&reg, (region_t *)reg_paint, (region_t *)reg_visible);
// Clip region of rendered_pict might be set during rendering, clear it to make
// sure we get everything into the buffer
x_clear_picture_clip_region(base->c, img->pict);
x_set_picture_clip_region(base->c, xd->back[2], 0, 0, &reg);
xcb_render_composite(base->c, op, img->pict, alpha_pict, xd->back[2], 0, 0, 0, 0,
to_i16_checked(dst_x), to_i16_checked(dst_y),
to_u16_checked(img->ewidth), to_u16_checked(img->eheight));
pixman_region32_fini(&reg);
return compose_impl(xd, img_data, dst_x, dst_y, reg_paint, reg_visible, xd->back[2]);
}
static void fill(backend_t *base, struct color c, const region_t *clip) {
@ -255,31 +350,42 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool
return NULL;
}
auto img = ccalloc(1, struct _xrender_image_data);
img->depth = (uint8_t)fmt.visual_depth;
img->width = img->ewidth = r->width;
img->height = img->eheight = r->height;
img->pixmap = pixmap;
img->opacity = 1;
img->has_alpha = fmt.alpha_size != 0;
img->pict =
auto img = ccalloc(1, struct backend_image);
auto inner = ccalloc(1, struct _xrender_image_data_inner);
inner->depth = (uint8_t)fmt.visual_depth;
inner->width = img->ewidth = r->width;
inner->height = img->eheight = r->height;
inner->pixmap = pixmap;
inner->has_alpha = fmt.alpha_size != 0;
inner->pict =
x_create_picture_with_visual_and_pixmap(base->c, fmt.visual, pixmap, 0, NULL);
img->owned = owned;
img->visual = fmt.visual;
inner->owned = owned;
inner->visual = fmt.visual;
inner->refcount = 1;
img->inner = (struct backend_image_inner_base *)inner;
img->opacity = 1;
free(r);
if (img->pict == XCB_NONE) {
if (inner->pict == XCB_NONE) {
free(inner);
free(img);
return NULL;
}
return img;
}
static void release_image_inner(backend_t *base, struct _xrender_image_data_inner *inner) {
xcb_render_free_picture(base->c, inner->pict);
if (inner->owned) {
xcb_free_pixmap(base->c, inner->pixmap);
}
free(inner);
}
static void release_image(backend_t *base, void *image) {
struct _xrender_image_data *img = image;
xcb_render_free_picture(base->c, img->pict);
if (img->owned) {
xcb_free_pixmap(base->c, img->pixmap);
struct backend_image *img = image;
img->inner->refcount--;
if (img->inner->refcount == 0) {
release_image_inner(base, (void *)img->inner);
}
free(img);
}
@ -375,71 +481,99 @@ static int buffer_age(backend_t *backend_data) {
return xd->buffer_age[xd->curr_back];
}
static bool is_image_transparent(backend_t *bd attr_unused, void *image) {
struct _xrender_image_data *img = image;
return img->has_alpha;
static struct _xrender_image_data_inner *
new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) {
auto new_inner = ccalloc(1, struct _xrender_image_data_inner);
new_inner->pixmap = x_create_pixmap(base->c, depth, base->root, w, h);
if (new_inner->pixmap == XCB_NONE) {
log_error("Failed to create pixmap for copy");
free(new_inner);
return NULL;
}
new_inner->pict = x_create_picture_with_visual_and_pixmap(
base->c, visual, new_inner->pixmap, 0, NULL);
if (new_inner->pict == XCB_NONE) {
log_error("Failed to create picture for copy");
xcb_free_pixmap(base->c, new_inner->pixmap);
free(new_inner);
return NULL;
}
new_inner->width = w;
new_inner->height = h;
new_inner->visual = visual;
new_inner->depth = depth;
new_inner->refcount = 1;
new_inner->owned = true;
return new_inner;
}
static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) {
if (img->inner->refcount == 1) {
return true;
}
auto inner = (struct _xrender_image_data_inner *)img->inner;
auto inner2 =
new_inner(base, inner->width, inner->height, inner->visual, inner->depth);
if (!inner2) {
return false;
}
x_set_picture_clip_region(base->c, inner->pict, 0, 0, reg);
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE,
inner2->pict, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width),
to_u16_checked(inner->height));
img->inner = (struct backend_image_inner_base *)inner2;
inner->refcount--;
return true;
}
static bool bake_image(backend_t *base, struct backend_image *img, const region_t *reg) {
struct _xrender_data *xd = (void *)base;
struct _xrender_image_data_inner *inner = (void *)img->inner;
assert(inner->visual != XCB_NONE);
if (!img->color_inverted && img->opacity == 1 && img->dim == 0) {
// Nothing to bake
return true;
}
log_trace("xrender: copying %#010x visual %#x", inner->pixmap, inner->visual);
auto inner2 =
new_inner(base, inner->width, inner->height, inner->visual, inner->depth);
if (!inner2) {
return false;
}
inner2->has_alpha = (inner->has_alpha || img->opacity != 1);
compose_impl(xd, img, 0, 0, reg, NULL, inner2->pict);
img->opacity = 1;
img->inner = (struct backend_image_inner_base *)inner2;
img->dim = 0;
img->color_inverted = false;
inner->refcount--;
if (inner->refcount == 0) {
release_image_inner(base, inner);
}
return true;
}
static bool image_op(backend_t *base, enum image_operations op, void *image,
const region_t *reg_op, const region_t *reg_visible, void *arg) {
struct _xrender_data *xd = (void *)base;
struct _xrender_image_data *img = image;
struct backend_image *img = image;
region_t reg;
double *dargs = arg;
int *iargs = arg;
if (op == IMAGE_OP_APPLY_ALPHA_ALL) {
img->opacity *= dargs[0];
img->has_alpha = true;
return true;
}
pixman_region32_init(&reg);
pixman_region32_intersect(&reg, (region_t *)reg_op, (region_t *)reg_visible);
const auto tmpw = to_u16_checked(img->width);
const auto tmph = to_u16_checked(img->height);
switch (op) {
case IMAGE_OP_INVERT_COLOR_ALL:
x_set_picture_clip_region(base->c, img->pict, 0, 0, reg_visible);
if (img->has_alpha) {
auto tmp_pict =
x_create_picture_with_visual(base->c, base->root, img->width,
img->height, img->visual, 0, NULL);
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, img->pict,
XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
case IMAGE_OP_BAKE_PROPERTIES: return bake_image(base, img, &reg);
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_DIFFERENCE,
xd->white_pixel, XCB_NONE, img->pict, 0, 0,
0, 0, 0, 0, tmpw, tmph);
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_IN_REVERSE,
tmp_pict, XCB_NONE, img->pict, 0, 0, 0, 0, 0,
0, tmpw, tmph);
xcb_render_free_picture(base->c, tmp_pict);
} else {
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_DIFFERENCE,
xd->white_pixel, XCB_NONE, img->pict, 0, 0,
0, 0, 0, 0, tmpw, tmph);
}
break;
case IMAGE_OP_DIM_ALL:
x_set_picture_clip_region(base->c, img->pict, 0, 0, reg_visible);
xcb_render_color_t color = {
.red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * dargs[0])};
// Dim the actually content of window
xcb_rectangle_t rect = {
.x = 0,
.y = 0,
.width = tmpw,
.height = tmph,
};
xcb_render_fill_rectangles(base->c, XCB_RENDER_PICT_OP_OVER, img->pict,
color, 1, &rect);
break;
case IMAGE_OP_APPLY_ALPHA:
assert(reg_op);
pixman_region32_intersect(&reg, (region_t *)reg_op, (region_t *)reg_visible);
if (!pixman_region32_not_empty(&reg)) {
break;
}
@ -448,58 +582,25 @@ static bool image_op(backend_t *base, enum image_operations op, void *image,
break;
}
if (!decouple_image(base, img, reg_visible)) {
pixman_region32_fini(&reg);
return false;
}
auto inner = (struct _xrender_image_data_inner *)img->inner;
auto alpha_pict = xd->alpha_pict[(int)((1 - dargs[0]) * MAX_ALPHA)];
x_set_picture_clip_region(base->c, img->pict, 0, 0, &reg);
x_set_picture_clip_region(base->c, inner->pict, 0, 0, &reg);
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_OUT_REVERSE, alpha_pict,
XCB_NONE, img->pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
img->has_alpha = true;
XCB_NONE, inner->pict, 0, 0, 0, 0, 0, 0,
to_u16_checked(inner->width),
to_u16_checked(inner->height));
inner->has_alpha = true;
break;
case IMAGE_OP_RESIZE_TILE:
img->ewidth = iargs[0];
img->eheight = iargs[1];
break;
case IMAGE_OP_APPLY_ALPHA_ALL: assert(false);
case IMAGE_OP_MAX_BRIGHTNESS: assert(false);
}
pixman_region32_fini(&reg);
return true;
}
// TODO(yshui): use copy-on-write
static void *clone_image(backend_t *base, const void *image, const region_t *reg) {
const struct _xrender_image_data *img = image;
struct _xrender_data *xd = (void *)base;
auto new_img = ccalloc(1, struct _xrender_image_data);
assert(img->visual != XCB_NONE);
log_trace("xrender: copying %#010x visual %#x", img->pixmap, img->visual);
*new_img = *img;
x_set_picture_clip_region(base->c, img->pict, 0, 0, reg);
new_img->pixmap =
x_create_pixmap(base->c, img->depth, base->root, img->width, img->height);
new_img->opacity = 1;
new_img->owned = true;
if (new_img->pixmap == XCB_NONE) {
log_error("Failed to create pixmap for copy");
free(new_img);
return NULL;
}
new_img->pict = x_create_picture_with_visual_and_pixmap(base->c, img->visual,
new_img->pixmap, 0, NULL);
if (new_img->pict == XCB_NONE) {
log_error("Failed to create picture for copy");
xcb_free_pixmap(base->c, new_img->pixmap);
free(new_img);
return NULL;
}
xcb_render_picture_t alpha_pict =
img->opacity == 1 ? XCB_NONE : xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)];
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, img->pict, alpha_pict,
new_img->pict, 0, 0, 0, 0, 0, 0, to_u16_checked(img->width),
to_u16_checked(img->height));
return new_img;
}
static void *
create_blur_context(backend_t *base attr_unused, enum blur_method method, void *args) {
auto ret = ccalloc(1, struct _xrender_blur_context);
@ -562,9 +663,10 @@ static void get_blur_size(void *blur_context, int *width, int *height) {
static bool
read_pixel(backend_t *backend_data, void *image_data, int x, int y, struct color *output) {
auto xd = (struct _xrender_data *)backend_data;
auto img = (struct _xrender_image_data *)image_data;
auto img = (struct backend_image *)image_data;
auto inner = (struct _xrender_image_data_inner *)img->inner;
auto r = XCB_AWAIT(xcb_get_image, xd->base.c, XCB_IMAGE_FORMAT_XY_PIXMAP, img->pixmap,
auto r = XCB_AWAIT(xcb_get_image, xd->base.c, XCB_IMAGE_FORMAT_XY_PIXMAP, inner->pixmap,
to_i16_checked(x), to_i16_checked(y), 1, 1, (uint32_t)-1L);
if (!r) {
@ -673,13 +775,14 @@ struct backend_operations xrender_ops = {
.render_shadow = default_backend_render_shadow,
//.prepare_win = prepare_win,
//.release_win = release_win,
.is_image_transparent = is_image_transparent,
.is_image_transparent = default_is_image_transparent,
.buffer_age = buffer_age,
.max_buffer_age = 2,
.image_op = image_op,
.read_pixel = read_pixel,
.clone_image = clone_image,
.clone_image = default_clone_image,
.set_image_property = default_set_image_property,
.create_blur_context = create_blur_context,
.destroy_blur_context = destroy_blur_context,
.get_blur_size = get_blur_size,

View File

@ -893,9 +893,9 @@ void root_damaged(session_t *ps) {
if (pixmap != XCB_NONE) {
ps->root_image = ps->backend_data->ops->bind_pixmap(
ps->backend_data, pixmap, x_get_visual_info(ps->c, ps->vis), false);
ps->backend_data->ops->image_op(
ps->backend_data, IMAGE_OP_RESIZE_TILE, ps->root_image, NULL,
NULL, (int[]){ps->root_width, ps->root_height});
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE,
ps->root_image, (int[]){ps->root_width, ps->root_height});
}
}