Commit 83f8bfd2 authored by Owen W. Taylor's avatar Owen W. Taylor

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
parent a454ad5c
......@@ -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 \
......
......@@ -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 ();
......
......@@ -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;
}
......@@ -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__ */
/* -*- 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);
}
/* -*- 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 */
......@@ -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);
......
/* -*- 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);
......@@ -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