shaped-texture: Get transformed textures pixels via offscreen

When a texture is transformed in any way (e.g. Wayland buffer
transforms), we cannot just fetch the pixels from the texture directly
and be done with it, as that will result in getting the untransformed
pixels.

To properly get the pixels in their right form, first draw to an
offscreen framebuffer, using the same method as when painting on the
stage, then read from the framebuffer into a cairo image surface.

https://gitlab.gnome.org/GNOME/mutter/merge_requests/362

Fixes: https://gitlab.gnome.org/GNOME/mutter/issues/408
This commit is contained in:
Jonas Ådahl 2018-12-20 17:34:18 +01:00 committed by Georges Basile Stavracas Neto
parent f9118fb99b
commit c84d7ebc6d

View File

@ -36,6 +36,7 @@
#include "compositor/meta-cullable.h"
#include "compositor/meta-texture-tower.h"
#include "compositor/region-utils.h"
#include "core/boxes-private.h"
#include "meta/meta-shaped-texture.h"
/* MAX_MIPMAPPING_FPS needs to be as small as possible for the best GPU
@ -1038,6 +1039,129 @@ meta_shaped_texture_set_transform (MetaShapedTexture *stex,
invalidate_size (stex);
}
static gboolean
should_get_via_offscreen (MetaShapedTexture *stex)
{
switch (stex->transform)
{
case META_MONITOR_TRANSFORM_90:
case META_MONITOR_TRANSFORM_180:
case META_MONITOR_TRANSFORM_270:
case META_MONITOR_TRANSFORM_FLIPPED:
case META_MONITOR_TRANSFORM_FLIPPED_90:
case META_MONITOR_TRANSFORM_FLIPPED_180:
case META_MONITOR_TRANSFORM_FLIPPED_270:
return TRUE;
case META_MONITOR_TRANSFORM_NORMAL:
break;
}
return FALSE;
}
static cairo_surface_t *
get_image_via_offscreen (MetaShapedTexture *stex,
cairo_rectangle_int_t *clip)
{
ClutterBackend *clutter_backend = clutter_get_default_backend ();
CoglContext *cogl_context =
clutter_backend_get_cogl_context (clutter_backend);
CoglTexture *image_texture;
GError *error = NULL;
CoglOffscreen *offscreen;
CoglFramebuffer *fb;
CoglMatrix projection_matrix;
int fb_width, fb_height;
cairo_rectangle_int_t fallback_clip;
CoglColor clear_color;
cairo_surface_t *surface;
if (cogl_has_feature (cogl_context, COGL_FEATURE_ID_TEXTURE_NPOT))
{
fb_width = stex->dst_width;
fb_height = stex->dst_height;
}
else
{
fb_width = _cogl_util_next_p2 (stex->dst_width);
fb_height = _cogl_util_next_p2 (stex->dst_height);
}
if (!clip)
{
fallback_clip = (cairo_rectangle_int_t) {
.width = stex->dst_width,
.height = stex->dst_height,
};
clip = &fallback_clip;
}
image_texture =
COGL_TEXTURE (cogl_texture_2d_new_with_size (cogl_context,
fb_width, fb_height));
cogl_primitive_texture_set_auto_mipmap (COGL_PRIMITIVE_TEXTURE (image_texture),
FALSE);
if (!cogl_texture_allocate (COGL_TEXTURE (image_texture), &error))
{
g_error_free (error);
cogl_object_unref (image_texture);
return FALSE;
}
if (fb_width != stex->dst_width || fb_height != stex->dst_height)
{
CoglSubTexture *sub_texture;
sub_texture = cogl_sub_texture_new (cogl_context,
image_texture,
0, 0,
stex->dst_width, stex->dst_height);
cogl_object_unref (image_texture);
image_texture = COGL_TEXTURE (sub_texture);
}
offscreen = cogl_offscreen_new_with_texture (COGL_TEXTURE (image_texture));
fb = COGL_FRAMEBUFFER (offscreen);
cogl_object_unref (image_texture);
if (!cogl_framebuffer_allocate (fb, &error))
{
g_error_free (error);
cogl_object_unref (fb);
return FALSE;
}
cogl_framebuffer_push_matrix (fb);
cogl_matrix_init_identity (&projection_matrix);
cogl_matrix_scale (&projection_matrix,
1.0 / (stex->dst_width / 2.0),
-1.0 / (stex->dst_height / 2.0), 0);
cogl_matrix_translate (&projection_matrix,
-(stex->dst_width / 2.0),
-(stex->dst_height / 2.0), 0);
cogl_framebuffer_set_projection_matrix (fb, &projection_matrix);
cogl_color_init_from_4ub (&clear_color, 0, 0, 0, 0);
cogl_framebuffer_clear (fb, COGL_BUFFER_BIT_COLOR, &clear_color);
do_paint (stex, fb, stex->texture, NULL);
cogl_framebuffer_pop_matrix (fb);
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
clip->width, clip->height);
cogl_framebuffer_read_pixels (fb,
clip->x, clip->y,
clip->width, clip->height,
CLUTTER_CAIRO_FORMAT_ARGB32,
cairo_image_surface_get_data (surface));
cogl_object_unref (fb);
cairo_surface_mark_dirty (surface);
return surface;
}
/**
* meta_shaped_texture_get_image:
* @stex: A #MetaShapedTexture
@ -1058,7 +1182,6 @@ meta_shaped_texture_get_image (MetaShapedTexture *stex,
{
cairo_rectangle_int_t *transformed_clip = NULL;
CoglTexture *texture, *mask_texture;
cairo_rectangle_int_t texture_rect = { 0, 0, 0, 0 };
cairo_surface_t *surface;
g_return_val_if_fail (META_IS_SHAPED_TEXTURE (stex), NULL);
@ -1068,17 +1191,36 @@ meta_shaped_texture_get_image (MetaShapedTexture *stex,
if (texture == NULL)
return NULL;
ensure_size_valid (stex);
if (stex->dst_width == 0 || stex->dst_height == 0)
return NULL;
if (clip != NULL)
{
transformed_clip = alloca (sizeof (cairo_rectangle_int_t));
*transformed_clip = *clip;
double tex_scale;
cairo_rectangle_int_t dst_rect;
if (!meta_rectangle_intersect (&texture_rect, transformed_clip,
transformed_clip = alloca (sizeof (cairo_rectangle_int_t));
clutter_actor_get_scale (CLUTTER_ACTOR (stex), &tex_scale, NULL);
meta_rectangle_scale_double (clip, 1.0 / tex_scale,
META_ROUNDING_STRATEGY_GROW,
transformed_clip);
dst_rect = (cairo_rectangle_int_t) {
.width = stex->dst_width,
.height = stex->dst_height,
};
if (!meta_rectangle_intersect (&dst_rect, transformed_clip,
transformed_clip))
return NULL;
}
if (should_get_via_offscreen (stex))
return get_image_via_offscreen (stex, transformed_clip);
if (transformed_clip)
texture = cogl_texture_new_from_sub_texture (texture,
transformed_clip->x,