1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 /**
4  * SECTION:meta-surface-actor
5  * @title: MetaSurfaceActor
6  * @short_description: An actor representing a surface in the scene graph
7  *
8  * MetaSurfaceActor is an abstract class which represents a surface in the
9  * Clutter scene graph. A subclass can implement the specifics of a surface
10  * depending on the way it is handled by a display protocol.
11  *
12  * An important feature of #MetaSurfaceActor is that it allows you to set an
13  * "input region": all events that occur in the surface, but outside of the
14  * input region are to be explicitly ignored. By default, this region is to
15  * %NULL, which means events on the whole surface is allowed.
16  */
17 
18 #include "config.h"
19 
20 #include "compositor/meta-surface-actor.h"
21 
22 #include "clutter/clutter.h"
23 #include "compositor/clutter-utils.h"
24 #include "compositor/meta-cullable.h"
25 #include "compositor/meta-shaped-texture-private.h"
26 #include "compositor/meta-window-actor-private.h"
27 #include "compositor/region-utils.h"
28 #include "meta/meta-shaped-texture.h"
29 
30 typedef struct _MetaSurfaceActorPrivate
31 {
32   MetaShapedTexture *texture;
33 
34   cairo_region_t *input_region;
35 
36   /* MetaCullable regions, see that documentation for more details */
37   cairo_region_t *unobscured_region;
38 
39   /* Freeze/thaw accounting */
40   cairo_region_t *pending_damage;
41   guint frozen : 1;
42 } MetaSurfaceActorPrivate;
43 
44 static void cullable_iface_init (MetaCullableInterface *iface);
45 
46 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (MetaSurfaceActor, meta_surface_actor, CLUTTER_TYPE_ACTOR,
47                                   G_ADD_PRIVATE (MetaSurfaceActor)
48                                   G_IMPLEMENT_INTERFACE (META_TYPE_CULLABLE, cullable_iface_init));
49 
50 enum
51 {
52   REPAINT_SCHEDULED,
53   SIZE_CHANGED,
54 
55   LAST_SIGNAL,
56 };
57 
58 static guint signals[LAST_SIGNAL];
59 
60 typedef enum
61 {
62   IN_STAGE_PERSPECTIVE,
63   IN_ACTOR_PERSPECTIVE
64 } ScalePerspectiveType;
65 
66 static cairo_region_t *
effective_unobscured_region(MetaSurfaceActor * surface_actor)67 effective_unobscured_region (MetaSurfaceActor *surface_actor)
68 {
69   MetaSurfaceActorPrivate *priv =
70     meta_surface_actor_get_instance_private (surface_actor);
71   ClutterActor *actor = CLUTTER_ACTOR (surface_actor);
72 
73   /* Fail if we have any mapped clones. */
74   if (clutter_actor_has_mapped_clones (actor))
75     return NULL;
76 
77   return priv->unobscured_region;
78 }
79 
80 static cairo_region_t*
get_scaled_region(MetaSurfaceActor * surface_actor,cairo_region_t * region,ScalePerspectiveType scale_perspective)81 get_scaled_region (MetaSurfaceActor     *surface_actor,
82                    cairo_region_t       *region,
83                    ScalePerspectiveType  scale_perspective)
84 {
85   MetaWindowActor *window_actor;
86   cairo_region_t *scaled_region = NULL;
87   int geometry_scale;
88   float x, y;
89 
90   window_actor = meta_window_actor_from_actor (CLUTTER_ACTOR (surface_actor));
91   geometry_scale = meta_window_actor_get_geometry_scale (window_actor);
92 
93   clutter_actor_get_position (CLUTTER_ACTOR (surface_actor), &x, &y);
94   cairo_region_translate (region, x, y);
95 
96   switch (scale_perspective)
97     {
98     case IN_STAGE_PERSPECTIVE:
99       scaled_region = meta_region_scale_double (region,
100                                                 geometry_scale,
101                                                 META_ROUNDING_STRATEGY_GROW);
102       break;
103     case IN_ACTOR_PERSPECTIVE:
104       scaled_region = meta_region_scale_double (region,
105                                                 1.0 / geometry_scale,
106                                                 META_ROUNDING_STRATEGY_GROW);
107       break;
108     }
109 
110   g_assert (scaled_region != NULL);
111   cairo_region_translate (region, -x, -y);
112   cairo_region_translate (scaled_region, -x, -y);
113 
114   return scaled_region;
115 }
116 
117 static void
set_unobscured_region(MetaSurfaceActor * surface_actor,cairo_region_t * unobscured_region)118 set_unobscured_region (MetaSurfaceActor *surface_actor,
119                        cairo_region_t   *unobscured_region)
120 {
121   MetaSurfaceActorPrivate *priv =
122     meta_surface_actor_get_instance_private (surface_actor);
123 
124   g_clear_pointer (&priv->unobscured_region, cairo_region_destroy);
125   if (unobscured_region)
126     {
127       if (cairo_region_is_empty (unobscured_region))
128         {
129           priv->unobscured_region = cairo_region_reference (unobscured_region);
130         }
131       else
132         {
133           cairo_rectangle_int_t bounds = { 0, };
134           float width, height;
135 
136           clutter_content_get_preferred_size (CLUTTER_CONTENT (priv->texture),
137                                               &width,
138                                               &height);
139           bounds = (cairo_rectangle_int_t) {
140             .width = width,
141             .height = height,
142           };
143 
144           priv->unobscured_region = get_scaled_region (surface_actor,
145                                                        unobscured_region,
146                                                        IN_ACTOR_PERSPECTIVE);
147 
148           cairo_region_intersect_rectangle (priv->unobscured_region, &bounds);
149         }
150     }
151 }
152 
153 static void
set_clip_region(MetaSurfaceActor * surface_actor,cairo_region_t * clip_region)154 set_clip_region (MetaSurfaceActor *surface_actor,
155                  cairo_region_t   *clip_region)
156 {
157   MetaSurfaceActorPrivate *priv =
158     meta_surface_actor_get_instance_private (surface_actor);
159   MetaShapedTexture *stex = priv->texture;
160 
161   if (clip_region && !cairo_region_is_empty (clip_region))
162     {
163       cairo_region_t *region;
164 
165       region = get_scaled_region (surface_actor,
166                                   clip_region,
167                                   IN_ACTOR_PERSPECTIVE);
168       meta_shaped_texture_set_clip_region (stex, region);
169 
170       cairo_region_destroy (region);
171     }
172   else
173     {
174       meta_shaped_texture_set_clip_region (stex, clip_region);
175     }
176 }
177 
178 static void
meta_surface_actor_pick(ClutterActor * actor,ClutterPickContext * pick_context)179 meta_surface_actor_pick (ClutterActor       *actor,
180                          ClutterPickContext *pick_context)
181 {
182   MetaSurfaceActor *self = META_SURFACE_ACTOR (actor);
183   MetaSurfaceActorPrivate *priv =
184     meta_surface_actor_get_instance_private (self);
185   ClutterActorIter iter;
186   ClutterActor *child;
187 
188   if (!clutter_actor_should_pick (actor, pick_context))
189     return;
190 
191   /* If there is no region then use the regular pick */
192   if (priv->input_region == NULL)
193     {
194       ClutterActorClass *actor_class =
195         CLUTTER_ACTOR_CLASS (meta_surface_actor_parent_class);
196 
197       actor_class->pick (actor, pick_context);
198     }
199   else
200     {
201       int n_rects;
202       int i;
203 
204       n_rects = cairo_region_num_rectangles (priv->input_region);
205 
206       for (i = 0; i < n_rects; i++)
207         {
208           cairo_rectangle_int_t rect;
209           ClutterActorBox box;
210 
211           cairo_region_get_rectangle (priv->input_region, i, &rect);
212 
213           box.x1 = rect.x;
214           box.y1 = rect.y;
215           box.x2 = rect.x + rect.width;
216           box.y2 = rect.y + rect.height;
217           clutter_actor_pick_box (actor, pick_context, &box);
218         }
219     }
220 
221   clutter_actor_iter_init (&iter, actor);
222 
223   while (clutter_actor_iter_next (&iter, &child))
224     clutter_actor_pick (child, pick_context);
225 }
226 
227 static gboolean
meta_surface_actor_get_paint_volume(ClutterActor * actor,ClutterPaintVolume * volume)228 meta_surface_actor_get_paint_volume (ClutterActor       *actor,
229                                      ClutterPaintVolume *volume)
230 {
231   return clutter_paint_volume_set_from_allocation (volume, actor);
232 }
233 
234 static void
meta_surface_actor_dispose(GObject * object)235 meta_surface_actor_dispose (GObject *object)
236 {
237   MetaSurfaceActor *self = META_SURFACE_ACTOR (object);
238   MetaSurfaceActorPrivate *priv =
239     meta_surface_actor_get_instance_private (self);
240 
241   g_clear_pointer (&priv->input_region, cairo_region_destroy);
242   g_clear_object (&priv->texture);
243 
244   set_unobscured_region (self, NULL);
245 
246   G_OBJECT_CLASS (meta_surface_actor_parent_class)->dispose (object);
247 }
248 
249 static void
meta_surface_actor_class_init(MetaSurfaceActorClass * klass)250 meta_surface_actor_class_init (MetaSurfaceActorClass *klass)
251 {
252   GObjectClass *object_class = G_OBJECT_CLASS (klass);
253   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
254 
255   object_class->dispose = meta_surface_actor_dispose;
256   actor_class->pick = meta_surface_actor_pick;
257   actor_class->get_paint_volume = meta_surface_actor_get_paint_volume;
258 
259   signals[REPAINT_SCHEDULED] = g_signal_new ("repaint-scheduled",
260                                              G_TYPE_FROM_CLASS (object_class),
261                                              G_SIGNAL_RUN_LAST,
262                                              0,
263                                              NULL, NULL, NULL,
264                                              G_TYPE_NONE, 0);
265 
266   signals[SIZE_CHANGED] = g_signal_new ("size-changed",
267                                         G_TYPE_FROM_CLASS (object_class),
268                                         G_SIGNAL_RUN_LAST,
269                                         0,
270                                         NULL, NULL, NULL,
271                                         G_TYPE_NONE, 0);
272 }
273 
274 gboolean
meta_surface_actor_is_opaque(MetaSurfaceActor * self)275 meta_surface_actor_is_opaque (MetaSurfaceActor *self)
276 {
277   return META_SURFACE_ACTOR_GET_CLASS (self)->is_opaque (self);
278 }
279 
280 static void
meta_surface_actor_cull_out(MetaCullable * cullable,cairo_region_t * unobscured_region,cairo_region_t * clip_region)281 meta_surface_actor_cull_out (MetaCullable   *cullable,
282                              cairo_region_t *unobscured_region,
283                              cairo_region_t *clip_region)
284 {
285   MetaSurfaceActor *surface_actor = META_SURFACE_ACTOR (cullable);
286   MetaSurfaceActorPrivate *priv =
287     meta_surface_actor_get_instance_private (surface_actor);
288   uint8_t opacity = clutter_actor_get_opacity (CLUTTER_ACTOR (cullable));
289 
290   set_unobscured_region (surface_actor, unobscured_region);
291   set_clip_region (surface_actor, clip_region);
292 
293   if (opacity == 0xff)
294     {
295       cairo_region_t *opaque_region;
296       cairo_region_t *scaled_opaque_region;
297 
298       opaque_region = meta_shaped_texture_get_opaque_region (priv->texture);
299 
300       if (!opaque_region)
301         return;
302 
303       scaled_opaque_region = get_scaled_region (surface_actor,
304                                                 opaque_region,
305                                                 IN_STAGE_PERSPECTIVE);
306 
307       if (unobscured_region)
308         cairo_region_subtract (unobscured_region, scaled_opaque_region);
309       if (clip_region)
310         cairo_region_subtract (clip_region, scaled_opaque_region);
311 
312       cairo_region_destroy (scaled_opaque_region);
313     }
314 }
315 
316 static gboolean
meta_surface_actor_is_untransformed(MetaCullable * cullable)317 meta_surface_actor_is_untransformed (MetaCullable *cullable)
318 {
319   ClutterActor *actor = CLUTTER_ACTOR (cullable);
320   MetaWindowActor *window_actor;
321   float width, height;
322   graphene_point3d_t verts[4];
323   int geometry_scale;
324 
325   clutter_actor_get_size (actor, &width, &height);
326   clutter_actor_get_abs_allocation_vertices (actor, verts);
327 
328   window_actor = meta_window_actor_from_actor (actor);
329   geometry_scale = meta_window_actor_get_geometry_scale (window_actor);
330 
331   return meta_actor_vertices_are_untransformed (verts,
332                                                 width * geometry_scale,
333                                                 height * geometry_scale,
334                                                 NULL, NULL);
335 }
336 
337 static void
meta_surface_actor_reset_culling(MetaCullable * cullable)338 meta_surface_actor_reset_culling (MetaCullable *cullable)
339 {
340   MetaSurfaceActor *surface_actor = META_SURFACE_ACTOR (cullable);
341 
342   set_clip_region (surface_actor, NULL);
343 }
344 
345 static void
cullable_iface_init(MetaCullableInterface * iface)346 cullable_iface_init (MetaCullableInterface *iface)
347 {
348   iface->cull_out = meta_surface_actor_cull_out;
349   iface->is_untransformed = meta_surface_actor_is_untransformed;
350   iface->reset_culling = meta_surface_actor_reset_culling;
351 }
352 
353 static void
texture_size_changed(MetaShapedTexture * texture,gpointer user_data)354 texture_size_changed (MetaShapedTexture *texture,
355                       gpointer           user_data)
356 {
357   MetaSurfaceActor *actor = META_SURFACE_ACTOR (user_data);
358   g_signal_emit (actor, signals[SIZE_CHANGED], 0);
359 }
360 
361 static void
meta_surface_actor_init(MetaSurfaceActor * self)362 meta_surface_actor_init (MetaSurfaceActor *self)
363 {
364   MetaSurfaceActorPrivate *priv =
365     meta_surface_actor_get_instance_private (self);
366 
367   priv->texture = meta_shaped_texture_new ();
368   g_signal_connect_object (priv->texture, "size-changed",
369                            G_CALLBACK (texture_size_changed), self, 0);
370   clutter_actor_set_content (CLUTTER_ACTOR (self),
371                              CLUTTER_CONTENT (priv->texture));
372   clutter_actor_set_request_mode (CLUTTER_ACTOR (self),
373                                   CLUTTER_REQUEST_CONTENT_SIZE);
374 }
375 
376 MetaShapedTexture *
meta_surface_actor_get_texture(MetaSurfaceActor * self)377 meta_surface_actor_get_texture (MetaSurfaceActor *self)
378 {
379   MetaSurfaceActorPrivate *priv =
380     meta_surface_actor_get_instance_private (self);
381 
382   return priv->texture;
383 }
384 
385 void
meta_surface_actor_update_area(MetaSurfaceActor * self,int x,int y,int width,int height)386 meta_surface_actor_update_area (MetaSurfaceActor *self,
387                                 int               x,
388                                 int               y,
389                                 int               width,
390                                 int               height)
391 {
392   MetaSurfaceActorPrivate *priv =
393     meta_surface_actor_get_instance_private (self);
394   gboolean repaint_scheduled = FALSE;
395   cairo_rectangle_int_t clip;
396 
397   if (meta_shaped_texture_update_area (priv->texture, x, y, width, height, &clip))
398     {
399       cairo_region_t *unobscured_region;
400 
401       unobscured_region = effective_unobscured_region (self);
402 
403       if (unobscured_region)
404         {
405           cairo_region_t *intersection;
406 
407           if (cairo_region_is_empty (unobscured_region))
408             return;
409 
410           intersection = cairo_region_copy (unobscured_region);
411           cairo_region_intersect_rectangle (intersection, &clip);
412 
413           if (!cairo_region_is_empty (intersection))
414             {
415               cairo_rectangle_int_t damage_rect;
416 
417               cairo_region_get_extents (intersection, &damage_rect);
418               clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (self), &damage_rect);
419               repaint_scheduled = TRUE;
420             }
421 
422           cairo_region_destroy (intersection);
423         }
424       else
425         {
426           clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (self), &clip);
427           repaint_scheduled = TRUE;
428         }
429     }
430 
431   if (repaint_scheduled)
432     g_signal_emit (self, signals[REPAINT_SCHEDULED], 0);
433 }
434 
435 gboolean
meta_surface_actor_is_obscured(MetaSurfaceActor * self)436 meta_surface_actor_is_obscured (MetaSurfaceActor *self)
437 {
438   cairo_region_t *unobscured_region;
439 
440   unobscured_region = effective_unobscured_region (self);
441 
442   if (unobscured_region)
443     return cairo_region_is_empty (unobscured_region);
444   else
445     return FALSE;
446 }
447 
448 gboolean
meta_surface_actor_is_obscured_on_stage_view(MetaSurfaceActor * self,ClutterStageView * stage_view,float * unobscurred_fraction)449 meta_surface_actor_is_obscured_on_stage_view (MetaSurfaceActor *self,
450                                               ClutterStageView *stage_view,
451                                               float            *unobscurred_fraction)
452 {
453   cairo_region_t *unobscured_region;
454 
455   unobscured_region = effective_unobscured_region (self);
456 
457   if (unobscured_region)
458     {
459       MetaSurfaceActorPrivate *priv =
460         meta_surface_actor_get_instance_private (self);
461       cairo_region_t *intersection_region;
462       cairo_rectangle_int_t stage_rect;
463       float x, y;
464       float bounds_width, bounds_height;
465       float bounds_size;
466       int intersection_size = 0;
467       int n_rects, i;
468 
469       if (cairo_region_is_empty (unobscured_region))
470         return TRUE;
471 
472       intersection_region = cairo_region_copy (unobscured_region);
473       clutter_actor_get_transformed_position (CLUTTER_ACTOR (self), &x, &y);
474       cairo_region_translate (intersection_region, x, y);
475 
476       clutter_stage_view_get_layout (stage_view, &stage_rect);
477       cairo_region_intersect_rectangle (intersection_region,
478                                         &stage_rect);
479 
480       if (cairo_region_is_empty (intersection_region))
481         {
482           cairo_region_destroy (intersection_region);
483           return TRUE;
484         }
485       else if (!unobscurred_fraction)
486         {
487           cairo_region_destroy (intersection_region);
488           return FALSE;
489         }
490 
491       clutter_content_get_preferred_size (CLUTTER_CONTENT (priv->texture),
492                                           &bounds_width,
493                                           &bounds_height);
494       bounds_size = bounds_width * bounds_height;
495 
496       n_rects = cairo_region_num_rectangles (intersection_region);
497       for (i = 0; i < n_rects; i++)
498         {
499           cairo_rectangle_int_t rect;
500 
501           cairo_region_get_rectangle (intersection_region, i, &rect);
502           intersection_size += (rect.width - rect.x) * (rect.height - rect.x);
503         }
504       cairo_region_destroy (intersection_region);
505 
506       g_return_val_if_fail (bounds_size > 0, FALSE);
507 
508       *unobscurred_fraction = CLAMP (intersection_size / bounds_size, 0, 1);
509       return FALSE;
510     }
511 
512   return !clutter_actor_is_effectively_on_stage_view (CLUTTER_ACTOR (self),
513                                                       stage_view);
514 }
515 
516 void
meta_surface_actor_set_input_region(MetaSurfaceActor * self,cairo_region_t * region)517 meta_surface_actor_set_input_region (MetaSurfaceActor *self,
518                                      cairo_region_t   *region)
519 {
520   MetaSurfaceActorPrivate *priv =
521     meta_surface_actor_get_instance_private (self);
522 
523   if (priv->input_region)
524     cairo_region_destroy (priv->input_region);
525 
526   if (region)
527     priv->input_region = cairo_region_reference (region);
528   else
529     priv->input_region = NULL;
530 }
531 
532 void
meta_surface_actor_set_opaque_region(MetaSurfaceActor * self,cairo_region_t * region)533 meta_surface_actor_set_opaque_region (MetaSurfaceActor *self,
534                                       cairo_region_t   *region)
535 {
536   MetaSurfaceActorPrivate *priv =
537     meta_surface_actor_get_instance_private (self);
538 
539   meta_shaped_texture_set_opaque_region (priv->texture, region);
540 }
541 
542 cairo_region_t *
meta_surface_actor_get_opaque_region(MetaSurfaceActor * self)543 meta_surface_actor_get_opaque_region (MetaSurfaceActor *self)
544 {
545   MetaSurfaceActorPrivate *priv =
546     meta_surface_actor_get_instance_private (self);
547 
548   return meta_shaped_texture_get_opaque_region (priv->texture);
549 }
550 
551 void
meta_surface_actor_process_damage(MetaSurfaceActor * self,int x,int y,int width,int height)552 meta_surface_actor_process_damage (MetaSurfaceActor *self,
553                                    int x, int y, int width, int height)
554 {
555   MetaSurfaceActorPrivate *priv =
556     meta_surface_actor_get_instance_private (self);
557 
558   if (meta_surface_actor_is_frozen (self))
559     {
560       /* The window is frozen due to an effect in progress: we ignore damage
561        * here on the off chance that this will stop the corresponding
562        * texture_from_pixmap from being update.
563        *
564        * pending_damage tracks any damage that happened while the window was
565        * frozen so that when can apply it when the window becomes unfrozen.
566        *
567        * It should be noted that this is an unreliable mechanism since it's
568        * quite likely that drivers will aim to provide a zero-copy
569        * implementation of the texture_from_pixmap extension and in those cases
570        * any drawing done to the window is always immediately reflected in the
571        * texture regardless of damage event handling.
572        */
573       cairo_rectangle_int_t rect = { .x = x, .y = y, .width = width, .height = height };
574 
575       if (!priv->pending_damage)
576         priv->pending_damage = cairo_region_create_rectangle (&rect);
577       else
578         cairo_region_union_rectangle (priv->pending_damage, &rect);
579       return;
580     }
581 
582   META_SURFACE_ACTOR_GET_CLASS (self)->process_damage (self, x, y, width, height);
583 }
584 
585 void
meta_surface_actor_set_frozen(MetaSurfaceActor * self,gboolean frozen)586 meta_surface_actor_set_frozen (MetaSurfaceActor *self,
587                                gboolean          frozen)
588 {
589   MetaSurfaceActorPrivate *priv =
590     meta_surface_actor_get_instance_private (self);
591 
592   if (priv->frozen == frozen)
593     return;
594 
595   priv->frozen = frozen;
596 
597   if (!frozen && priv->pending_damage)
598     {
599       int i, n_rects = cairo_region_num_rectangles (priv->pending_damage);
600       cairo_rectangle_int_t rect;
601 
602       /* Since we ignore damage events while a window is frozen for certain effects
603        * we need to apply the tracked damage now. */
604 
605       for (i = 0; i < n_rects; i++)
606         {
607           cairo_region_get_rectangle (priv->pending_damage, i, &rect);
608           meta_surface_actor_process_damage (self, rect.x, rect.y,
609                                              rect.width, rect.height);
610         }
611       g_clear_pointer (&priv->pending_damage, cairo_region_destroy);
612     }
613 }
614 
615 gboolean
meta_surface_actor_is_frozen(MetaSurfaceActor * self)616 meta_surface_actor_is_frozen (MetaSurfaceActor *self)
617 {
618   MetaSurfaceActorPrivate *priv =
619     meta_surface_actor_get_instance_private (self);
620 
621   return priv->frozen;
622 }
623