wlroots/render/pixman/pass.c
David Turner ee0007c0f2 render/pixman: Fix source crop
Fix source crop in the pixman render backend.  It was being applied by
using a source offset as arguments to pixman_image_composite32().  But
this is wrong because the source crop should get applied before all the
other transforms, not after them (or at least this is how it works in
the other wlroots render backends).  Instead, apply the source crop as
yet another matrix transform when we're doing transforms (Or keep it the
same as previously if there's no other transforming going on).

Closes: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3830
2024-04-26 16:12:38 +00:00

238 lines
7.5 KiB
C

#include <assert.h>
#include <stdlib.h>
#include "render/pixman.h"
static const struct wlr_render_pass_impl render_pass_impl;
static struct wlr_pixman_render_pass *get_render_pass(struct wlr_render_pass *wlr_pass) {
assert(wlr_pass->impl == &render_pass_impl);
struct wlr_pixman_render_pass *pass = wl_container_of(wlr_pass, pass, base);
return pass;
}
static struct wlr_pixman_texture *get_texture(struct wlr_texture *wlr_texture) {
assert(wlr_texture_is_pixman(wlr_texture));
struct wlr_pixman_texture *texture = wl_container_of(wlr_texture, texture, wlr_texture);
return texture;
}
static bool render_pass_submit(struct wlr_render_pass *wlr_pass) {
struct wlr_pixman_render_pass *pass = get_render_pass(wlr_pass);
wlr_buffer_end_data_ptr_access(pass->buffer->buffer);
wlr_buffer_unlock(pass->buffer->buffer);
free(pass);
return true;
}
static pixman_op_t get_pixman_blending(enum wlr_render_blend_mode mode) {
switch (mode) {
case WLR_RENDER_BLEND_MODE_PREMULTIPLIED:
return PIXMAN_OP_OVER;
case WLR_RENDER_BLEND_MODE_NONE:
return PIXMAN_OP_SRC;
}
abort();
}
static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
const struct wlr_render_texture_options *options) {
struct wlr_pixman_render_pass *pass = get_render_pass(wlr_pass);
struct wlr_pixman_texture *texture = get_texture(options->texture);
struct wlr_pixman_buffer *buffer = pass->buffer;
if (texture->buffer != NULL && !begin_pixman_data_ptr_access(texture->buffer,
&texture->image, WLR_BUFFER_DATA_PTR_ACCESS_READ)) {
return;
}
pixman_op_t op = get_pixman_blending(options->blend_mode);
pixman_image_set_clip_region32(buffer->image, (pixman_region32_t *)options->clip);
struct wlr_fbox src_fbox;
wlr_render_texture_options_get_src_box(options, &src_fbox);
struct wlr_box src_box = {
.x = roundf(src_fbox.x),
.y = roundf(src_fbox.y),
.width = roundf(src_fbox.width),
.height = roundf(src_fbox.height),
};
struct wlr_box dst_box;
wlr_render_texture_options_get_dst_box(options, &dst_box);
pixman_image_t *mask = NULL;
float alpha = wlr_render_texture_options_get_alpha(options);
if (alpha != 1) {
mask = pixman_image_create_solid_fill(&(struct pixman_color){
.alpha = 0xFFFF * alpha,
});
}
struct wlr_box orig_box;
wlr_box_transform(&orig_box, &dst_box, options->transform,
buffer->buffer->width, buffer->buffer->height);
if (options->transform != WL_OUTPUT_TRANSFORM_NORMAL ||
orig_box.width != src_box.width ||
orig_box.height != src_box.height) {
// Cosinus/sinus values are extact integers for enum wl_output_transform entries
int tr_cos = 1, tr_sin = 0, tr_x = 0, tr_y = 0;
switch (options->transform) {
case WL_OUTPUT_TRANSFORM_NORMAL:
case WL_OUTPUT_TRANSFORM_FLIPPED:
break;
case WL_OUTPUT_TRANSFORM_90:
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
tr_cos = 0;
tr_sin = 1;
tr_x = buffer->buffer->height;
break;
case WL_OUTPUT_TRANSFORM_180:
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
tr_cos = -1;
tr_sin = 0;
tr_x = buffer->buffer->width;
tr_y = buffer->buffer->height;
break;
case WL_OUTPUT_TRANSFORM_270:
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
tr_cos = 0;
tr_sin = -1;
tr_y = buffer->buffer->width;
break;
}
switch (options->transform) {
case WL_OUTPUT_TRANSFORM_FLIPPED:
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
tr_x = buffer->buffer->width - tr_x;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
tr_x = buffer->buffer->height - tr_x;
break;
case WL_OUTPUT_TRANSFORM_NORMAL:
case WL_OUTPUT_TRANSFORM_90:
case WL_OUTPUT_TRANSFORM_180:
case WL_OUTPUT_TRANSFORM_270:
break;
}
struct pixman_transform transform;
pixman_transform_init_identity(&transform);
pixman_transform_rotate(&transform, NULL,
pixman_int_to_fixed(tr_cos), pixman_int_to_fixed(tr_sin));
if (options->transform >= WL_OUTPUT_TRANSFORM_FLIPPED) {
pixman_transform_scale(&transform, NULL,
pixman_int_to_fixed(-1), pixman_int_to_fixed(1));
}
// pixman rotates about the origin, translate the result so that its new top-left
// corner is back at the origin.
pixman_transform_translate(&transform, NULL,
pixman_int_to_fixed(tr_x), pixman_int_to_fixed(tr_y));
pixman_transform_translate(&transform, NULL,
-pixman_int_to_fixed(orig_box.x), -pixman_int_to_fixed(orig_box.y));
pixman_transform_scale(&transform, NULL,
pixman_double_to_fixed(src_box.width / (double)orig_box.width),
pixman_double_to_fixed(src_box.height / (double)orig_box.height));
// Apply the translation for source crop so the origin is now at the top-left of
// the region we're actually using. Do this last so all the other transforms
// apply on top of this.
pixman_transform_translate(&transform, NULL,
pixman_int_to_fixed(src_box.x), pixman_int_to_fixed(src_box.y));
switch (options->filter_mode) {
case WLR_SCALE_FILTER_BILINEAR:
pixman_image_set_filter(texture->image, PIXMAN_FILTER_BILINEAR, NULL, 0);
break;
case WLR_SCALE_FILTER_NEAREST:
pixman_image_set_filter(texture->image, PIXMAN_FILTER_NEAREST, NULL, 0);
break;
}
pixman_image_set_transform(texture->image, &transform);
// We've already applied the transforms for source crop and scaling so just
// composite over the whole output and let the transform deal with everything.
pixman_image_composite32(op, texture->image, mask, buffer->image,
0, 0, 0, 0, 0, 0, buffer->buffer->width, buffer->buffer->height);
pixman_image_set_transform(texture->image, NULL);
} else {
// No transforms or crop needed, just a straight blit from the source
pixman_image_set_transform(texture->image, NULL);
pixman_image_composite32(op, texture->image, mask, buffer->image,
src_box.x, src_box.y, 0, 0, dst_box.x, dst_box.y,
src_box.width, src_box.height);
}
pixman_image_set_clip_region32(buffer->image, NULL);
if (texture->buffer != NULL) {
wlr_buffer_end_data_ptr_access(texture->buffer);
}
if (mask != NULL) {
pixman_image_unref(mask);
}
}
static void render_pass_add_rect(struct wlr_render_pass *wlr_pass,
const struct wlr_render_rect_options *options) {
struct wlr_pixman_render_pass *pass = get_render_pass(wlr_pass);
struct wlr_pixman_buffer *buffer = pass->buffer;
struct wlr_box box;
wlr_render_rect_options_get_box(options, pass->buffer->buffer, &box);
pixman_op_t op = get_pixman_blending(options->color.a == 1 ?
WLR_RENDER_BLEND_MODE_NONE : options->blend_mode);
struct pixman_color color = {
.red = options->color.r * 0xFFFF,
.green = options->color.g * 0xFFFF,
.blue = options->color.b * 0xFFFF,
.alpha = options->color.a * 0xFFFF,
};
pixman_image_t *fill = pixman_image_create_solid_fill(&color);
pixman_image_set_clip_region32(buffer->image, (pixman_region32_t *)options->clip);
pixman_image_composite32(op, fill, NULL, buffer->image,
0, 0, 0, 0, box.x, box.y, box.width, box.height);
pixman_image_set_clip_region32(buffer->image, NULL);
pixman_image_unref(fill);
}
static const struct wlr_render_pass_impl render_pass_impl = {
.submit = render_pass_submit,
.add_texture = render_pass_add_texture,
.add_rect = render_pass_add_rect,
};
struct wlr_pixman_render_pass *begin_pixman_render_pass(
struct wlr_pixman_buffer *buffer) {
struct wlr_pixman_render_pass *pass = calloc(1, sizeof(*pass));
if (pass == NULL) {
return NULL;
}
wlr_render_pass_init(&pass->base, &render_pass_impl);
if (!begin_pixman_data_ptr_access(buffer->buffer, &buffer->image,
WLR_BUFFER_DATA_PTR_ACCESS_READ | WLR_BUFFER_DATA_PTR_ACCESS_WRITE)) {
free(pass);
return NULL;
}
wlr_buffer_lock(buffer->buffer);
pass->buffer = buffer;
return pass;
}