mutter/src/compositor/meta-surface-actor.c
Georges Basile Stavracas Neto fb9e8768a3 window-actor: Handle geometry scale
Geometry scale is applied to each surface individually, using
Clutter scales, and not only this breaks subsurfaces, it also
pollutes the toolkit and makes the actor tree slightly too
fragile. If GNOME Shell mistakenly tries to set the actor scale
of any of these surfaces, for example, various artifacts might
happen.

Move geometry scale handling to MetaWindowActor. It is applied
as a child transform operation, so that the Clutter-managed
scale properties are left untouched.

In the future where the entirety of the window is managed by a
ClutterContent itself, the geometry scale will be applied
directly into the transform matrix of MetaWindowActor. However,
doing that now would break the various ClutterClones used by
GNOME Shell, so the child transform is an acceptable compromise
during this transition.

https://gitlab.gnome.org/GNOME/mutter/merge_requests/409
2019-08-23 13:23:07 +00:00

623 lines
18 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/**
* SECTION:meta-surface-actor
* @title: MetaSurfaceActor
* @short_description: An actor representing a surface in the scene graph
*
* MetaSurfaceActor is an abstract class which represents a surface in the
* Clutter scene graph. A subclass can implement the specifics of a surface
* depending on the way it is handled by a display protocol.
*
* An important feature of #MetaSurfaceActor is that it allows you to set an
* "input region": all events that occur in the surface, but outside of the
* input region are to be explicitly ignored. By default, this region is to
* %NULL, which means events on the whole surface is allowed.
*/
#include "config.h"
#include "compositor/meta-surface-actor.h"
#include "clutter/clutter.h"
#include "compositor/meta-cullable.h"
#include "compositor/meta-shaped-texture-private.h"
#include "compositor/meta-window-actor-private.h"
#include "compositor/region-utils.h"
#include "meta/meta-shaped-texture.h"
typedef struct _MetaSurfaceActorPrivate
{
MetaShapedTexture *texture;
cairo_region_t *input_region;
/* MetaCullable regions, see that documentation for more details */
cairo_region_t *clip_region;
cairo_region_t *unobscured_region;
/* Freeze/thaw accounting */
cairo_region_t *pending_damage;
guint frozen : 1;
} MetaSurfaceActorPrivate;
static void cullable_iface_init (MetaCullableInterface *iface);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (MetaSurfaceActor, meta_surface_actor, CLUTTER_TYPE_ACTOR,
G_ADD_PRIVATE (MetaSurfaceActor)
G_IMPLEMENT_INTERFACE (META_TYPE_CULLABLE, cullable_iface_init));
enum
{
REPAINT_SCHEDULED,
SIZE_CHANGED,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL];
static cairo_region_t *
effective_unobscured_region (MetaSurfaceActor *surface_actor)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (surface_actor);
ClutterActor *actor;
/* Fail if we have any mapped clones. */
actor = CLUTTER_ACTOR (surface_actor);
do
{
if (clutter_actor_has_mapped_clones (actor))
return NULL;
actor = clutter_actor_get_parent (actor);
}
while (actor != NULL);
return priv->unobscured_region;
}
static void
set_unobscured_region (MetaSurfaceActor *surface_actor,
cairo_region_t *unobscured_region)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (surface_actor);
g_clear_pointer (&priv->unobscured_region, cairo_region_destroy);
if (unobscured_region)
{
cairo_rectangle_int_t bounds = { 0, };
float width, height;
clutter_content_get_preferred_size (CLUTTER_CONTENT (priv->texture),
&width,
&height);
bounds = (cairo_rectangle_int_t) {
.width = width,
.height = height,
};
priv->unobscured_region = cairo_region_copy (unobscured_region);
cairo_region_intersect_rectangle (priv->unobscured_region, &bounds);
}
}
static void
set_clip_region (MetaSurfaceActor *surface_actor,
cairo_region_t *clip_region)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (surface_actor);
g_clear_pointer (&priv->clip_region, cairo_region_destroy);
if (clip_region)
priv->clip_region = cairo_region_copy (clip_region);
}
static void
meta_surface_actor_paint (ClutterActor *actor)
{
MetaSurfaceActor *surface_actor = META_SURFACE_ACTOR (actor);
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (surface_actor);
if (priv->clip_region && cairo_region_is_empty (priv->clip_region))
return;
CLUTTER_ACTOR_CLASS (meta_surface_actor_parent_class)->paint (actor);
}
static void
meta_surface_actor_pick (ClutterActor *actor,
const ClutterColor *color)
{
MetaSurfaceActor *self = META_SURFACE_ACTOR (actor);
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
ClutterActorIter iter;
ClutterActor *child;
if (!clutter_actor_should_pick_paint (actor))
return;
/* If there is no region then use the regular pick */
if (priv->input_region == NULL)
CLUTTER_ACTOR_CLASS (meta_surface_actor_parent_class)->pick (actor, color);
else
{
int n_rects;
float *rectangles;
int i;
CoglPipeline *pipeline;
CoglContext *ctx;
CoglFramebuffer *fb;
CoglColor cogl_color;
n_rects = cairo_region_num_rectangles (priv->input_region);
rectangles = g_alloca (sizeof (float) * 4 * n_rects);
for (i = 0; i < n_rects; i++)
{
cairo_rectangle_int_t rect;
int pos = i * 4;
cairo_region_get_rectangle (priv->input_region, i, &rect);
rectangles[pos + 0] = rect.x;
rectangles[pos + 1] = rect.y;
rectangles[pos + 2] = rect.x + rect.width;
rectangles[pos + 3] = rect.y + rect.height;
}
ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
fb = cogl_get_draw_framebuffer ();
cogl_color_init_from_4ub (&cogl_color, color->red, color->green, color->blue, color->alpha);
pipeline = cogl_pipeline_new (ctx);
cogl_pipeline_set_color (pipeline, &cogl_color);
cogl_framebuffer_draw_rectangles (fb, pipeline, rectangles, n_rects);
cogl_object_unref (pipeline);
}
clutter_actor_iter_init (&iter, actor);
while (clutter_actor_iter_next (&iter, &child))
clutter_actor_paint (child);
}
static gboolean
meta_surface_actor_get_paint_volume (ClutterActor *actor,
ClutterPaintVolume *volume)
{
return clutter_paint_volume_set_from_allocation (volume, actor);
}
static void
meta_surface_actor_dispose (GObject *object)
{
MetaSurfaceActor *self = META_SURFACE_ACTOR (object);
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
g_clear_pointer (&priv->input_region, cairo_region_destroy);
set_unobscured_region (self, NULL);
set_clip_region (self, NULL);
G_OBJECT_CLASS (meta_surface_actor_parent_class)->dispose (object);
}
static void
meta_surface_actor_class_init (MetaSurfaceActorClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
object_class->dispose = meta_surface_actor_dispose;
actor_class->paint = meta_surface_actor_paint;
actor_class->pick = meta_surface_actor_pick;
actor_class->get_paint_volume = meta_surface_actor_get_paint_volume;
signals[REPAINT_SCHEDULED] = g_signal_new ("repaint-scheduled",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[SIZE_CHANGED] = g_signal_new ("size-changed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static void
meta_surface_actor_cull_out (MetaCullable *cullable,
cairo_region_t *unobscured_region,
cairo_region_t *clip_region)
{
MetaSurfaceActor *surface_actor = META_SURFACE_ACTOR (cullable);
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (surface_actor);
uint8_t opacity = clutter_actor_get_opacity (CLUTTER_ACTOR (cullable));
set_unobscured_region (surface_actor, unobscured_region);
set_clip_region (surface_actor, clip_region);
if (opacity == 0xff)
{
MetaWindowActor *window_actor;
cairo_region_t *scaled_opaque_region;
cairo_region_t *opaque_region;
int geometry_scale = 1;
opaque_region = meta_shaped_texture_get_opaque_region (priv->texture);
if (!opaque_region)
return;
window_actor =
meta_window_actor_from_actor (CLUTTER_ACTOR (surface_actor));
if (window_actor)
geometry_scale = meta_window_actor_get_geometry_scale (window_actor);
scaled_opaque_region = meta_region_scale (opaque_region, geometry_scale);
if (unobscured_region)
cairo_region_subtract (unobscured_region, scaled_opaque_region);
if (clip_region)
cairo_region_subtract (clip_region, scaled_opaque_region);
cairo_region_destroy (scaled_opaque_region);
}
}
static void
meta_surface_actor_reset_culling (MetaCullable *cullable)
{
MetaSurfaceActor *surface_actor = META_SURFACE_ACTOR (cullable);
set_clip_region (surface_actor, NULL);
}
static void
cullable_iface_init (MetaCullableInterface *iface)
{
iface->cull_out = meta_surface_actor_cull_out;
iface->reset_culling = meta_surface_actor_reset_culling;
}
static void
texture_size_changed (MetaShapedTexture *texture,
gpointer user_data)
{
MetaSurfaceActor *actor = META_SURFACE_ACTOR (user_data);
g_signal_emit (actor, signals[SIZE_CHANGED], 0);
}
static void
meta_surface_actor_init (MetaSurfaceActor *self)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
priv->texture = meta_shaped_texture_new ();
g_signal_connect_object (priv->texture, "size-changed",
G_CALLBACK (texture_size_changed), self, 0);
clutter_actor_set_content (CLUTTER_ACTOR (self),
CLUTTER_CONTENT (priv->texture));
clutter_actor_set_request_mode (CLUTTER_ACTOR (self),
CLUTTER_REQUEST_CONTENT_SIZE);
}
cairo_surface_t *
meta_surface_actor_get_image (MetaSurfaceActor *self,
cairo_rectangle_int_t *clip)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
return meta_shaped_texture_get_image (priv->texture, clip);
}
MetaShapedTexture *
meta_surface_actor_get_texture (MetaSurfaceActor *self)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
return priv->texture;
}
static void
meta_surface_actor_update_area (MetaSurfaceActor *self,
int x, int y, int width, int height)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
gboolean repaint_scheduled = FALSE;
cairo_rectangle_int_t clip;
if (meta_shaped_texture_update_area (priv->texture, x, y, width, height, &clip))
{
cairo_region_t *unobscured_region;
unobscured_region = effective_unobscured_region (self);
if (unobscured_region)
{
cairo_region_t *intersection;
if (cairo_region_is_empty (unobscured_region))
return;
intersection = cairo_region_copy (unobscured_region);
cairo_region_intersect_rectangle (intersection, &clip);
if (!cairo_region_is_empty (intersection))
{
cairo_rectangle_int_t damage_rect;
cairo_region_get_extents (intersection, &damage_rect);
clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (self), &damage_rect);
repaint_scheduled = TRUE;
}
cairo_region_destroy (intersection);
}
else
{
clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (self), &clip);
repaint_scheduled = TRUE;
}
}
if (repaint_scheduled)
g_signal_emit (self, signals[REPAINT_SCHEDULED], 0);
}
gboolean
meta_surface_actor_is_obscured (MetaSurfaceActor *self)
{
cairo_region_t *unobscured_region;
unobscured_region = effective_unobscured_region (self);
if (unobscured_region)
return cairo_region_is_empty (unobscured_region);
else
return FALSE;
}
void
meta_surface_actor_set_input_region (MetaSurfaceActor *self,
cairo_region_t *region)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
if (priv->input_region)
cairo_region_destroy (priv->input_region);
if (region)
priv->input_region = cairo_region_reference (region);
else
priv->input_region = NULL;
}
void
meta_surface_actor_set_opaque_region (MetaSurfaceActor *self,
cairo_region_t *region)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
meta_shaped_texture_set_opaque_region (priv->texture, region);
}
cairo_region_t *
meta_surface_actor_get_opaque_region (MetaSurfaceActor *self)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
return meta_shaped_texture_get_opaque_region (priv->texture);
}
static gboolean
is_frozen (MetaSurfaceActor *self)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
return priv->frozen;
}
void
meta_surface_actor_process_damage (MetaSurfaceActor *self,
int x, int y, int width, int height)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
if (is_frozen (self))
{
/* The window is frozen due to an effect in progress: we ignore damage
* here on the off chance that this will stop the corresponding
* texture_from_pixmap from being update.
*
* pending_damage tracks any damage that happened while the window was
* frozen so that when can apply it when the window becomes unfrozen.
*
* It should be noted that this is an unreliable mechanism since it's
* quite likely that drivers will aim to provide a zero-copy
* implementation of the texture_from_pixmap extension and in those cases
* any drawing done to the window is always immediately reflected in the
* texture regardless of damage event handling.
*/
cairo_rectangle_int_t rect = { .x = x, .y = y, .width = width, .height = height };
if (!priv->pending_damage)
priv->pending_damage = cairo_region_create_rectangle (&rect);
else
cairo_region_union_rectangle (priv->pending_damage, &rect);
return;
}
META_SURFACE_ACTOR_GET_CLASS (self)->process_damage (self, x, y, width, height);
if (meta_surface_actor_is_visible (self))
meta_surface_actor_update_area (self, x, y, width, height);
}
void
meta_surface_actor_pre_paint (MetaSurfaceActor *self)
{
META_SURFACE_ACTOR_GET_CLASS (self)->pre_paint (self);
}
gboolean
meta_surface_actor_is_argb32 (MetaSurfaceActor *self)
{
MetaShapedTexture *stex = meta_surface_actor_get_texture (self);
CoglTexture *texture = meta_shaped_texture_get_texture (stex);
/* If we don't have a texture, like during initialization, assume
* that we're ARGB32.
*
* If we are unredirected and we have no texture assume that we are
* not ARGB32 otherwise we wouldn't be unredirected in the first
* place. This prevents us from continually redirecting and
* unredirecting on every paint.
*/
if (!texture)
return !meta_surface_actor_is_unredirected (self);
switch (cogl_texture_get_components (texture))
{
case COGL_TEXTURE_COMPONENTS_A:
case COGL_TEXTURE_COMPONENTS_RGBA:
return TRUE;
case COGL_TEXTURE_COMPONENTS_RG:
case COGL_TEXTURE_COMPONENTS_RGB:
case COGL_TEXTURE_COMPONENTS_DEPTH:
return FALSE;
default:
g_assert_not_reached ();
return FALSE;
}
}
gboolean
meta_surface_actor_is_visible (MetaSurfaceActor *self)
{
return META_SURFACE_ACTOR_GET_CLASS (self)->is_visible (self);
}
void
meta_surface_actor_set_frozen (MetaSurfaceActor *self,
gboolean frozen)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
priv->frozen = frozen;
if (!frozen && priv->pending_damage)
{
int i, n_rects = cairo_region_num_rectangles (priv->pending_damage);
cairo_rectangle_int_t rect;
/* Since we ignore damage events while a window is frozen for certain effects
* we need to apply the tracked damage now. */
for (i = 0; i < n_rects; i++)
{
cairo_region_get_rectangle (priv->pending_damage, i, &rect);
meta_surface_actor_process_damage (self, rect.x, rect.y,
rect.width, rect.height);
}
g_clear_pointer (&priv->pending_damage, cairo_region_destroy);
}
}
gboolean
meta_surface_actor_should_unredirect (MetaSurfaceActor *self)
{
return META_SURFACE_ACTOR_GET_CLASS (self)->should_unredirect (self);
}
void
meta_surface_actor_set_unredirected (MetaSurfaceActor *self,
gboolean unredirected)
{
META_SURFACE_ACTOR_GET_CLASS (self)->set_unredirected (self, unredirected);
}
gboolean
meta_surface_actor_is_unredirected (MetaSurfaceActor *self)
{
return META_SURFACE_ACTOR_GET_CLASS (self)->is_unredirected (self);
}
MetaWindow *
meta_surface_actor_get_window (MetaSurfaceActor *self)
{
return META_SURFACE_ACTOR_GET_CLASS (self)->get_window (self);
}
void
meta_surface_actor_set_transform (MetaSurfaceActor *self,
MetaMonitorTransform transform)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
meta_shaped_texture_set_transform (priv->texture, transform);
}
void
meta_surface_actor_set_viewport_src_rect (MetaSurfaceActor *self,
ClutterRect *src_rect)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
meta_shaped_texture_set_viewport_src_rect (priv->texture, src_rect);
}
void
meta_surface_actor_reset_viewport_src_rect (MetaSurfaceActor *self)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
meta_shaped_texture_reset_viewport_src_rect (priv->texture);
}
void
meta_surface_actor_set_viewport_dst_size (MetaSurfaceActor *self,
int dst_width,
int dst_height)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
meta_shaped_texture_set_viewport_dst_size (priv->texture,
dst_width,
dst_height);
}
void
meta_surface_actor_reset_viewport_dst_size (MetaSurfaceActor *self)
{
MetaSurfaceActorPrivate *priv =
meta_surface_actor_get_instance_private (self);
meta_shaped_texture_reset_viewport_dst_size (priv->texture);
}