From 83f8bfd2caa5b6001749b081372d97c21791fc8f Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Mon, 29 Jun 2009 14:30:26 -0400 Subject: [PATCH] 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(-) create mode 100644 src/compositor/mutter-window-group.c create mode 100644 src/compositor/mutter-window-group.h diff --git a/src/Makefile.am b/src/Makefile.am index 8c4a13ba7..5bb0fa66a 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 9bcdb38fd..12f2aa2e5 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 @@ -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 9a91150bb..fd35a61c6 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); + if (priv->material_unshaped == COGL_INVALID_HANDLE) + priv->material_unshaped = cogl_material_new (); + + material = priv->material_unshaped; } - material = priv->material; - -#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) + 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 7e553098d..3310bc5e5 100644 --- a/src/compositor/mutter-shaped-texture.h +++ b/src/compositor/mutter-shaped-texture.h @@ -31,6 +31,8 @@ #include #endif /* HAVE_GLX_TEXTURE_PIXMAP */ +#include + 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 000000000..2436edf45 --- /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 + +#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 000000000..59edc3b6f --- /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 + +#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 2cf5d9883..3c0d0f988 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 +#include #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 327019084..0bcd2225c 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 + #include #include #include @@ -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); @@ -1213,6 +1228,214 @@ mutter_window_unmapped (MutterWindow *self) priv->needs_repair = FALSE; } +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) { @@ -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 879cb6104..1ea70ecb9 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 0c54bfea3..71dd40c00 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 */