render/pixman: Improve transform performance

The old code to render transformed textures with pixman would run
composite over the whole output regardless of the texture size.  When
rendering something small this caused a huge performance hit.

Rewrite the transform branch of render_pass_add_texture to:
- Only composite over the rectangle we're drawing to
- Generally try to make things a lot clearer and some comments

Fixes: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3832
This commit is contained in:
David Turner 2024-04-25 11:14:28 +01:00 committed by Simon Ser
parent be667b0628
commit e08d52bbc6

View File

@ -70,6 +70,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
}); });
} }
// Rotate the final destination size into source coordinates
struct wlr_box orig_box; struct wlr_box orig_box;
wlr_box_transform(&orig_box, &dst_box, options->transform, wlr_box_transform(&orig_box, &dst_box, options->transform,
buffer->buffer->width, buffer->buffer->height); buffer->buffer->width, buffer->buffer->height);
@ -87,65 +88,75 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_90:
tr_cos = 0; tr_cos = 0;
tr_sin = 1; tr_sin = 1;
tr_x = buffer->buffer->height; tr_y = src_box.width;
break; break;
case WL_OUTPUT_TRANSFORM_180: case WL_OUTPUT_TRANSFORM_180:
case WL_OUTPUT_TRANSFORM_FLIPPED_180: case WL_OUTPUT_TRANSFORM_FLIPPED_180:
tr_cos = -1; tr_cos = -1;
tr_sin = 0; tr_sin = 0;
tr_x = buffer->buffer->width; tr_x = src_box.width;
tr_y = buffer->buffer->height; tr_y = src_box.height;
break; break;
case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_270:
case WL_OUTPUT_TRANSFORM_FLIPPED_270: case WL_OUTPUT_TRANSFORM_FLIPPED_270:
tr_cos = 0; tr_cos = 0;
tr_sin = -1; tr_sin = -1;
tr_y = buffer->buffer->width; tr_x = src_box.height;
break; break;
} }
switch (options->transform) { // Pixman transforms are generally the opposite of what you expect because they
case WL_OUTPUT_TRANSFORM_FLIPPED: // apply to the coordinate system rather than the image. The comments here
case WL_OUTPUT_TRANSFORM_FLIPPED_180: // refer to what happens to the image, so all the code between
tr_x = buffer->buffer->width - tr_x; // pixman_transform_init_identity() and pixman_image_set_transform() is probably
break; // best read backwards. Also this means translations are in the opposite
case WL_OUTPUT_TRANSFORM_FLIPPED_90: // direction, imagine them as moving the origin around rather than moving the
case WL_OUTPUT_TRANSFORM_FLIPPED_270: // image.
tr_x = buffer->buffer->height - tr_x; //
break; // Beware that this doesn't work quite the same as wp_viewporter: We apply crop
case WL_OUTPUT_TRANSFORM_NORMAL: // before transform and scale, whereas it defines crop in post-transform-scale
case WL_OUTPUT_TRANSFORM_90: // coordinates. But this only applies to internal wlroots code - the viewporter
case WL_OUTPUT_TRANSFORM_180: // extension code makes sure that to clients everything works as it should.
case WL_OUTPUT_TRANSFORM_270:
break;
}
struct pixman_transform transform; struct pixman_transform transform;
pixman_transform_init_identity(&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 // Apply scaling to get to the dst_box size. Because the scaling is applied last
// corner is back at the origin. // it depends on the whether the rotation swapped width and height, which is why
pixman_transform_translate(&transform, NULL, // we use orig_box instead of dst_box.
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_transform_scale(&transform, NULL,
pixman_double_to_fixed(src_box.width / (double)orig_box.width), pixman_double_to_fixed(src_box.width / (double)orig_box.width),
pixman_double_to_fixed(src_box.height / (double)orig_box.height)); pixman_double_to_fixed(src_box.height / (double)orig_box.height));
// pixman rotates about the origin which again leaves everything outside of the
// viewport. 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));
// Apply the rotation
pixman_transform_rotate(&transform, NULL,
pixman_int_to_fixed(tr_cos), pixman_int_to_fixed(tr_sin));
// Apply flip before rotation
if (options->transform >= WL_OUTPUT_TRANSFORM_FLIPPED) {
// The flip leaves everything left of the Y axis which is outside the
// viewport. So translate everything back into the viewport.
pixman_transform_translate(&transform, NULL,
-pixman_int_to_fixed(src_box.width), pixman_int_to_fixed(0));
// Flip by applying a scale of -1 to the X axis
pixman_transform_scale(&transform, NULL,
pixman_int_to_fixed(-1), pixman_int_to_fixed(1));
}
// Apply the translation for source crop so the origin is now at the top-left of // 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 // the region we're actually using. Do this last so all the other transforms
// apply on top of this. // apply on top of this.
pixman_transform_translate(&transform, NULL, pixman_transform_translate(&transform, NULL,
pixman_int_to_fixed(src_box.x), pixman_int_to_fixed(src_box.y)); pixman_int_to_fixed(src_box.x), pixman_int_to_fixed(src_box.y));
pixman_image_set_transform(texture->image, &transform);
switch (options->filter_mode) { switch (options->filter_mode) {
case WLR_SCALE_FILTER_BILINEAR: case WLR_SCALE_FILTER_BILINEAR:
pixman_image_set_filter(texture->image, PIXMAN_FILTER_BILINEAR, NULL, 0); pixman_image_set_filter(texture->image, PIXMAN_FILTER_BILINEAR, NULL, 0);
@ -155,12 +166,17 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
break; break;
} }
pixman_image_set_transform(texture->image, &transform); // Now composite the result onto the pass buffer. We specify a source origin of 0,0
// because the x,y part of source crop is already done using the transform. The
// We've already applied the transforms for source crop and scaling so just // width,height part of source crop is done here by the width and height we pass:
// composite over the whole output and let the transform deal with everything. // because of the scaling, cropping at the end by dst_box.{width,height} is
// equivalent to if we cropped at the start by src_box.{width,height}.
pixman_image_composite32(op, texture->image, mask, buffer->image, pixman_image_composite32(op, texture->image, mask, buffer->image,
0, 0, 0, 0, 0, 0, buffer->buffer->width, buffer->buffer->height); 0, 0, // source x,y
0, 0, // mask x,y
dst_box.x, dst_box.y, // dest x,y
dst_box.width, dst_box.height // composite width,height
);
pixman_image_set_transform(texture->image, NULL); pixman_image_set_transform(texture->image, NULL);
} else { } else {