[mutter] Reduce overpaint in the window group



commit 83f8bfd2caa5b6001749b081372d97c21791fc8f
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Mon Jun 29 14:30:26 2009 -0400

    Reduce overpaint in the window group
    
    When we are painting a stack of 5-10 maximized windows, the
    standard bottom-to-top method of drawing every actor results
    in a tremendous amount of overdraw and can easily max out
    the available memory bandwidth on a low-end* graphics chipset.
    It's even worse if window textures are being accessed over
    the AGP bus.
    
    When we have opaque windows, we can go ahead and compute visibility
    ourselves (in classic X-server fashion) and use that information to
    restrict drawing obscured actors.
    
    * Add MutterWindowGroup - a ClutterGroup subclass with logic
      for figuring out obscured regions.
    
    * Add mutter_window_get_obscured_region() to get the region
      obscured by that window.
    
    * Add mutter_shaped_texture_set_clip_region() to hint
      a clip region to the painting code; this is set based on
      the computed visible region of MutterWindowGroup.
    
    * Add tidy_texture_frame_set_needs_paint() to hint that the
      paint can be skipped entirely; this is used when we detect
      that the window shadow is entirely obscured.
    
    http://bugzilla.gnome.org/show_bug.cgi?id=587344

 src/Makefile.am                          |    2 +
 src/compositor/compositor.c              |    3 +-
 src/compositor/mutter-shaped-texture.c   |  179 ++++++++++++++++++------
 src/compositor/mutter-shaped-texture.h   |    6 +
 src/compositor/mutter-window-group.c     |  194 +++++++++++++++++++++++++
 src/compositor/mutter-window-group.h     |   52 +++++++
 src/compositor/mutter-window-private.h   |    9 ++
 src/compositor/mutter-window.c           |  229 ++++++++++++++++++++++++++++++
 src/compositor/tidy/tidy-texture-frame.c |   31 ++++
 src/compositor/tidy/tidy-texture-frame.h |    3 +
 10 files changed, 664 insertions(+), 44 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 8c4a13b..5bb0fa6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -31,6 +31,8 @@ mutter_SOURCES= 				\
 	compositor/mutter-shaped-texture.c	\
 	compositor/mutter-window.c		\
 	compositor/mutter-window-private.h	\
+	compositor/mutter-window-group.c	\
+	compositor/mutter-window-group.h	\
 	compositor/shadow.c			\
 	compositor/shadow.h			\
 	compositor/mutter-shaped-texture.h	\
diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c
index 9bcdb38..12f2aa2 100644
--- a/src/compositor/compositor.c
+++ b/src/compositor/compositor.c
@@ -12,6 +12,7 @@
 #include "xprops.h"
 #include "prefs.h"
 #include "mutter-window-private.h"
+#include "mutter-window-group.h"
 #include "../core/window-private.h" /* to check window->hidden */
 #include "../core/display-private.h" /* for meta_display_lookup_x_window() */
 #include <X11/extensions/shape.h>
@@ -420,7 +421,7 @@ meta_compositor_manage_screen (MetaCompositor *compositor,
 
   XSelectInput (xdisplay, xwin, event_mask);
 
-  info->window_group = clutter_group_new ();
+  info->window_group = mutter_window_group_new (screen);
   info->overlay_group = clutter_group_new ();
   info->hidden_group = clutter_group_new ();
 
diff --git a/src/compositor/mutter-shaped-texture.c b/src/compositor/mutter-shaped-texture.c
index 9a91150..fd35a61 100755
--- a/src/compositor/mutter-shaped-texture.c
+++ b/src/compositor/mutter-shaped-texture.c
@@ -57,10 +57,13 @@ struct _MutterShapedTexturePrivate
 {
   CoglHandle mask_texture;
   CoglHandle material;
+  CoglHandle material_unshaped;
 #if 1 /* see workaround comment in mutter_shaped_texture_paint */
   CoglHandle material_workaround;
 #endif
 
+  GdkRegion *clip_region;
+
   guint mask_width, mask_height;
 
   GArray *rectangles;
@@ -106,6 +109,11 @@ mutter_shaped_texture_dispose (GObject *object)
       cogl_material_unref (priv->material);
       priv->material = COGL_INVALID_HANDLE;
     }
+  if (priv->material_unshaped != COGL_INVALID_HANDLE)
+    {
+      cogl_material_unref (priv->material_unshaped);
+      priv->material_unshaped = COGL_INVALID_HANDLE;
+    }
 #if 1 /* see comment in mutter_shaped_texture_paint */
   if (priv->material_workaround != COGL_INVALID_HANDLE)
     {
@@ -114,6 +122,8 @@ mutter_shaped_texture_dispose (GObject *object)
     }
 #endif
 
+  mutter_shaped_texture_set_clip_region (self, NULL);
+
   G_OBJECT_CLASS (mutter_shaped_texture_parent_class)->dispose (object);
 }
 
@@ -253,6 +263,9 @@ mutter_shaped_texture_paint (ClutterActor *actor)
   guint depth;
 #endif
 
+  if (priv->clip_region && gdk_region_empty (priv->clip_region))
+    return;
+
   if (!CLUTTER_ACTOR_IS_REALIZED (CLUTTER_ACTOR (stex)))
     clutter_actor_realize (CLUTTER_ACTOR (stex));
 
@@ -264,61 +277,65 @@ mutter_shaped_texture_paint (ClutterActor *actor)
   if (tex_width == 0 || tex_width == 0) /* no contents yet */
     return;
 
-  /* If there are no rectangles fallback to the regular paint
-     method */
-  if (priv->rectangles->len < 1)
-    {
-      CLUTTER_ACTOR_CLASS (mutter_shaped_texture_parent_class)
-        ->paint (actor);
-      return;
-    }
-
   if (paint_tex == COGL_INVALID_HANDLE)
     return;
 
-  mutter_shaped_texture_ensure_mask (stex);
-
-  if (priv->material == COGL_INVALID_HANDLE)
+  if (priv->rectangles->len < 1)
     {
-      priv->material = cogl_material_new ();
+      /* If there are no rectangles use a single-layer texture */
 
-      cogl_material_set_layer_combine (priv->material, 1,
-				       "RGBA = MODULATE (PREVIOUS, TEXTURE[A])",
-				       NULL);
-    }
-  material = priv->material;
+      if (priv->material_unshaped == COGL_INVALID_HANDLE)
+	priv->material_unshaped = cogl_material_new ();
 
-#if 1
-  /* This was added as a workaround. It seems that with the intel
-   * drivers when multi-texturing using an RGB TFP texture, the
-   * texture is actually setup internally as an RGBA texture, where
-   * the alpha channel is mostly 0.0 so you only see a shimmer of the
-   * window. This workaround forcibly defines the alpha channel as
-   * 1.0. Maybe there is some clutter/cogl state that is interacting
-   * with this that is being overlooked, but for now this seems to
-   * work. */
-  g_object_get (stex, "pixmap-depth", &depth, NULL);
-  if (depth == 24)
+      material = priv->material_unshaped;
+    }
+  else
     {
-      if (priv->material_workaround == COGL_INVALID_HANDLE)
-        {
-          material = priv->material_workaround = cogl_material_new ();
+      mutter_shaped_texture_ensure_mask (stex);
 
-	  cogl_material_set_layer_combine (material, 0,
-					   "RGB = MODULATE (TEXTURE, PREVIOUS)"
-					   "A = REPLACE (PREVIOUS)",
-					   NULL);
-	  cogl_material_set_layer_combine (material, 1,
+      if (priv->material == COGL_INVALID_HANDLE)
+	{
+	  priv->material = cogl_material_new ();
+
+	  cogl_material_set_layer_combine (priv->material, 1,
 					   "RGBA = MODULATE (PREVIOUS, TEXTURE[A])",
 					   NULL);
-        }
+	}
+      material = priv->material;
 
-      material = priv->material_workaround;
-    }
+#if 1
+      /* This was added as a workaround. It seems that with the intel
+       * drivers when multi-texturing using an RGB TFP texture, the
+       * texture is actually setup internally as an RGBA texture, where
+       * the alpha channel is mostly 0.0 so you only see a shimmer of the
+       * window. This workaround forcibly defines the alpha channel as
+       * 1.0. Maybe there is some clutter/cogl state that is interacting
+       * with this that is being overlooked, but for now this seems to
+       * work. */
+      g_object_get (stex, "pixmap-depth", &depth, NULL);
+      if (depth == 24)
+	{
+	  if (priv->material_workaround == COGL_INVALID_HANDLE)
+	    {
+	      material = priv->material_workaround = cogl_material_new ();
+
+	      cogl_material_set_layer_combine (material, 0,
+					       "RGB = MODULATE (TEXTURE, PREVIOUS)"
+					       "A = REPLACE (PREVIOUS)",
+					       NULL);
+	      cogl_material_set_layer_combine (material, 1,
+					       "RGBA = MODULATE (PREVIOUS, TEXTURE[A])",
+					       NULL);
+	    }
+
+	  material = priv->material_workaround;
+	}
 #endif
 
+      cogl_material_set_layer (material, 1, priv->mask_texture);
+    }
+
   cogl_material_set_layer (material, 0, paint_tex);
-  cogl_material_set_layer (material, 1, priv->mask_texture);
 
   {
     CoglColor color;
@@ -330,9 +347,51 @@ mutter_shaped_texture_paint (ClutterActor *actor)
   cogl_set_source (material);
 
   clutter_actor_get_allocation_box (actor, &alloc);
+
+  if (priv->clip_region)
+    {
+      GdkRectangle *rects;
+      int n_rects;
+      int i;
+
+      /* Limit to how many separate rectangles we'll draw; beyond this just
+       * fall back and draw the whole thing */
+#     define MAX_RECTS 16
+
+      /* Would be nice to be able to check the number of rects first */
+      gdk_region_get_rectangles (priv->clip_region, &rects, &n_rects);
+      if (n_rects > MAX_RECTS)
+	{
+	  g_free (rects);
+	  /* Fall through to following code */
+	}
+      else
+	{
+	  float coords[MAX_RECTS * 8];
+	  for (i = 0; i < n_rects; i++)
+	    {
+	      GdkRectangle *rect = &rects[i];
+
+	      coords[i * 8 + 0] = rect->x;
+	      coords[i * 8 + 1] = rect->y;
+	      coords[i * 8 + 2] = rect->x + rect->width;
+	      coords[i * 8 + 3] = rect->y + rect->height;
+	      coords[i * 8 + 4] = rect->x / (alloc.x2 - alloc.x1);
+	      coords[i * 8 + 5] = rect->y / (alloc.y2 - alloc.y1);
+	      coords[i * 8 + 6] = (rect->x + rect->width) / (alloc.x2 - alloc.x1);
+	      coords[i * 8 + 7] = (rect->y + rect->height) / (alloc.y2 - alloc.y1);
+	    }
+
+	  g_free (rects);
+
+	  cogl_rectangles_with_texture_coords (coords, n_rects);
+	  return;
+	}
+    }
+
   cogl_rectangle (0, 0,
-                  alloc.x2 - alloc.x1,
-                  alloc.y2 - alloc.y1);
+		  alloc.x2 - alloc.x1,
+		  alloc.y2 - alloc.y1);
 }
 
 static void
@@ -426,3 +485,37 @@ mutter_shaped_texture_add_rectangles (MutterShapedTexture *stex,
   mutter_shaped_texture_dirty_mask (stex);
   clutter_actor_queue_redraw (CLUTTER_ACTOR (stex));
 }
+
+/**
+ * mutter_shaped_texture_set_clip_region:
+ * @frame: a #TidyTextureframe
+ * @clip_region: (transfer full): the region of the texture that
+ *   is visible and should be painted. OWNERSHIP IS ASSUMED BY
+ *   THE FUNCTION (for efficiency to avoid a copy.)
+ *
+ * Provides a hint to the texture about what areas of the texture
+ * are not completely obscured and thus need to be painted. This
+ * is an optimization and is not supposed to have any effect on
+ * the output.
+ *
+ * Typically a parent container will set the clip region before
+ * painting its children, and then unset it afterwards.
+ */
+void
+mutter_shaped_texture_set_clip_region (MutterShapedTexture *stex,
+				       GdkRegion           *clip_region)
+{
+  MutterShapedTexturePrivate *priv;
+
+  g_return_if_fail (MUTTER_IS_SHAPED_TEXTURE (stex));
+
+  priv = stex->priv;
+
+  if (priv->clip_region)
+    {
+      gdk_region_destroy (priv->clip_region);
+      priv->clip_region = NULL;
+    }
+
+  priv->clip_region = clip_region;
+}
diff --git a/src/compositor/mutter-shaped-texture.h b/src/compositor/mutter-shaped-texture.h
index 7e55309..3310bc5 100644
--- a/src/compositor/mutter-shaped-texture.h
+++ b/src/compositor/mutter-shaped-texture.h
@@ -31,6 +31,8 @@
 #include <clutter/glx/clutter-glx.h>
 #endif /* HAVE_GLX_TEXTURE_PIXMAP */
 
+#include <gdk/gdkregion.h>
+
 G_BEGIN_DECLS
 
 #define MUTTER_TYPE_SHAPED_TEXTURE			    \
@@ -90,6 +92,10 @@ void mutter_shaped_texture_add_rectangles (MutterShapedTexture *stex,
 					   size_t num_rects,
 					   const XRectangle *rects);
 
+/* Assumes ownership of clip_region */
+void mutter_shaped_texture_set_clip_region (MutterShapedTexture *stex,
+					    GdkRegion           *clip_region);
+
 G_END_DECLS
 
 #endif /* __MUTTER_SHAPED_TEXTURE_H__ */
diff --git a/src/compositor/mutter-window-group.c b/src/compositor/mutter-window-group.c
new file mode 100644
index 0000000..2436edf
--- /dev/null
+++ b/src/compositor/mutter-window-group.c
@@ -0,0 +1,194 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#define _ISOC99_SOURCE /* for roundf */
+#include <math.h>
+
+#include "mutter-window-private.h"
+#include "mutter-window-group.h"
+
+struct _MutterWindowGroupClass
+{
+  ClutterGroupClass parent_class;
+};
+
+struct _MutterWindowGroup
+{
+  ClutterGroup parent;
+
+  MetaScreen *screen;
+};
+
+G_DEFINE_TYPE (MutterWindowGroup, mutter_window_group, CLUTTER_TYPE_GROUP);
+
+/* We want to find out if the window is "close enough" to
+ * 1:1 transform. We do that by converting the transformed coordinates
+ * to 24.8 fixed-point before checking if they look right.
+ */
+static inline int
+round_to_fixed (float x)
+{
+  return roundf (x * 256);
+}
+
+/* We can only (easily) apply our logic for figuring out what a window
+ * obscures if is not transformed. This function does that check and
+ * as a side effect gets the position of the upper-left corner of the
+ * actors.
+ *
+ * (We actually could handle scaled and non-integrally positioned actors
+ * too as long as they weren't shaped - no filtering is done at the
+ * edges so a rectangle stays a rectangle. But the gain from that is
+ * small, especally since most of our windows are shaped. The simple
+ * case we handle here is the case that matters when the user is just
+ * using the desktop normally.)
+ *
+ * If we assume that the window group is untransformed (it better not
+ * be!) then we could also make this determination by checking directly
+ * if the actor itself is rotated, scaled, or at a non-integral position.
+ * However, the criterion for "close enough" in that case get trickier,
+ * since, for example, the allowed rotation depends on the size of
+ * actor. The approach we take here is to just require everything
+ * to be within 1/256th of a pixel.
+ */
+static gboolean
+actor_is_untransformed (ClutterActor *actor,
+                        int          *x_origin,
+                        int          *y_origin)
+{
+  gfloat widthf, heightf;
+  int width, height;
+  ClutterVertex verts[4];
+  int v0x, v0y, v1x, v1y, v2x, v2y, v3x, v3y;
+  int x, y;
+
+  clutter_actor_get_size (actor, &widthf, &heightf);
+  width = round_to_fixed (widthf); height = round_to_fixed (heightf);
+
+  clutter_actor_get_abs_allocation_vertices (actor, verts);
+  v0x = round_to_fixed (verts[0].x); v0y = round_to_fixed (verts[0].y);
+  v1x = round_to_fixed (verts[1].x); v1y = round_to_fixed (verts[1].y);
+  v2x = round_to_fixed (verts[2].x); v2y = round_to_fixed (verts[2].y);
+  v3x = round_to_fixed (verts[3].x); v3y = round_to_fixed (verts[3].y);
+
+  /* Using shifting for converting fixed => int, gets things right for
+   * negative values. / 256. wouldn't do the same
+   */
+  x = v0x >> 8;
+  y = v0y >> 8;
+
+  /* At integral coordinates? */
+  if (x * 256 != v0x || y * 256 != v0y)
+    return FALSE;
+
+  /* Not scaled? */
+  if (v1x - v0x != width || v2y - v0y != height)
+    return FALSE;
+
+  /* Not rotated/skewed? */
+  if (v0x != v2x || v0y != v1y ||
+      v3x != v1x || v3y != v2y)
+    return FALSE;
+
+  *x_origin = x;
+  *y_origin = y;
+
+  return TRUE;
+}
+
+static void
+mutter_window_group_paint (ClutterActor *actor)
+{
+  MutterWindowGroup *window_group = MUTTER_WINDOW_GROUP (actor);
+  GdkRegion *visible_region;
+  GdkRectangle screen_rect = { 0 };
+  GList *children, *l;
+
+  /* We walk the list from top to bottom (opposite of painting order),
+   * and subtract the opaque area of each window out of the visible
+   * region that we pass to the windows below.
+   */
+  children = clutter_container_get_children (CLUTTER_CONTAINER (actor));
+  children = g_list_reverse (children);
+
+  /* Start off with the full screen area (for a multihead setup, we
+   * might want to use a more accurate union of the monitors to avoid
+   * painting in holes from mismatched monitor sizes. That's just an
+   * optimization, however.)
+   */
+  meta_screen_get_size (window_group->screen, &screen_rect.width, &screen_rect.height);
+  visible_region = gdk_region_rectangle (&screen_rect);
+
+  for (l = children; l; l = l->next)
+    {
+      MutterWindow *cw;
+      gboolean x, y;
+
+      if (!MUTTER_IS_WINDOW (l->data) || !CLUTTER_ACTOR_IS_VISIBLE (l->data))
+        continue;
+
+      cw = l->data;
+
+      if (!actor_is_untransformed (CLUTTER_ACTOR (cw), &x, &y))
+        continue;
+
+      /* Temporarily move to the coordinate system of the actor */
+      gdk_region_offset (visible_region, - x, - y);
+
+      mutter_window_set_visible_region (cw, visible_region);
+
+      if (clutter_actor_get_paint_opacity (actor) == 0xff)
+        {
+          GdkRegion *obscured_region = mutter_window_get_obscured_region (cw);
+          if (obscured_region)
+            gdk_region_subtract (visible_region, obscured_region);
+        }
+
+      mutter_window_set_visible_region_beneath (cw, visible_region);
+      gdk_region_offset (visible_region, x, y);
+    }
+
+  gdk_region_destroy (visible_region);
+
+  CLUTTER_ACTOR_CLASS (mutter_window_group_parent_class)->paint (actor);
+
+  /* Now that we are done painting, unset the visible regions (they will
+   * mess up painting clones of our actors)
+   */
+  for (l = children; l; l = l->next)
+    {
+      MutterWindow *cw;
+
+      if (!MUTTER_IS_WINDOW (l->data))
+        continue;
+
+      cw = l->data;
+      mutter_window_reset_visible_regions (cw);
+    }
+
+  g_list_free (children);
+}
+
+static void
+mutter_window_group_class_init (MutterWindowGroupClass *klass)
+{
+  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+  actor_class->paint = mutter_window_group_paint;
+}
+
+static void
+mutter_window_group_init (MutterWindowGroup *window_group)
+{
+}
+
+ClutterActor *
+mutter_window_group_new (MetaScreen *screen)
+{
+  MutterWindowGroup *window_group;
+
+  window_group = g_object_new (MUTTER_TYPE_WINDOW_GROUP, NULL);
+
+  window_group->screen = screen;
+
+  return CLUTTER_ACTOR (window_group);
+}
diff --git a/src/compositor/mutter-window-group.h b/src/compositor/mutter-window-group.h
new file mode 100644
index 0000000..59edc3b
--- /dev/null
+++ b/src/compositor/mutter-window-group.h
@@ -0,0 +1,52 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#ifndef MUTTER_WINDOW_GROUP_H
+#define MUTTER_WINDOW_GROUP_H
+
+#include <clutter/clutter.h>
+
+#include "screen.h"
+
+/**
+ * MutterWindowGroup:
+ *
+ * This class is a subclass of ClutterGroup with special handling for
+ * MutterWindow when painting the group. When we are painting a stack
+ * of 5-10 maximized windows, the standard bottom-to-top method of
+ * drawing every actor results in a tremendous amount of overdraw
+ * and can easily max out the available memory bandwidth on a low-end
+ * graphics chipset. It's even worse if window textures are being accessed
+ * over the AGP bus.
+ *
+ * The basic technique applied here is to do a pre-pass before painting
+ * where we walk window from top to bottom and compute the visible area
+ * at each step by subtracting out the windows above it. The visible
+ * area is passed to MutterWindow which uses it to clip the portion of
+ * the window which drawn and avoid redrawing the shadow if it is completely
+ * obscured.
+ *
+ * A caveat is that this is ineffective if applications are using ARGB
+ * visuals, since we have no way of knowing whether a window obscures
+ * the windows behind it or not. Alternate approaches using the depth
+ * or stencil buffer rather than client side regions might be able to
+ * handle alpha windows, but the combination of glAlphaFunc and stenciling
+ * tends not to be efficient except on newer cards. (And on newer cards
+ * we have lots of memory and bandwidth.)
+ */
+
+#define MUTTER_TYPE_WINDOW_GROUP            (mutter_window_group_get_type ())
+#define MUTTER_WINDOW_GROUP(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MUTTER_TYPE_WINDOW_GROUP, MutterWindowGroup))
+#define MUTTER_WINDOW_GROUP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), MUTTER_TYPE_WINDOW_GROUP, MutterWindowGroupClass))
+#define MUTTER_IS_WINDOW_GROUP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MUTTER_TYPE_WINDOW_GROUP))
+#define MUTTER_IS_WINDOW_GROU_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), MUTTER_TYPE_WINDOW_GROUP))
+#define MUTTER_WINDOW_GROUP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), MUTTER_TYPE_WINDOW_GROUP, MutterWindowGroupClass))
+
+typedef struct _MutterWindowGroup        MutterWindowGroup;
+typedef struct _MutterWindowGroupClass   MutterWindowGroupClass;
+typedef struct _MutterWindowGroupPrivate MutterWindowGroupPrivate;
+
+GType mutter_window_group_get_type (void);
+
+ClutterActor *mutter_window_group_new (MetaScreen *screen);
+
+#endif /* MUTTER_WINDOW_GROUP_H */
diff --git a/src/compositor/mutter-window-private.h b/src/compositor/mutter-window-private.h
index 2cf5d98..3c0d0f9 100644
--- a/src/compositor/mutter-window-private.h
+++ b/src/compositor/mutter-window-private.h
@@ -4,6 +4,7 @@
 #define MUTTER_WINDOW_PRIVATE_H
 
 #include <X11/extensions/Xdamage.h>
+#include <gdk/gdk.h>
 #include "compositor-mutter.h"
 
 MutterWindow *mutter_window_new (MetaWindow *window);
@@ -36,6 +37,14 @@ void     mutter_window_update_opacity          (MutterWindow       *cw);
 void     mutter_window_mapped                  (MutterWindow       *cw);
 void     mutter_window_unmapped                (MutterWindow       *cw);
 
+GdkRegion *mutter_window_get_obscured_region   (MutterWindow       *cw);
+
+void mutter_window_set_visible_region          (MutterWindow       *cw,
+                                                GdkRegion          *visible_region);
+void mutter_window_set_visible_region_beneath  (MutterWindow       *cw,
+                                                GdkRegion          *beneath_region);
+void mutter_window_reset_visible_regions       (MutterWindow       *cw);
+
 void mutter_window_effect_completed (MutterWindow *actor,
                                      gulong        event);
 
diff --git a/src/compositor/mutter-window.c b/src/compositor/mutter-window.c
index 3270190..0bcd222 100644
--- a/src/compositor/mutter-window.c
+++ b/src/compositor/mutter-window.c
@@ -1,5 +1,8 @@
 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
 
+#define _ISOC99_SOURCE /* for roundf */
+#include <math.h>
+
 #include <X11/extensions/shape.h>
 #include <X11/extensions/Xcomposite.h>
 #include <X11/extensions/Xdamage.h>
@@ -18,6 +21,7 @@
 #include "mutter-shaped-texture.h"
 #include "mutter-window-private.h"
 #include "shadow.h"
+#include "tidy/tidy-texture-frame.h"
 
 struct _MutterWindowPrivate
 {
@@ -38,6 +42,12 @@ struct _MutterWindowPrivate
 
   gchar *           desc;
 
+  /* If the window is shaped, a region that matches the shape */
+  GdkRegion        *shape_region;
+  /* A rectangular region with the unshaped extends of the window
+   * texture */
+  GdkRegion        *bounding_region;
+
   /*
    * These need to be counters rather than flags, since more plugins
    * can implement same effect; the practicality of stacking effects
@@ -91,6 +101,8 @@ static void mutter_window_get_property (GObject      *object,
 static void     mutter_window_detach     (MutterWindow *self);
 static gboolean mutter_window_has_shadow (MutterWindow *self);
 
+static void mutter_window_clear_shape_region    (MutterWindow *self);
+static void mutter_window_clear_bounding_region (MutterWindow *self);
 
 static gboolean is_shaped                (MetaDisplay  *display,
                                           Window        xwindow);
@@ -388,6 +400,9 @@ mutter_window_dispose (GObject *object)
 
   mutter_window_detach (self);
 
+  mutter_window_clear_shape_region (self);
+  mutter_window_clear_bounding_region (self);
+
   if (priv->damage != None)
     {
       meta_error_trap_push (display);
@@ -1214,6 +1229,214 @@ mutter_window_unmapped (MutterWindow *self)
 }
 
 static void
+mutter_window_clear_shape_region (MutterWindow *self)
+{
+  MutterWindowPrivate *priv = self->priv;
+
+  if (priv->shape_region)
+    {
+      gdk_region_destroy (priv->shape_region);
+      priv->shape_region = NULL;
+    }
+}
+
+static void
+mutter_window_clear_bounding_region (MutterWindow *self)
+{
+  MutterWindowPrivate *priv = self->priv;
+
+  if (priv->bounding_region)
+    {
+      gdk_region_destroy (priv->bounding_region);
+      priv->bounding_region = NULL;
+    }
+}
+
+static void
+mutter_window_update_bounding_region (MutterWindow *self,
+                                      int           width,
+                                      int           height)
+{
+  MutterWindowPrivate *priv = self->priv;
+  GdkRectangle bounding_rectangle = { 0, 0, width, height };
+
+  mutter_window_clear_bounding_region (self);
+
+  priv->bounding_region = gdk_region_rectangle (&bounding_rectangle);
+}
+
+static void
+mutter_window_update_shape_region (MutterWindow *self,
+                                   int           n_rects,
+                                   XRectangle   *rects)
+{
+  MutterWindowPrivate *priv = self->priv;
+  int i;
+
+  mutter_window_clear_shape_region (self);
+
+  priv->shape_region = gdk_region_new ();
+  for (i = 0; i < n_rects; i++)
+    {
+      GdkRectangle rect = { rects[i].x, rects[i].y, rects[i].width, rects[i].height };
+      gdk_region_union_with_rect (priv->shape_region, &rect);
+    }
+}
+
+/**
+ * mutter_window_get_obscured_region:
+ * @self: a #MutterWindow
+ *
+ * Gets the region that is completely obscured by the window. Coordinates
+ * are relative to the upper-left of the window.
+ *
+ * Return value: (transfer none): the area obscured by the window,
+ *  %NULL is the same as an empty region.
+ */
+GdkRegion *
+mutter_window_get_obscured_region (MutterWindow *self)
+{
+  MutterWindowPrivate *priv = self->priv;
+
+  if (!priv->argb32 && priv->back_pixmap)
+    {
+      if (priv->shaped)
+        return priv->shape_region;
+      else
+        return priv->bounding_region;
+    }
+  else
+    return NULL;
+}
+
+#if 0
+/* Print out a region; useful for debugging */
+static void
+dump_region (GdkRegion *region)
+{
+  GdkRectangle *rects;
+  int n_rects;
+  int i;
+
+  gdk_region_get_rectangles (region, &rects, &n_rects);
+  g_print ("[");
+  for (i = 0; i < n_rects; i++)
+    {
+      g_print ("+%d+%dx%dx%d ",
+               rects[i].x, rects[i].y, rects[i].width, rects[i].height);
+    }
+  g_print ("]\n");
+  g_free (rects);
+}
+#endif
+
+/**
+ * mutter_window_set_visible_region:
+ * @self: a #MutterWindow
+ * @visible_region: the region of the screen that isn't completely
+ *  obscured.
+ *
+ * Provides a hint as to what areas of the window need to be
+ * drawn. Regions not in @visible_region are completely obscured.
+ * This will be set before painting then unset afterwards.
+ */
+void
+mutter_window_set_visible_region (MutterWindow *self,
+                                  GdkRegion    *visible_region)
+{
+  MutterWindowPrivate *priv = self->priv;
+  GdkRegion *texture_clip_region = NULL;
+
+  /* Get the area of the window texture that would be drawn if
+   * we weren't obscured at all
+   */
+  if (priv->shaped)
+    {
+      if (priv->shape_region)
+        texture_clip_region = gdk_region_copy (priv->shape_region);
+    }
+  else
+    {
+      if (priv->bounding_region)
+        texture_clip_region = gdk_region_copy (priv->bounding_region);
+    }
+
+  if (!texture_clip_region)
+    texture_clip_region = gdk_region_new ();
+
+  /* Then intersect that with the visible region to get the region
+   * that we actually need to redraw.
+   */
+  gdk_region_intersect (texture_clip_region, visible_region);
+
+  /* Assumes ownership */
+  mutter_shaped_texture_set_clip_region (MUTTER_SHAPED_TEXTURE (priv->actor),
+                                         texture_clip_region);
+}
+
+/**
+ * mutter_window_set_visible_region_beneath:
+ * @self: a #MutterWindow
+ * @visible_region: the region of the screen that isn't completely
+ *  obscured beneath the main window texture.
+ *
+ * Provides a hint as to what areas need to be drawn *beneath*
+ * the main window texture.  This is the relevant visible region
+ * when drawing the shadow, properly accounting for areas of the
+ * shadow hid by the window itself. This will be set before painting
+ * then unset afterwards.
+ */
+void
+mutter_window_set_visible_region_beneath (MutterWindow *self,
+                                          GdkRegion    *beneath_region)
+{
+  MutterWindowPrivate *priv = self->priv;
+
+  if (priv->shadow)
+    {
+      GdkRectangle shadow_rect;
+      ClutterActorBox box;
+      GdkOverlapType overlap;
+
+      /* We could compute an full clip region as we do for the window
+       * texture, but the shadow is relatively cheap to draw, and
+       * a little more complex to clip, so we just catch the case where
+       * the shadow is completely obscured and doesn't need to be drawn
+       * at all.
+       */
+      clutter_actor_get_allocation_box (priv->shadow, &box);
+
+      shadow_rect.x = roundf (box.x1);
+      shadow_rect.y = roundf (box.y1);
+      shadow_rect.width = roundf (box.x2 - box.x1);
+      shadow_rect.height = roundf (box.y2 - box.y1);
+
+      overlap = gdk_region_rect_in (beneath_region, &shadow_rect);
+
+      tidy_texture_frame_set_needs_paint (TIDY_TEXTURE_FRAME (priv->shadow),
+                                          overlap != GDK_OVERLAP_RECTANGLE_OUT);
+    }
+}
+
+/**
+ * mutter_window_reset_visible_regions:
+ * @self: a #MutterWindow
+ *
+ * Unsets the regions set by mutter_window_reset_visible_region() and
+ *mutter_window_reset_visible_region_beneath()
+ */
+void
+mutter_window_reset_visible_regions (MutterWindow *self)
+{
+  MutterWindowPrivate *priv = self->priv;
+
+  mutter_shaped_texture_set_clip_region (MUTTER_SHAPED_TEXTURE (priv->actor),
+                                         NULL);
+  if (priv->shadow)
+    tidy_texture_frame_set_needs_paint (TIDY_TEXTURE_FRAME (priv->shadow), TRUE);
+}
+
+static void
 check_needs_repair (MutterWindow *self)
 {
   MutterWindowPrivate *priv     = self->priv;
@@ -1268,6 +1491,7 @@ check_needs_repair (MutterWindow *self)
       if (priv->back_pixmap == None)
         {
           meta_verbose ("Unable to get named pixmap for %p\n", self);
+          mutter_window_update_bounding_region (self, 0, 0);
           return;
         }
 
@@ -1293,6 +1517,8 @@ check_needs_repair (MutterWindow *self)
       if (priv->shadow)
         clutter_actor_set_size (priv->shadow, pxm_width, pxm_height);
 
+      mutter_window_update_bounding_region (self, pxm_width, pxm_height);
+
       full = TRUE;
     }
 
@@ -1389,6 +1615,7 @@ check_needs_reshape (MutterWindow *self)
     return;
 
   mutter_shaped_texture_clear_rectangles (MUTTER_SHAPED_TEXTURE (priv->actor));
+  mutter_window_clear_shape_region (self);
 
 #ifdef HAVE_SHAPE
   if (priv->shaped)
@@ -1408,6 +1635,8 @@ check_needs_reshape (MutterWindow *self)
           mutter_shaped_texture_add_rectangles (MUTTER_SHAPED_TEXTURE (priv->actor),
                                               n_rects, rects);
 
+          mutter_window_update_shape_region (self, n_rects, rects);
+
           XFree (rects);
         }
     }
diff --git a/src/compositor/tidy/tidy-texture-frame.c b/src/compositor/tidy/tidy-texture-frame.c
index 879cb61..1ea70ec 100644
--- a/src/compositor/tidy/tidy-texture-frame.c
+++ b/src/compositor/tidy/tidy-texture-frame.c
@@ -64,6 +64,8 @@ struct _TidyTextureFramePrivate
   gfloat bottom;
 
   CoglHandle material;
+
+  guint needs_paint : 1;
 };
 
 static void
@@ -175,6 +177,9 @@ tidy_texture_frame_paint (ClutterActor *self)
   if (G_UNLIKELY (priv->parent_texture == NULL))
     return;
 
+  if (!priv->needs_paint)
+    return;
+
   /* parent texture may have been hidden, so need to make sure it gets
    * realized
    */
@@ -608,3 +613,29 @@ tidy_texture_frame_get_frame (TidyTextureFrame *frame,
   if (left)
     *left = priv->left;
 }
+
+/**
+ * tidy_texture_frame_set_needs_paint:
+ * @frame: a #TidyTextureframe
+ * @needs_paint: if %FALSE, the paint will be skipped
+ *
+ * Provides a hint to the texture frame that it is totally obscured
+ * and doesn't need to be painted. This would typically be called
+ * by a parent container if it detects the condition prior to
+ * painting its children and then unset afterwards.
+ *
+ * Since it is not supposed to have any effect on display, it does
+ * not queue a repaint.
+ */
+void
+tidy_texture_frame_set_needs_paint (TidyTextureFrame *frame,
+				    gboolean          needs_paint)
+{
+  TidyTextureFramePrivate *priv;
+
+  g_return_if_fail (TIDY_IS_TEXTURE_FRAME (frame));
+
+  priv = frame->priv;
+
+  priv->needs_paint = needs_paint;
+}
diff --git a/src/compositor/tidy/tidy-texture-frame.h b/src/compositor/tidy/tidy-texture-frame.h
index 0c54bfe..71dd40c 100644
--- a/src/compositor/tidy/tidy-texture-frame.h
+++ b/src/compositor/tidy/tidy-texture-frame.h
@@ -76,6 +76,9 @@ void            tidy_texture_frame_get_frame          (TidyTextureFrame *frame,
                                                        gfloat           *bottom,
                                                        gfloat           *left);
 
+void            tidy_texture_frame_set_needs_paint    (TidyTextureFrame *frame,
+                                                       gboolean          needs_paint);
+
 G_END_DECLS
 
 #endif /* _HAVE_TIDY_TEXTURE_FRAME_H */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]