1 /*
2 * shaped texture
3 *
4 * An actor to draw a texture clipped to a list of rectangles
5 *
6 * Authored By Neil Roberts <neil@linux.intel.com>
7 *
8 * Copyright (C) 2008 Intel Corporation
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License as
12 * published by the Free Software Foundation; either version 2 of the
13 * License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA
23 * 02110-1335, USA.
24 */
25
26 /**
27 * SECTION:meta-shaped-texture
28 * @title: MetaShapedTexture
29 * @short_description: An actor to draw a masked texture.
30 */
31
32 #include <config.h>
33
34 #include <meta/meta-shaped-texture.h>
35 #include "clutter-utils.h"
36 #include "meta-texture-tower.h"
37 #include "meta-texture-rectangle.h"
38 #include "meta-shaped-texture-private.h"
39 #include "cogl-utils.h"
40
41 #include <clutter/clutter.h>
42 #include <cogl/cogl.h>
43 #include <cogl/winsys/cogl-texture-pixmap-x11.h>
44 #include <gdk/gdk.h> /* for gdk_rectangle_intersect() */
45 #include <string.h>
46
47 /* MAX_MIPMAPPING_FPS needs to be as small as possible for the best GPU
48 * performance, but higher than the refresh rate of commonly slow updating
49 * windows like top or a blinking cursor, so that such windows do get
50 * mipmapped.
51 */
52 #define MAX_MIPMAPPING_FPS 5
53 #define MIN_MIPMAP_AGE_USEC (G_USEC_PER_SEC / MAX_MIPMAPPING_FPS)
54
55 /* MIN_FAST_UPDATES_BEFORE_UNMIPMAP allows windows to update themselves
56 * occasionally without causing mipmapping to be disabled, so long as such
57 * an update takes fewer update_area calls than:
58 */
59 #define MIN_FAST_UPDATES_BEFORE_UNMIPMAP 20
60
61 static void meta_shaped_texture_dispose (GObject *object);
62
63 static void meta_shaped_texture_paint (ClutterActor *actor);
64
65 static void meta_shaped_texture_get_preferred_width (ClutterActor *self,
66 gfloat for_height,
67 gfloat *min_width_p,
68 gfloat *natural_width_p);
69
70 static void meta_shaped_texture_get_preferred_height (ClutterActor *self,
71 gfloat for_width,
72 gfloat *min_height_p,
73 gfloat *natural_height_p);
74
75 static gboolean meta_shaped_texture_get_paint_volume (ClutterActor *self, ClutterPaintVolume *volume);
76
77 G_DEFINE_TYPE (MetaShapedTexture, meta_shaped_texture,
78 CLUTTER_TYPE_ACTOR);
79
80 #define META_SHAPED_TEXTURE_GET_PRIVATE(obj) \
81 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), META_TYPE_SHAPED_TEXTURE, \
82 MetaShapedTexturePrivate))
83
84 enum {
85 SIZE_CHANGED,
86
87 LAST_SIGNAL,
88 };
89
90 static guint signals[LAST_SIGNAL];
91
92 struct _MetaShapedTexturePrivate
93 {
94 MetaTextureTower *paint_tower;
95 Pixmap pixmap;
96 CoglTexture *texture;
97 CoglTexture *mask_texture;
98
99 cairo_region_t *clip_region;
100 cairo_region_t *unobscured_region;
101 cairo_region_t *opaque_region;
102
103 cairo_region_t *overlay_region;
104 cairo_path_t *overlay_path;
105
106 guint tex_width, tex_height;
107
108 gint64 prev_invalidation, last_invalidation;
109 guint fast_updates;
110 guint remipmap_timeout_id;
111 gint64 earliest_remipmap;
112
113 guint create_mipmaps : 1;
114 guint mask_needs_update : 1;
115 };
116
117 static void
meta_shaped_texture_class_init(MetaShapedTextureClass * klass)118 meta_shaped_texture_class_init (MetaShapedTextureClass *klass)
119 {
120 GObjectClass *gobject_class = (GObjectClass *) klass;
121 ClutterActorClass *actor_class = (ClutterActorClass *) klass;
122
123 gobject_class->dispose = meta_shaped_texture_dispose;
124
125 actor_class->get_preferred_width = meta_shaped_texture_get_preferred_width;
126 actor_class->get_preferred_height = meta_shaped_texture_get_preferred_height;
127 actor_class->paint = meta_shaped_texture_paint;
128 actor_class->get_paint_volume = meta_shaped_texture_get_paint_volume;
129
130 signals[SIZE_CHANGED] = g_signal_new ("size-changed",
131 G_TYPE_FROM_CLASS (gobject_class),
132 G_SIGNAL_RUN_LAST,
133 0,
134 NULL, NULL, NULL,
135 G_TYPE_NONE, 0);
136
137 g_type_class_add_private (klass, sizeof (MetaShapedTexturePrivate));
138 }
139
140 static void
meta_shaped_texture_init(MetaShapedTexture * self)141 meta_shaped_texture_init (MetaShapedTexture *self)
142 {
143 MetaShapedTexturePrivate *priv;
144
145 priv = self->priv = META_SHAPED_TEXTURE_GET_PRIVATE (self);
146
147 priv->overlay_path = NULL;
148 priv->overlay_region = NULL;
149 priv->paint_tower = meta_texture_tower_new ();
150 priv->texture = NULL;
151 priv->mask_texture = NULL;
152 priv->create_mipmaps = TRUE;
153 priv->mask_needs_update = TRUE;
154 }
155
156 static void
meta_shaped_texture_dispose(GObject * object)157 meta_shaped_texture_dispose (GObject *object)
158 {
159 MetaShapedTexture *self = (MetaShapedTexture *) object;
160 MetaShapedTexturePrivate *priv = self->priv;
161
162 if (priv->remipmap_timeout_id)
163 {
164 g_source_remove (priv->remipmap_timeout_id);
165 priv->remipmap_timeout_id = 0;
166 }
167
168 if (priv->paint_tower)
169 meta_texture_tower_free (priv->paint_tower);
170 priv->paint_tower = NULL;
171
172 meta_shaped_texture_dirty_mask (self);
173 g_clear_pointer (&priv->texture, cogl_object_unref);
174 g_clear_pointer (&priv->opaque_region, cairo_region_destroy);
175
176 meta_shaped_texture_set_clip_region (self, NULL);
177 meta_shaped_texture_set_overlay_path (self, NULL, NULL);
178
179 G_OBJECT_CLASS (meta_shaped_texture_parent_class)->dispose (object);
180 }
181
182 static CoglPipeline *
get_base_pipeline(CoglContext * ctx)183 get_base_pipeline (CoglContext *ctx)
184 {
185 static CoglPipeline *template = NULL;
186 if (G_UNLIKELY (template == NULL))
187 {
188 template = cogl_pipeline_new (ctx);
189 cogl_pipeline_set_layer_wrap_mode_s (template, 0, COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE);
190 cogl_pipeline_set_layer_wrap_mode_t (template, 0, COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE);
191 cogl_pipeline_set_layer_wrap_mode_s (template, 1, COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE);
192 cogl_pipeline_set_layer_wrap_mode_t (template, 1, COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE);
193 }
194 return template;
195 }
196
197 static CoglPipeline *
get_unmasked_pipeline(CoglContext * ctx)198 get_unmasked_pipeline (CoglContext *ctx)
199 {
200 return get_base_pipeline (ctx);
201 }
202
203 static CoglPipeline *
get_masked_pipeline(CoglContext * ctx)204 get_masked_pipeline (CoglContext *ctx)
205 {
206 static CoglPipeline *template = NULL;
207 if (G_UNLIKELY (template == NULL))
208 {
209 template = cogl_pipeline_copy (get_base_pipeline (ctx));
210 cogl_pipeline_set_layer_combine (template, 1,
211 "RGBA = MODULATE (PREVIOUS, TEXTURE[A])",
212 NULL);
213 }
214
215 return template;
216 }
217
218 static CoglPipeline *
get_unblended_pipeline(CoglContext * ctx)219 get_unblended_pipeline (CoglContext *ctx)
220 {
221 static CoglPipeline *template = NULL;
222 if (G_UNLIKELY (template == NULL))
223 {
224 template = cogl_pipeline_copy (get_base_pipeline (ctx));
225 cogl_pipeline_set_layer_combine (template,
226 0,
227 "RGBA = REPLACE (TEXTURE)",
228 NULL);
229
230 }
231
232 return template;
233 }
234
235 static void
paint_clipped_rectangle(CoglFramebuffer * fb,CoglPipeline * pipeline,cairo_rectangle_int_t * rect,ClutterActorBox * alloc)236 paint_clipped_rectangle (CoglFramebuffer *fb,
237 CoglPipeline *pipeline,
238 cairo_rectangle_int_t *rect,
239 ClutterActorBox *alloc)
240 {
241 float coords[8];
242 float x1, y1, x2, y2;
243
244 x1 = rect->x;
245 y1 = rect->y;
246 x2 = rect->x + rect->width;
247 y2 = rect->y + rect->height;
248
249 coords[0] = rect->x / (alloc->x2 - alloc->x1);
250 coords[1] = rect->y / (alloc->y2 - alloc->y1);
251 coords[2] = (rect->x + rect->width) / (alloc->x2 - alloc->x1);
252 coords[3] = (rect->y + rect->height) / (alloc->y2 - alloc->y1);
253
254 coords[4] = coords[0];
255 coords[5] = coords[1];
256 coords[6] = coords[2];
257 coords[7] = coords[3];
258
259 cogl_framebuffer_draw_multitextured_rectangle (fb, pipeline,
260 x1, y1, x2, y2,
261 &coords[0], 8);
262
263 }
264
265 LOCAL_SYMBOL void
meta_shaped_texture_dirty_mask(MetaShapedTexture * stex)266 meta_shaped_texture_dirty_mask (MetaShapedTexture *stex)
267 {
268 MetaShapedTexturePrivate *priv = stex->priv;
269
270 g_clear_pointer (&priv->mask_texture, cogl_object_unref);
271 }
272
273 static void
install_overlay_path(MetaShapedTexture * stex,guchar * mask_data,int tex_width,int tex_height,int stride)274 install_overlay_path (MetaShapedTexture *stex,
275 guchar *mask_data,
276 int tex_width,
277 int tex_height,
278 int stride)
279 {
280 MetaShapedTexturePrivate *priv = stex->priv;
281 int i, n_rects;
282 cairo_t *cr;
283 cairo_rectangle_int_t rect;
284 cairo_surface_t *surface;
285
286 if (priv->overlay_region == NULL)
287 return;
288
289 surface = cairo_image_surface_create_for_data (mask_data,
290 CAIRO_FORMAT_A8,
291 tex_width,
292 tex_height,
293 stride);
294
295 cr = cairo_create (surface);
296 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
297
298 n_rects = cairo_region_num_rectangles (priv->overlay_region);
299 for (i = 0; i < n_rects; i++)
300 {
301 cairo_region_get_rectangle (priv->overlay_region, i, &rect);
302 cairo_rectangle (cr, rect.x, rect.y, rect.width, rect.height);
303 }
304
305 cairo_fill_preserve (cr);
306 if (priv->overlay_path == NULL)
307 {
308 /* If we have an overlay region but not an overlay path, then we
309 * just need to clear the rectangles in the overlay region. */
310 goto out;
311 }
312
313 cairo_clip (cr);
314
315 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
316 cairo_set_source_rgba (cr, 1, 1, 1, 1);
317
318 cairo_append_path (cr, priv->overlay_path);
319 cairo_fill (cr);
320
321 out:
322 cairo_destroy (cr);
323 cairo_surface_destroy (surface);
324 }
325
326 LOCAL_SYMBOL void
meta_shaped_texture_ensure_mask(MetaShapedTexture * stex,cairo_region_t * shape_region,gboolean has_frame)327 meta_shaped_texture_ensure_mask (MetaShapedTexture *stex,
328 cairo_region_t *shape_region,
329 gboolean has_frame)
330 {
331 MetaShapedTexturePrivate *priv = stex->priv;
332 CoglTexture *paint_tex;
333 guint tex_width, tex_height;
334
335 paint_tex = priv->texture;
336
337 if (paint_tex == NULL)
338 return;
339
340 tex_width = cogl_texture_get_width (paint_tex);
341 tex_height = cogl_texture_get_height (paint_tex);
342
343 /* If the mask texture we have was created for a different size then
344 recreate it */
345 if (priv->mask_texture != NULL && priv->mask_needs_update)
346 {
347 priv->mask_needs_update = FALSE;
348 meta_shaped_texture_dirty_mask (stex);
349 }
350
351 /* If we don't have a mask texture yet then create one */
352 if (priv->mask_texture == NULL)
353 {
354 guchar *mask_data;
355 int i;
356 int n_rects;
357 int stride;
358
359 /* If we have no shape region and no (or an empty) overlay region, we
360 * don't need to create a full mask texture, so quit early. */
361 if (shape_region == NULL &&
362 (priv->overlay_region == NULL ||
363 cairo_region_num_rectangles (priv->overlay_region) == 0))
364 {
365 return;
366 }
367
368 if (shape_region == NULL)
369 return;
370
371 n_rects = cairo_region_num_rectangles (shape_region);
372
373 if (n_rects == 0)
374 return;
375
376 stride = cairo_format_stride_for_width (CAIRO_FORMAT_A8, tex_width);
377
378 /* Create data for an empty image */
379 mask_data = g_malloc0 (stride * tex_height);
380
381 /* Fill in each rectangle. */
382 for (i = 0; i < n_rects; i ++)
383 {
384 cairo_rectangle_int_t rect;
385 cairo_region_get_rectangle (shape_region, i, &rect);
386
387 gint x1 = rect.x, x2 = x1 + rect.width;
388 gint y1 = rect.y, y2 = y1 + rect.height;
389 guchar *p;
390
391 /* Clip the rectangle to the size of the texture */
392 x1 = CLAMP (x1, 0, (gint) tex_width - 1);
393 x2 = CLAMP (x2, x1, (gint) tex_width);
394 y1 = CLAMP (y1, 0, (gint) tex_height - 1);
395 y2 = CLAMP (y2, y1, (gint) tex_height);
396
397 /* Fill the rectangle */
398 for (p = mask_data + y1 * stride + x1;
399 y1 < y2;
400 y1++, p += stride)
401 memset (p, 255, x2 - x1);
402 }
403
404 if (has_frame)
405 install_overlay_path (stex, mask_data, tex_width, tex_height, stride);
406
407 if (meta_texture_rectangle_check (paint_tex))
408 priv->mask_texture = meta_cogl_rectangle_new (tex_width, tex_height,
409 COGL_PIXEL_FORMAT_A_8,
410 stride, mask_data);
411 else
412 priv->mask_texture = meta_cogl_texture_new_from_data_wrapper (tex_width, tex_height,
413 COGL_TEXTURE_NONE,
414 COGL_PIXEL_FORMAT_A_8,
415 COGL_PIXEL_FORMAT_ANY,
416 stride,
417 mask_data);
418
419 g_free (mask_data);
420 }
421 }
422
423 static gboolean
texture_is_idle_and_not_mipmapped(gpointer user_data)424 texture_is_idle_and_not_mipmapped (gpointer user_data)
425 {
426 MetaShapedTexture *stex = META_SHAPED_TEXTURE (user_data);
427 MetaShapedTexturePrivate *priv = stex->priv;
428
429 if ((g_get_monotonic_time () - priv->earliest_remipmap) < 0)
430 return G_SOURCE_CONTINUE;
431
432 clutter_actor_queue_redraw (CLUTTER_ACTOR (stex));
433 priv->remipmap_timeout_id = 0;
434
435 return G_SOURCE_REMOVE;
436 }
437
438 static void
meta_shaped_texture_paint(ClutterActor * actor)439 meta_shaped_texture_paint (ClutterActor *actor)
440 {
441 MetaShapedTexture *stex = (MetaShapedTexture *) actor;
442 MetaShapedTexturePrivate *priv = stex->priv;
443 guint tex_width, tex_height;
444 guchar opacity;
445 CoglContext *ctx;
446 CoglFramebuffer *fb;
447 CoglTexture *paint_tex = NULL;
448 ClutterActorBox alloc;
449 CoglPipelineFilter filter;
450 gint64 now = g_get_monotonic_time ();
451
452 if (priv->clip_region && cairo_region_is_empty (priv->clip_region))
453 return;
454
455 if (!CLUTTER_ACTOR_IS_REALIZED (CLUTTER_ACTOR (stex)))
456 clutter_actor_realize (CLUTTER_ACTOR (stex));
457
458 /* The GL EXT_texture_from_pixmap extension does allow for it to be
459 * used together with SGIS_generate_mipmap, however this is very
460 * rarely supported. Also, even when it is supported there
461 * are distinct performance implications from:
462 *
463 * - Updating mipmaps that we don't need
464 * - Having to reallocate pixmaps on the server into larger buffers
465 *
466 * So, we just unconditionally use our mipmap emulation code. If we
467 * wanted to use SGIS_generate_mipmap, we'd have to query COGL to
468 * see if it was supported (no API currently), and then if and only
469 * if that was the case, set the clutter texture quality to HIGH.
470 * Setting the texture quality to high without SGIS_generate_mipmap
471 * support for TFP textures will result in fallbacks to XGetImage.
472 */
473 if (priv->create_mipmaps && priv->last_invalidation)
474 {
475 gint64 age = now - priv->last_invalidation;
476
477 if (age >= MIN_MIPMAP_AGE_USEC ||
478 priv->fast_updates < MIN_FAST_UPDATES_BEFORE_UNMIPMAP)
479 paint_tex = meta_texture_tower_get_paint_texture (priv->paint_tower);
480 }
481
482 if (paint_tex == NULL)
483 {
484 paint_tex = COGL_TEXTURE (priv->texture);
485
486 if (paint_tex == NULL)
487 return;
488
489 if (priv->create_mipmaps)
490 {
491 /* Minus 1000 to ensure we don't fail the age test in timeout */
492 priv->earliest_remipmap = now + MIN_MIPMAP_AGE_USEC - 1000;
493
494 if (!priv->remipmap_timeout_id)
495 priv->remipmap_timeout_id =
496 g_timeout_add (MIN_MIPMAP_AGE_USEC / 1000,
497 texture_is_idle_and_not_mipmapped,
498 stex);
499 }
500 }
501
502 tex_width = priv->tex_width;
503 tex_height = priv->tex_height;
504
505 if (tex_width == 0 || tex_height == 0) /* no contents yet */
506 return;
507
508 cairo_rectangle_int_t tex_rect = { 0, 0, tex_width, tex_height };
509
510 /* Use nearest-pixel interpolation if the texture is unscaled. This
511 * improves performance, especially with software rendering.
512 */
513
514 filter = COGL_PIPELINE_FILTER_LINEAR;
515
516 if (meta_actor_painting_untransformed (tex_width, tex_height, NULL, NULL))
517 filter = COGL_PIPELINE_FILTER_NEAREST;
518
519 ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
520 fb = cogl_get_draw_framebuffer ();
521
522 opacity = clutter_actor_get_paint_opacity (actor);
523 clutter_actor_get_allocation_box (actor, &alloc);
524
525 cairo_region_t *blended_region;
526 gboolean use_opaque_region = (priv->opaque_region != NULL && opacity == 255);
527
528 if (use_opaque_region)
529 {
530 if (priv->clip_region != NULL)
531 blended_region = cairo_region_copy (priv->clip_region);
532 else
533 blended_region = cairo_region_create_rectangle (&tex_rect);
534
535 cairo_region_subtract (blended_region, priv->opaque_region);
536 }
537 else
538 {
539 if (priv->clip_region != NULL)
540 blended_region = cairo_region_reference (priv->clip_region);
541 else
542 blended_region = NULL;
543 }
544
545 /* Limit to how many separate rectangles we'll draw; beyond this just
546 * fall back and draw the whole thing */
547 #define MAX_RECTS 16
548
549 if (blended_region != NULL)
550 {
551 int n_rects = cairo_region_num_rectangles (blended_region);
552 if (n_rects > MAX_RECTS)
553 {
554 /* Fall back to taking the fully blended path. */
555 use_opaque_region = FALSE;
556
557 cairo_region_destroy (blended_region);
558 blended_region = NULL;
559 }
560 }
561
562 /* First, paint the unblended parts, which are part of the opaque region. */
563 if (use_opaque_region)
564 {
565 cairo_region_t *region;
566 int n_rects;
567 int i;
568
569 if (priv->clip_region != NULL)
570 {
571 region = cairo_region_copy (priv->clip_region);
572 cairo_region_intersect (region, priv->opaque_region);
573 }
574 else
575 {
576 region = cairo_region_reference (priv->opaque_region);
577 }
578
579 if (!cairo_region_is_empty (region))
580 {
581 CoglPipeline *opaque_pipeline = get_unblended_pipeline (ctx);
582 cogl_pipeline_set_layer_texture (opaque_pipeline, 0, paint_tex);
583 cogl_pipeline_set_layer_filters (opaque_pipeline, 0, filter, filter);
584
585 n_rects = cairo_region_num_rectangles (region);
586 for (i = 0; i < n_rects; i++)
587 {
588 cairo_rectangle_int_t rect;
589 cairo_region_get_rectangle (region, i, &rect);
590 paint_clipped_rectangle (fb, opaque_pipeline, &rect, &alloc);
591 }
592 }
593
594 cairo_region_destroy (region);
595 }
596
597 /* Now, go ahead and paint the blended parts. */
598
599 /* We have three cases:
600 * 1) blended_region has rectangles - paint the rectangles.
601 * 2) blended_region is empty - don't paint anything
602 * 3) blended_region is NULL - paint fully-blended.
603 *
604 * 1) and 3) are the times where we have to paint stuff. This tests
605 * for 1) and 3).
606 */
607 if (blended_region == NULL || !cairo_region_is_empty (blended_region))
608 {
609 CoglPipeline *blended_pipeline;
610
611 if (priv->mask_texture == NULL)
612 {
613 blended_pipeline = get_unmasked_pipeline (ctx);
614 }
615 else
616 {
617 blended_pipeline = get_masked_pipeline (ctx);
618 cogl_pipeline_set_layer_texture (blended_pipeline, 1, priv->mask_texture);
619 cogl_pipeline_set_layer_filters (blended_pipeline, 1, filter, filter);
620 }
621
622 cogl_pipeline_set_layer_texture (blended_pipeline, 0, paint_tex);
623 cogl_pipeline_set_layer_filters (blended_pipeline, 0, filter, filter);
624
625 CoglColor color;
626 cogl_color_init_from_4ub (&color, opacity, opacity, opacity, opacity);
627 cogl_pipeline_set_color (blended_pipeline, &color);
628
629 if (blended_region != NULL)
630 {
631 /* 1) blended_region is not empty. Paint the rectangles. */
632 int i;
633 int n_rects = cairo_region_num_rectangles (blended_region);
634
635 for (i = 0; i < n_rects; i++)
636 {
637 cairo_rectangle_int_t rect;
638 cairo_region_get_rectangle (blended_region, i, &rect);
639
640 if (!gdk_rectangle_intersect (&tex_rect, &rect, &rect))
641 continue;
642
643 paint_clipped_rectangle (fb, blended_pipeline, &rect, &alloc);
644 }
645 }
646 else
647 {
648 /* 3) blended_region is NULL. Do a full paint. */
649 cogl_framebuffer_draw_rectangle (fb, blended_pipeline,
650 0, 0,
651 alloc.x2 - alloc.x1,
652 alloc.y2 - alloc.y1);
653 }
654 }
655
656 if (blended_region != NULL)
657 cairo_region_destroy (blended_region);
658 }
659
660 static void
meta_shaped_texture_get_preferred_width(ClutterActor * self,gfloat for_height,gfloat * min_width_p,gfloat * natural_width_p)661 meta_shaped_texture_get_preferred_width (ClutterActor *self,
662 gfloat for_height,
663 gfloat *min_width_p,
664 gfloat *natural_width_p)
665 {
666 MetaShapedTexturePrivate *priv = META_SHAPED_TEXTURE (self)->priv;
667
668 if (min_width_p)
669 *min_width_p = priv->tex_width;
670
671 if (natural_width_p)
672 *natural_width_p = priv->tex_width;
673 }
674
675 static void
meta_shaped_texture_get_preferred_height(ClutterActor * self,gfloat for_width,gfloat * min_height_p,gfloat * natural_height_p)676 meta_shaped_texture_get_preferred_height (ClutterActor *self,
677 gfloat for_width,
678 gfloat *min_height_p,
679 gfloat *natural_height_p)
680 {
681 MetaShapedTexturePrivate *priv = META_SHAPED_TEXTURE (self)->priv;
682
683 if (min_height_p)
684 *min_height_p = priv->tex_height;
685
686 if (natural_height_p)
687 *natural_height_p = priv->tex_height;
688 }
689
690 static gboolean
meta_shaped_texture_get_paint_volume(ClutterActor * self,ClutterPaintVolume * volume)691 meta_shaped_texture_get_paint_volume (ClutterActor *self,
692 ClutterPaintVolume *volume)
693 {
694 return clutter_paint_volume_set_from_allocation (volume, self);
695 }
696
697 ClutterActor *
meta_shaped_texture_new(void)698 meta_shaped_texture_new (void)
699 {
700 ClutterActor *self = g_object_new (META_TYPE_SHAPED_TEXTURE, NULL);
701
702 return self;
703 }
704
705 void
meta_shaped_texture_set_create_mipmaps(MetaShapedTexture * stex,gboolean create_mipmaps)706 meta_shaped_texture_set_create_mipmaps (MetaShapedTexture *stex,
707 gboolean create_mipmaps)
708 {
709 MetaShapedTexturePrivate *priv;
710
711 g_return_if_fail (META_IS_SHAPED_TEXTURE (stex));
712
713 priv = stex->priv;
714
715 create_mipmaps = create_mipmaps != FALSE;
716
717 if (create_mipmaps != priv->create_mipmaps)
718 {
719 CoglTexture *base_texture;
720 priv->create_mipmaps = create_mipmaps;
721 base_texture = create_mipmaps ? priv->texture : NULL;
722
723 meta_texture_tower_set_base_texture (priv->paint_tower, base_texture);
724 }
725 }
726
727 /**
728 * meta_shaped_texture_update_area:
729 * @stex: #MetaShapedTexture
730 * @x: the x coordinate of the damaged area
731 * @y: the y coordinate of the damaged area
732 * @width: the width of the damaged area
733 * @height: the height of the damaged area
734 * @unobscured_region: The unobscured region of the window or %NULL if
735 * there is no valid one (like when the actor is transformed or
736 * has a mapped clone)
737 *
738 * Repairs the damaged area indicated by @x, @y, @width and @height
739 * and queues a redraw for the intersection @visibible_region and
740 * the damage area. If @visibible_region is %NULL a redraw will always
741 * get queued.
742 *
743 * Return value: Whether a redraw have been queued or not
744 */
745 gboolean
meta_shaped_texture_update_area(MetaShapedTexture * stex,int x,int y,int width,int height,cairo_region_t * unobscured_region)746 meta_shaped_texture_update_area (MetaShapedTexture *stex,
747 int x,
748 int y,
749 int width,
750 int height,
751 cairo_region_t *unobscured_region)
752 {
753 MetaShapedTexturePrivate *priv;
754 const cairo_rectangle_int_t clip = { x, y, width, height };
755
756 priv = stex->priv;
757
758 if (priv->texture == NULL)
759 return FALSE;
760
761 meta_texture_tower_update_area (priv->paint_tower, x, y, width, height);
762
763 priv->prev_invalidation = priv->last_invalidation;
764 priv->last_invalidation = g_get_monotonic_time ();
765
766 if (priv->prev_invalidation)
767 {
768 gint64 interval = priv->last_invalidation - priv->prev_invalidation;
769 gboolean fast_update = interval < MIN_MIPMAP_AGE_USEC;
770
771 if (!fast_update)
772 priv->fast_updates = 0;
773 else if (priv->fast_updates < MIN_FAST_UPDATES_BEFORE_UNMIPMAP)
774 priv->fast_updates++;
775 }
776
777 if (unobscured_region)
778 {
779 cairo_region_t *intersection;
780
781 if (cairo_region_is_empty (unobscured_region))
782 return FALSE;
783
784 intersection = cairo_region_copy (unobscured_region);
785 cairo_region_intersect_rectangle (intersection, &clip);
786
787 if (!cairo_region_is_empty (intersection))
788 {
789 cairo_rectangle_int_t damage_rect;
790 cairo_region_get_extents (intersection, &damage_rect);
791 clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (stex), &damage_rect);
792 cairo_region_destroy (intersection);
793 return TRUE;
794 }
795
796 cairo_region_destroy (intersection);
797 return FALSE;
798 }
799 else
800 {
801 clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (stex), &clip);
802 return TRUE;
803 }
804 }
805
806 static void
set_cogl_texture(MetaShapedTexture * stex,CoglTexture * cogl_tex)807 set_cogl_texture (MetaShapedTexture *stex,
808 CoglTexture *cogl_tex)
809 {
810 MetaShapedTexturePrivate *priv;
811 guint width, height;
812
813 g_return_if_fail (META_IS_SHAPED_TEXTURE (stex));
814
815 priv = stex->priv;
816
817 if (priv->texture != NULL)
818 cogl_object_unref (priv->texture);
819
820 priv->texture = cogl_tex;
821
822 if (cogl_tex != NULL)
823 {
824 width = cogl_texture_get_width (COGL_TEXTURE (cogl_tex));
825 height = cogl_texture_get_height (COGL_TEXTURE (cogl_tex));
826 }
827 else
828 {
829 width = 0;
830 height = 0;
831 }
832
833 priv->mask_needs_update = (priv->tex_width != width ||
834 priv->tex_height != height);
835
836 if (priv->mask_needs_update)
837 {
838 priv->tex_width = width;
839 priv->tex_height = height;
840 meta_shaped_texture_dirty_mask (stex);
841 clutter_actor_queue_relayout (CLUTTER_ACTOR (stex));
842 g_signal_emit (stex, signals[SIZE_CHANGED], 0);
843 }
844
845 /* NB: We don't queue a redraw of the actor here because we don't
846 * know how much of the buffer has changed with respect to the
847 * previous buffer. We only queue a redraw in response to surface
848 * damage. */
849
850 if (priv->create_mipmaps)
851 meta_texture_tower_set_base_texture (priv->paint_tower, cogl_tex);
852 }
853
854 /**
855 * meta_shaped_texture_set_texture:
856 * @stex: The #MetaShapedTexture
857 * @pixmap: The #CoglTexture to display
858 */
859 void
meta_shaped_texture_set_texture(MetaShapedTexture * stex,CoglTexture * texture)860 meta_shaped_texture_set_texture (MetaShapedTexture *stex,
861 CoglTexture *texture)
862 {
863 g_return_if_fail (META_IS_SHAPED_TEXTURE (stex));
864
865 set_cogl_texture (stex, texture);
866 }
867
868 /**
869 * meta_shaped_texture_get_texture:
870 * @stex: The #MetaShapedTexture
871 *
872 * Returns: (transfer none): the unshaped texture
873 */
874 CoglTexture *
meta_shaped_texture_get_texture(MetaShapedTexture * stex)875 meta_shaped_texture_get_texture (MetaShapedTexture *stex)
876 {
877 g_return_val_if_fail (META_IS_SHAPED_TEXTURE (stex), NULL);
878 return stex->priv->texture;
879 }
880
881 /**
882 * meta_shaped_texture_set_overlay_path:
883 * @stex: a #MetaShapedTexture
884 * @overlay_region: A region containing the parts of the mask to overlay.
885 * All rectangles in this region are wiped clear to full transparency,
886 * and the overlay path is clipped to this region.
887 * @overlay_path: (transfer full): This path will be painted onto the mask
888 * texture with a fully opaque source. Due to the lack of refcounting
889 * in #cairo_path_t, ownership of the path is assumed.
890 */
891 void
meta_shaped_texture_set_overlay_path(MetaShapedTexture * stex,cairo_region_t * overlay_region,cairo_path_t * overlay_path)892 meta_shaped_texture_set_overlay_path (MetaShapedTexture *stex,
893 cairo_region_t *overlay_region,
894 cairo_path_t *overlay_path)
895 {
896 MetaShapedTexturePrivate *priv;
897
898 g_return_if_fail (META_IS_SHAPED_TEXTURE (stex));
899
900 priv = stex->priv;
901
902 if (priv->overlay_region != NULL)
903 {
904 cairo_region_destroy (priv->overlay_region);
905 priv->overlay_region = NULL;
906 }
907
908 if (priv->overlay_path != NULL)
909 {
910 cairo_path_destroy (priv->overlay_path);
911 priv->overlay_path = NULL;
912 }
913
914 cairo_region_reference (overlay_region);
915 priv->overlay_region = overlay_region;
916
917 /* cairo_path_t does not have refcounting. */
918 priv->overlay_path = overlay_path;
919 }
920
921 /**
922 * meta_shaped_texture_set_clip_region:
923 * @stex: a #MetaShapedTexture
924 * @clip_region: the region of the texture that is visible and
925 * should be painted.
926 *
927 * Provides a hint to the texture about what areas of the texture
928 * are not completely obscured and thus need to be painted. This
929 * is an optimization and is not supposed to have any effect on
930 * the output.
931 *
932 * Typically a parent container will set the clip region before
933 * painting its children, and then unset it afterwards.
934 */
935 void
meta_shaped_texture_set_clip_region(MetaShapedTexture * stex,cairo_region_t * clip_region)936 meta_shaped_texture_set_clip_region (MetaShapedTexture *stex,
937 cairo_region_t *clip_region)
938 {
939 MetaShapedTexturePrivate *priv;
940
941 g_return_if_fail (META_IS_SHAPED_TEXTURE (stex));
942
943 priv = stex->priv;
944
945 if (priv->clip_region)
946 cairo_region_destroy (priv->clip_region);
947
948 if (clip_region)
949 priv->clip_region = cairo_region_copy (clip_region);
950 else
951 priv->clip_region = NULL;
952 }
953
954 /**
955 * meta_shaped_texture_set_opaque_region:
956 * @stex: a #MetaShapedTexture
957 * @opaque_region: (transfer full): the region of the texture that
958 * can have blending turned off.
959 *
960 * As most windows have a large portion that does not require blending,
961 * we can easily turn off blending if we know the areas that do not
962 * require blending. This sets the region where we will not blend for
963 * optimization purposes.
964 */
965 void
meta_shaped_texture_set_opaque_region(MetaShapedTexture * stex,cairo_region_t * opaque_region)966 meta_shaped_texture_set_opaque_region (MetaShapedTexture *stex,
967 cairo_region_t *opaque_region)
968 {
969 MetaShapedTexturePrivate *priv;
970
971 g_return_if_fail (META_IS_SHAPED_TEXTURE (stex));
972
973 priv = stex->priv;
974
975 if (priv->opaque_region)
976 cairo_region_destroy (priv->opaque_region);
977
978 if (opaque_region)
979 priv->opaque_region = cairo_region_reference (opaque_region);
980 else
981 priv->opaque_region = NULL;
982 }
983
984 /**
985 * meta_shaped_texture_get_image:
986 * @stex: A #MetaShapedTexture
987 * @clip: A clipping rectangle, to help prevent extra processing.
988 * In the case that the clipping rectangle is partially or fully
989 * outside the bounds of the texture, the rectangle will be clipped.
990 *
991 * Flattens the two layers of the shaped texture into one ARGB32
992 * image by alpha blending the two images, and returns the flattened
993 * image.
994 *
995 * Returns: (transfer full): a new cairo surface to be freed with
996 * cairo_surface_destroy().
997 */
998 cairo_surface_t *
meta_shaped_texture_get_image(MetaShapedTexture * stex,cairo_rectangle_int_t * clip)999 meta_shaped_texture_get_image (MetaShapedTexture *stex,
1000 cairo_rectangle_int_t *clip)
1001 {
1002 CoglTexture *texture, *mask_texture;
1003 cairo_rectangle_int_t texture_rect = { 0, 0, 0, 0 };
1004 cairo_surface_t *surface;
1005
1006 g_return_val_if_fail (META_IS_SHAPED_TEXTURE (stex), NULL);
1007
1008 texture = stex->priv->texture;
1009
1010 if (texture == NULL)
1011 return NULL;
1012
1013 texture_rect.width = cogl_texture_get_width (texture);
1014 texture_rect.height = cogl_texture_get_height (texture);
1015
1016 if (clip != NULL)
1017 {
1018 /* GdkRectangle is just a typedef of cairo_rectangle_int_t,
1019 * so we can use the gdk_rectangle_* APIs on these. */
1020 if (!gdk_rectangle_intersect (&texture_rect, clip, clip))
1021 return NULL;
1022 }
1023
1024 if (clip != NULL)
1025 texture = cogl_texture_new_from_sub_texture (texture,
1026 clip->x,
1027 clip->y,
1028 clip->width,
1029 clip->height);
1030
1031 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
1032 cogl_texture_get_width (texture),
1033 cogl_texture_get_height (texture));
1034
1035 cogl_texture_get_data (texture, CLUTTER_CAIRO_FORMAT_ARGB32,
1036 cairo_image_surface_get_stride (surface),
1037 cairo_image_surface_get_data (surface));
1038
1039 cairo_surface_mark_dirty (surface);
1040
1041 if (clip != NULL)
1042 cogl_object_unref (texture);
1043
1044 mask_texture = stex->priv->mask_texture;
1045 if (mask_texture != NULL)
1046 {
1047 cairo_t *cr;
1048 cairo_surface_t *mask_surface;
1049
1050 if (clip != NULL)
1051 mask_texture = cogl_texture_new_from_sub_texture (mask_texture,
1052 clip->x,
1053 clip->y,
1054 clip->width,
1055 clip->height);
1056
1057 mask_surface = cairo_image_surface_create (CAIRO_FORMAT_A8,
1058 cogl_texture_get_width (mask_texture),
1059 cogl_texture_get_height (mask_texture));
1060
1061 cogl_texture_get_data (mask_texture, COGL_PIXEL_FORMAT_A_8,
1062 cairo_image_surface_get_stride (mask_surface),
1063 cairo_image_surface_get_data (mask_surface));
1064
1065 cairo_surface_mark_dirty (mask_surface);
1066
1067 cr = cairo_create (surface);
1068 cairo_set_source_surface (cr, mask_surface, 0, 0);
1069 cairo_set_operator (cr, CAIRO_OPERATOR_DEST_IN);
1070 cairo_paint (cr);
1071 cairo_destroy (cr);
1072
1073 cairo_surface_destroy (mask_surface);
1074
1075 if (clip != NULL)
1076 cogl_object_unref (mask_texture);
1077 }
1078
1079 return surface;
1080 }
1081