diff --git a/src/compositor/meta-shaped-texture.c b/src/compositor/meta-shaped-texture.c index d8c250fc9..8de173bf7 100644 --- a/src/compositor/meta-shaped-texture.c +++ b/src/compositor/meta-shaped-texture.c @@ -29,6 +29,7 @@ #include #include "meta-shaped-texture-private.h" +#include "meta-texture-rectangle.h" #include #include /* for gdk_rectangle_intersect() */ @@ -38,6 +39,7 @@ #include "core/boxes-private.h" #include "meta-cullable.h" +#include static void meta_shaped_texture_dispose (GObject *object); @@ -55,8 +57,12 @@ static void meta_shaped_texture_get_preferred_height (ClutterActor *self, static gboolean meta_shaped_texture_get_paint_volume (ClutterActor *self, ClutterPaintVolume *volume); +static void disable_backing_store (MetaShapedTexture *stex); + static void cullable_iface_init (MetaCullableInterface *iface); +static gboolean meta_debug_show_backing_store = FALSE; + G_DEFINE_TYPE_WITH_CODE (MetaShapedTexture, meta_shaped_texture, CLUTTER_TYPE_ACTOR, G_IMPLEMENT_INTERFACE (META_TYPE_CULLABLE, cullable_iface_init)); @@ -72,6 +78,14 @@ enum { static guint signals[LAST_SIGNAL]; +typedef struct +{ + CoglTexture *texture; + CoglTexture *mask_texture; + cairo_surface_t *mask_surface; + cairo_region_t *region; +} MetaTextureBackingStore; + struct _MetaShapedTexturePrivate { MetaTextureTower *paint_tower; @@ -93,6 +107,16 @@ struct _MetaShapedTexturePrivate cairo_region_t *clip_region; cairo_region_t *unobscured_region; + /* textures get corrupted on suspend, so save them */ + cairo_surface_t *saved_base_surface; + cairo_surface_t *saved_mask_surface; + + /* We can't just restore external textures, so we need to track + * which parts of the external texture are freshly drawn from + * the client after corruption, and fill in the rest from our + * saved snapshot */ + MetaTextureBackingStore *backing_store; + guint tex_width, tex_height; guint fallback_width, fallback_height; @@ -120,12 +144,19 @@ meta_shaped_texture_class_init (MetaShapedTextureClass *klass) G_TYPE_NONE, 0); g_type_class_add_private (klass, sizeof (MetaShapedTexturePrivate)); + + if (g_getenv ("MUTTER_DEBUG_BACKING_STORE")) + meta_debug_show_backing_store = TRUE; } static void meta_shaped_texture_init (MetaShapedTexture *self) { MetaShapedTexturePrivate *priv; + MetaBackend *backend = meta_get_backend (); + ClutterBackend *clutter_backend = clutter_get_default_backend (); + CoglContext *cogl_context = + clutter_backend_get_cogl_context (clutter_backend); priv = self->priv = META_SHAPED_TEXTURE_GET_PRIVATE (self); @@ -135,6 +166,12 @@ meta_shaped_texture_init (MetaShapedTexture *self) priv->mask_texture = NULL; priv->create_mipmaps = TRUE; priv->is_y_inverted = TRUE; + + if (cogl_has_feature (cogl_context, COGL_FEATURE_ID_UNSTABLE_TEXTURES)) + { + g_signal_connect_object (backend, "suspending", G_CALLBACK (meta_shaped_texture_save), self, G_CONNECT_SWAPPED); + g_signal_connect_object (backend, "resuming", G_CALLBACK (meta_shaped_texture_restore), self, G_CONNECT_SWAPPED); + } } static void @@ -210,25 +247,74 @@ meta_shaped_texture_dispose (GObject *object) G_OBJECT_CLASS (meta_shaped_texture_parent_class)->dispose (object); } +static int +get_layer_indices (MetaShapedTexture *stex, + int *main_layer_index, + int *backing_mask_layer_index, + int *backing_layer_index, + int *mask_layer_index) +{ + MetaShapedTexturePrivate *priv = stex->priv; + int next_layer_index = 0; + + if (main_layer_index) + *main_layer_index = next_layer_index; + + next_layer_index++; + + if (priv->backing_store) + { + if (backing_mask_layer_index) + *backing_mask_layer_index = next_layer_index; + next_layer_index++; + if (backing_layer_index) + *backing_layer_index = next_layer_index; + next_layer_index++; + } + else + { + if (backing_mask_layer_index) + *backing_mask_layer_index = -1; + if (backing_layer_index) + *backing_layer_index = -1; + } + + if (mask_layer_index) + *mask_layer_index = next_layer_index; + + return next_layer_index; +} + static CoglPipeline * get_base_pipeline (MetaShapedTexture *stex, CoglContext *ctx) { MetaShapedTexturePrivate *priv = stex->priv; CoglPipeline *pipeline; + int main_layer_index; + int backing_layer_index; + int backing_mask_layer_index; + int i, number_of_layers; if (priv->base_pipeline) return priv->base_pipeline; pipeline = cogl_pipeline_new (ctx); - cogl_pipeline_set_layer_wrap_mode_s (pipeline, 0, - COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE); - cogl_pipeline_set_layer_wrap_mode_t (pipeline, 0, - COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE); - cogl_pipeline_set_layer_wrap_mode_s (pipeline, 1, - COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE); - cogl_pipeline_set_layer_wrap_mode_t (pipeline, 1, - COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE); + + number_of_layers = get_layer_indices (stex, + &main_layer_index, + &backing_mask_layer_index, + &backing_layer_index, + NULL); + + for (i = 0; i < number_of_layers; i++) + { + cogl_pipeline_set_layer_wrap_mode_s (pipeline, i, + COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE); + cogl_pipeline_set_layer_wrap_mode_t (pipeline, i, + COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE); + } + if (!priv->is_y_inverted) { CoglMatrix matrix; @@ -236,11 +322,26 @@ get_base_pipeline (MetaShapedTexture *stex, cogl_matrix_init_identity (&matrix); cogl_matrix_scale (&matrix, 1, -1, 1); cogl_matrix_translate (&matrix, 0, -1, 0); - cogl_pipeline_set_layer_matrix (pipeline, 0, &matrix); + cogl_pipeline_set_layer_matrix (pipeline, main_layer_index, &matrix); + } + + if (priv->backing_store) + { + g_autofree char *backing_description = NULL; + cogl_pipeline_set_layer_combine (pipeline, backing_mask_layer_index, + "RGBA = REPLACE(PREVIOUS)", + NULL); + backing_description = g_strdup_printf ("RGBA = INTERPOLATE(PREVIOUS, TEXTURE_%d, TEXTURE_%d[A])", + backing_layer_index, + backing_mask_layer_index); + cogl_pipeline_set_layer_combine (pipeline, + backing_layer_index, + backing_description, + NULL); } if (priv->snippet) - cogl_pipeline_add_layer_snippet (pipeline, 0, priv->snippet); + cogl_pipeline_add_layer_snippet (pipeline, main_layer_index, priv->snippet); priv->base_pipeline = pipeline; @@ -260,12 +361,15 @@ get_masked_pipeline (MetaShapedTexture *stex, { MetaShapedTexturePrivate *priv = stex->priv; CoglPipeline *pipeline; + int mask_layer_index; if (priv->masked_pipeline) return priv->masked_pipeline; + get_layer_indices (stex, NULL, NULL, NULL, &mask_layer_index); + pipeline = cogl_pipeline_copy (get_base_pipeline (stex, ctx)); - cogl_pipeline_set_layer_combine (pipeline, 1, + cogl_pipeline_set_layer_combine (pipeline, mask_layer_index, "RGBA = MODULATE (PREVIOUS, TEXTURE[A])", NULL); @@ -340,6 +444,8 @@ set_cogl_texture (MetaShapedTexture *stex, if (priv->texture) cogl_object_unref (priv->texture); + g_clear_pointer (&priv->saved_base_surface, cairo_surface_destroy); + priv->texture = cogl_tex; if (cogl_tex != NULL) @@ -385,6 +491,10 @@ do_paint (MetaShapedTexture *stex, CoglContext *ctx; ClutterActorBox alloc; CoglPipelineFilter filter; + int main_layer_index; + int backing_mask_layer_index; + int backing_layer_index; + int mask_layer_index; tex_width = priv->tex_width; tex_height = priv->tex_height; @@ -447,6 +557,12 @@ do_paint (MetaShapedTexture *stex, } } + get_layer_indices (stex, + &main_layer_index, + &backing_mask_layer_index, + &backing_layer_index, + &mask_layer_index); + /* First, paint the unblended parts, which are part of the opaque region. */ if (use_opaque_region) { @@ -468,8 +584,24 @@ do_paint (MetaShapedTexture *stex, if (!cairo_region_is_empty (region)) { opaque_pipeline = get_unblended_pipeline (stex, ctx); - cogl_pipeline_set_layer_texture (opaque_pipeline, 0, paint_tex); - cogl_pipeline_set_layer_filters (opaque_pipeline, 0, filter, filter); + cogl_pipeline_set_layer_texture (opaque_pipeline, main_layer_index, paint_tex); + cogl_pipeline_set_layer_filters (opaque_pipeline, main_layer_index, filter, filter); + + if (priv->backing_store) + { + cogl_pipeline_set_layer_texture (opaque_pipeline, + backing_mask_layer_index, + priv->backing_store->mask_texture); + cogl_pipeline_set_layer_filters (opaque_pipeline, + backing_mask_layer_index, + filter, filter); + cogl_pipeline_set_layer_texture (opaque_pipeline, + backing_layer_index, + priv->backing_store->texture); + cogl_pipeline_set_layer_filters (opaque_pipeline, + backing_layer_index, + filter, filter); + } n_rects = cairo_region_num_rectangles (region); for (i = 0; i < n_rects; i++) @@ -504,12 +636,28 @@ do_paint (MetaShapedTexture *stex, else { blended_pipeline = get_masked_pipeline (stex, ctx); - cogl_pipeline_set_layer_texture (blended_pipeline, 1, priv->mask_texture); - cogl_pipeline_set_layer_filters (blended_pipeline, 1, filter, filter); + cogl_pipeline_set_layer_texture (blended_pipeline, mask_layer_index, priv->mask_texture); + cogl_pipeline_set_layer_filters (blended_pipeline, mask_layer_index, filter, filter); } - cogl_pipeline_set_layer_texture (blended_pipeline, 0, paint_tex); - cogl_pipeline_set_layer_filters (blended_pipeline, 0, filter, filter); + cogl_pipeline_set_layer_texture (blended_pipeline, main_layer_index, paint_tex); + cogl_pipeline_set_layer_filters (blended_pipeline, main_layer_index, filter, filter); + + if (priv->backing_store) + { + cogl_pipeline_set_layer_texture (blended_pipeline, + backing_mask_layer_index, + priv->backing_store->mask_texture); + cogl_pipeline_set_layer_filters (blended_pipeline, + backing_mask_layer_index, + filter, filter); + cogl_pipeline_set_layer_texture (blended_pipeline, + backing_layer_index, + priv->backing_store->texture); + cogl_pipeline_set_layer_filters (blended_pipeline, + backing_layer_index, + filter, filter); + } CoglColor color; cogl_color_init_from_4ub (&color, opacity, opacity, opacity, opacity); @@ -725,6 +873,7 @@ meta_shaped_texture_set_mask_texture (MetaShapedTexture *stex, priv = stex->priv; g_clear_pointer (&priv->mask_texture, cogl_object_unref); + g_clear_pointer (&priv->saved_mask_surface, cairo_surface_destroy); if (mask_texture != NULL) { @@ -746,6 +895,66 @@ meta_shaped_texture_is_obscured (MetaShapedTexture *self) return FALSE; } +static void +meta_texture_backing_store_redraw_mask (MetaTextureBackingStore *backing_store) +{ + CoglError *error = NULL; + + if (!cogl_texture_set_data (backing_store->mask_texture, COGL_PIXEL_FORMAT_A_8, + cairo_image_surface_get_stride (backing_store->mask_surface), + cairo_image_surface_get_data (backing_store->mask_surface), 0, + &error)) + { + + g_warning ("Failed to update backing mask texture"); + g_clear_pointer (&error, cogl_error_free); + } +} + +static gboolean +meta_texture_backing_store_shrink (MetaTextureBackingStore *backing_store, + const cairo_rectangle_int_t *area) +{ + cairo_t *cr; + + cairo_region_subtract_rectangle (backing_store->region, area); + + /* If the client has finally redrawn the entire surface, we can + * ditch our snapshot + */ + if (cairo_region_is_empty (backing_store->region)) + return FALSE; + + cr = cairo_create (backing_store->mask_surface); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + gdk_cairo_region (cr, backing_store->region); + cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); + cairo_fill (cr); + cairo_destroy (cr); + + meta_texture_backing_store_redraw_mask (backing_store); + + return TRUE; +} + +static void +shrink_backing_region (MetaShapedTexture *stex, + const cairo_rectangle_int_t *area) +{ + MetaShapedTexturePrivate *priv = stex->priv; + gboolean still_backing_texture; + + if (!priv->backing_store) + return; + + still_backing_texture = + meta_texture_backing_store_shrink (priv->backing_store, area); + + if (!still_backing_texture) + disable_backing_store (stex); +} + /** * meta_shaped_texture_update_area: * @stex: #MetaShapedTexture @@ -775,6 +984,8 @@ meta_shaped_texture_update_area (MetaShapedTexture *stex, if (priv->texture == NULL) return FALSE; + shrink_backing_region (stex, &clip); + meta_texture_tower_update_area (priv->paint_tower, x, y, width, height); unobscured_region = effective_unobscured_region (stex); @@ -919,8 +1130,9 @@ should_get_via_offscreen (MetaShapedTexture *stex) } static cairo_surface_t * -get_image_via_offscreen (MetaShapedTexture *stex, - cairo_rectangle_int_t *clip) +get_image_via_offscreen (MetaShapedTexture *stex, + cairo_rectangle_int_t *clip, + CoglTexture **texture) { MetaShapedTexturePrivate *priv = stex->priv; ClutterBackend *clutter_backend = clutter_get_default_backend (); @@ -1015,9 +1227,29 @@ get_image_via_offscreen (MetaShapedTexture *stex, clip->width, clip->height, CLUTTER_CAIRO_FORMAT_ARGB32, cairo_image_surface_get_data (surface)); + cairo_surface_mark_dirty (surface); + + if (texture) + { + *texture = cogl_object_ref (image_texture); + + if (G_UNLIKELY (meta_debug_show_backing_store)) + { + cairo_t *cr; + + cr = cairo_create (surface); + cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 0.75); + cairo_paint (cr); + cairo_destroy (cr); + } + + cogl_texture_set_data (*texture, CLUTTER_CAIRO_FORMAT_ARGB32, + cairo_image_surface_get_stride (surface), + cairo_image_surface_get_data (surface), 0, NULL); + } + cogl_object_unref (fb); - cairo_surface_mark_dirty (surface); return surface; } @@ -1078,7 +1310,7 @@ meta_shaped_texture_get_image (MetaShapedTexture *stex, } if (should_get_via_offscreen (stex)) - return get_image_via_offscreen (stex, transformed_clip); + return get_image_via_offscreen (stex, transformed_clip, NULL); if (transformed_clip) texture = cogl_texture_new_from_sub_texture (texture, @@ -1139,6 +1371,226 @@ meta_shaped_texture_get_image (MetaShapedTexture *stex, return surface; } +static void +meta_texture_backing_store_free (MetaTextureBackingStore *backing_store) +{ + g_clear_pointer (&backing_store->texture, cogl_object_unref); + g_clear_pointer (&backing_store->mask_texture, cogl_object_unref); + g_clear_pointer (&backing_store->mask_surface, cairo_surface_destroy); + g_clear_pointer (&backing_store->region, cairo_region_destroy); + + g_slice_free (MetaTextureBackingStore, backing_store); +} + +static MetaTextureBackingStore * +meta_texture_backing_store_new (CoglTexture *texture) +{ + MetaTextureBackingStore *backing_store = NULL; + ClutterBackend *backend = clutter_get_default_backend (); + CoglContext *context = clutter_backend_get_cogl_context (backend); + CoglTexture *mask_texture = NULL; + guchar *mask_data; + int width, height, stride; + cairo_surface_t *surface; + cairo_region_t *region; + cairo_rectangle_int_t backing_rectangle; + + width = cogl_texture_get_width (texture); + height = cogl_texture_get_height (texture); + stride = cairo_format_stride_for_width (CAIRO_FORMAT_A8, width); + + /* we start off by only letting the backing texture through, and none of the real texture */ + backing_rectangle.x = 0; + backing_rectangle.y = 0; + backing_rectangle.width = width; + backing_rectangle.height = height; + + region = cairo_region_create_rectangle (&backing_rectangle); + + /* initialize mask to transparent, so the entire backing store shows through + * up front + */ + mask_data = g_malloc0 (stride * height); + surface = cairo_image_surface_create_for_data (mask_data, + CAIRO_FORMAT_A8, + width, + height, + stride); + + if (meta_texture_rectangle_check (texture)) + { + mask_texture = COGL_TEXTURE (cogl_texture_rectangle_new_with_size (context, + width, + height)); + cogl_texture_set_components (mask_texture, COGL_TEXTURE_COMPONENTS_A); + cogl_texture_set_region (mask_texture, + 0, 0, + 0, 0, + width, height, + width, height, + COGL_PIXEL_FORMAT_A_8, + stride, mask_data); + } + else + { + CoglError *error = NULL; + + mask_texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (context, width, height, + COGL_PIXEL_FORMAT_A_8, + stride, mask_data, &error)); + + if (error) + { + g_warning ("Failed to allocate mask texture: %s", error->message); + cogl_error_free (error); + } + } + + if (mask_texture) + { + backing_store = g_slice_new0 (MetaTextureBackingStore); + backing_store->texture = cogl_object_ref (texture); + backing_store->mask_texture = mask_texture; + backing_store->mask_surface = surface; + backing_store->region = region; + } + + return backing_store; +} + +static void +enable_backing_store (MetaShapedTexture *stex, + CoglTexture *texture) +{ + MetaShapedTexturePrivate *priv = stex->priv; + + g_clear_pointer (&priv->backing_store, meta_texture_backing_store_free); + + priv->backing_store = meta_texture_backing_store_new (texture); + + meta_shaped_texture_reset_pipelines (stex); +} + +static void +disable_backing_store (MetaShapedTexture *stex) +{ + MetaShapedTexturePrivate *priv = stex->priv; + + g_clear_pointer (&priv->backing_store, meta_texture_backing_store_free); + + meta_shaped_texture_reset_pipelines (stex); +} + +void +meta_shaped_texture_save (MetaShapedTexture *stex) +{ + + CoglTexture *texture, *mask_texture; + MetaShapedTexturePrivate *priv = stex->priv; + cairo_surface_t *surface; + + g_return_if_fail (META_IS_SHAPED_TEXTURE (stex)); + + texture = COGL_TEXTURE (priv->texture); + + if (texture == NULL) + return; + + g_clear_pointer (&priv->saved_base_surface, cairo_surface_destroy); + g_clear_pointer (&priv->saved_mask_surface, cairo_surface_destroy); + g_clear_pointer (&priv->backing_store, meta_texture_backing_store_free); + + if (should_get_via_offscreen (stex)) + { + CoglTexture *backing_texture; + + meta_shaped_texture_reset_pipelines (stex); + + surface = get_image_via_offscreen (stex, NULL, &backing_texture); + + enable_backing_store (stex, backing_texture); + cogl_object_unref (backing_texture); + } + else + { + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + cogl_texture_get_width (texture), + cogl_texture_get_height (texture)); + + cogl_texture_get_data (texture, CLUTTER_CAIRO_FORMAT_ARGB32, + cairo_image_surface_get_stride (surface), + cairo_image_surface_get_data (surface)); + } + + priv->saved_base_surface = surface; + + mask_texture = stex->priv->mask_texture; + if (mask_texture != NULL) + { + cairo_surface_t *mask_surface; + + mask_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + cogl_texture_get_width (mask_texture), + cogl_texture_get_height (mask_texture)); + + cogl_texture_get_data (mask_texture, CLUTTER_CAIRO_FORMAT_ARGB32, + cairo_image_surface_get_stride (mask_surface), + cairo_image_surface_get_data (mask_surface)); + + cairo_surface_mark_dirty (mask_surface); + + priv->saved_mask_surface = mask_surface; + } +} + +void +meta_shaped_texture_restore (MetaShapedTexture *stex) +{ + MetaShapedTexturePrivate *priv = stex->priv; + CoglTexture *texture; + CoglError *error = NULL; + + texture = meta_shaped_texture_get_texture (stex); + + if (texture == NULL) + return; + + if (priv->mask_texture) + { + if (!cogl_texture_set_data (priv->mask_texture, CLUTTER_CAIRO_FORMAT_ARGB32, + cairo_image_surface_get_stride (priv->saved_mask_surface), + cairo_image_surface_get_data (priv->saved_mask_surface), 0, + &error)) + { + g_warning ("Failed to restore mask texture"); + g_clear_pointer (&error, cogl_error_free); + } + g_clear_pointer (&priv->saved_mask_surface, cairo_surface_destroy); + } + + /* if the main texture doesn't support direct writes, then + * write to the local backing texture instead, and blend old + * versus new at paint time. + */ + if (priv->backing_store) + { + meta_texture_backing_store_redraw_mask (priv->backing_store); + texture = priv->backing_store->texture; + } + + if (!cogl_texture_set_data (texture, CLUTTER_CAIRO_FORMAT_ARGB32, + cairo_image_surface_get_stride (priv->saved_base_surface), + cairo_image_surface_get_data (priv->saved_base_surface), 0, + &error)) + { + g_warning ("Failed to restore texture"); + g_clear_pointer (&error, cogl_error_free); + } + g_clear_pointer (&priv->saved_base_surface, cairo_surface_destroy); + + clutter_actor_queue_redraw (CLUTTER_ACTOR (stex)); +} + void meta_shaped_texture_set_fallback_size (MetaShapedTexture *self, guint fallback_width, diff --git a/src/meta/meta-shaped-texture.h b/src/meta/meta-shaped-texture.h index 80b23f2ea..fc0567c9f 100644 --- a/src/meta/meta-shaped-texture.h +++ b/src/meta/meta-shaped-texture.h @@ -81,6 +81,8 @@ void meta_shaped_texture_set_opaque_region (MetaShapedTexture *stex, cairo_surface_t * meta_shaped_texture_get_image (MetaShapedTexture *stex, cairo_rectangle_int_t *clip); +void meta_shaped_texture_save (MetaShapedTexture *self); +void meta_shaped_texture_restore (MetaShapedTexture *self); G_END_DECLS #endif /* __META_SHAPED_TEXTURE_H__ */