[mutter/wip/background-rework: 3/3] Rewrite background code



commit 3c2284e69a8650b0c20de19357e36d2b68e4eedb
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Sun Aug 10 17:26:14 2014 +0200

    Rewrite background code
    
    The old requirement that multiple MetaBackgroundActor objects be
    layered on top of each to produce blended backgrounds resulted in
    extremely inefficient drawing since the entire framebuffer had
    to be read and written multiple times.
    
     * Replace the MetaBackground ClutterContent with a plain GObject
       that serves to hold the background parameters and prerender
       textures to be used to draw the background. It handles
       colors, gradients, and blended images, but does not handle
       vignetting
    
     * Add vignetting to MetaBackgroundActor directly.
    
     * Add MetaBackgroundImage and MetaBackgroundImageCache to allow
       multiple MetaBackground objects to share the same images
    
    By removing the usage of ClutterContent, the following optimizations
    were easy to add:
    
     Blending is turned off when the actor is fully opaque
     Nearest-neighbour filtering is used when drawing 1:1
    
    The GLSL vignette code is slightly improved to use a vertex shader
    snippet for computing the texture coordinate => position in actor
    mapping.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=735637

 src/Makefile.am                          |    6 +-
 src/compositor/cogl-utils.c              |   34 +
 src/compositor/cogl-utils.h              |    4 +
 src/compositor/meta-background-actor.c   |  541 ++++++++++-
 src/compositor/meta-background-image.c   |  342 +++++++
 src/compositor/meta-background-private.h |   15 +
 src/compositor/meta-background.c         | 1595 ++++++++++++------------------
 src/compositor/plugins/default.c         |   17 +-
 src/meta/meta-background-actor.h         |   15 +-
 src/meta/meta-background-image.h         |   76 ++
 src/meta/meta-background.h               |   75 +-
 11 files changed, 1642 insertions(+), 1078 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 1e33608..75b694b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -100,8 +100,10 @@ libmutter_la_SOURCES =                             \
        compositor/compositor.c                 \
        compositor/compositor-private.h         \
        compositor/meta-background.c            \
+       compositor/meta-background-private.h    \
        compositor/meta-background-actor.c      \
        compositor/meta-background-actor-private.h      \
+       compositor/meta-background-image.c      \
        compositor/meta-background-group.c      \
        compositor/meta-cullable.c              \
        compositor/meta-cullable.h              \
@@ -133,6 +135,7 @@ libmutter_la_SOURCES =                              \
        meta/compositor.h                       \
        meta/meta-background.h                  \
        meta/meta-background-actor.h            \
+       meta/meta-background-image.h            \
        meta/meta-background-group.h            \
        meta/meta-plugin.h                      \
        meta/meta-shadow-factory.h              \
@@ -282,9 +285,10 @@ libmutterinclude_headers =                 \
        meta/keybindings.h                      \
        meta/main.h                             \
        meta/meta-backend.h                     \
+       meta/meta-background.h                  \
        meta/meta-background-actor.h            \
+       meta/meta-background-image.h            \
        meta/meta-background-group.h            \
-       meta/meta-background.h                  \
        meta/meta-cursor-tracker.h              \
        meta/meta-idle-monitor.h                \
        meta/meta-plugin.h                      \
diff --git a/src/compositor/cogl-utils.c b/src/compositor/cogl-utils.c
index 68c2127..a1929e3 100644
--- a/src/compositor/cogl-utils.c
+++ b/src/compositor/cogl-utils.c
@@ -64,3 +64,37 @@ meta_create_texture_pipeline (CoglTexture *src_texture)
 
   return pipeline;
 }
+
+static gboolean is_pot(int x)
+{
+  return x > 0 && (x & (x - 1)) == 0;
+}
+
+CoglTexture *
+meta_create_large_texture (int                   width,
+                           int                   height,
+                           CoglTextureComponents components)
+{
+  ClutterBackend *backend = clutter_get_default_backend ();
+  CoglContext *ctx = clutter_backend_get_cogl_context (backend);
+  CoglTexture *texture;
+
+  gboolean should_use_rectangle = FALSE;
+
+  if (!(is_pot (width) && is_pot (height)) &&
+      !cogl_has_feature (ctx, COGL_FEATURE_ID_TEXTURE_NPOT))
+    {
+      if (cogl_has_feature (ctx, COGL_FEATURE_ID_TEXTURE_RECTANGLE))
+        should_use_rectangle = TRUE;
+    }
+
+  if (should_use_rectangle)
+    texture = COGL_TEXTURE (cogl_texture_rectangle_new_with_size (ctx,
+                                                                  width, height));
+  else
+    texture = COGL_TEXTURE (cogl_texture_2d_new_with_size (ctx,
+                                                           width, height));
+  cogl_texture_set_components (texture, components);
+
+  return texture;
+}
diff --git a/src/compositor/cogl-utils.h b/src/compositor/cogl-utils.h
index 281f9b4..ffa51a5 100644
--- a/src/compositor/cogl-utils.h
+++ b/src/compositor/cogl-utils.h
@@ -25,4 +25,8 @@
 
 CoglPipeline * meta_create_texture_pipeline (CoglTexture *texture);
 
+CoglTexture *meta_create_large_texture (int                   width,
+                                        int                   height,
+                                        CoglTextureComponents components);
+
 #endif /* __META_COGL_UTILS_H__ */
diff --git a/src/compositor/meta-background-actor.c b/src/compositor/meta-background-actor.c
index b728287..fcdfa30 100644
--- a/src/compositor/meta-background-actor.c
+++ b/src/compositor/meta-background-actor.c
@@ -1,7 +1,7 @@
 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
 /*
  * Copyright 2009 Sander Dijkhuis
- * Copyright 2010 Red Hat, Inc.
+ * Copyright 2014 Red Hat, Inc.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -26,23 +26,119 @@
  *
  */
 
-#include <config.h>
+/*
+ * The overall model drawing model of this widget is that we have one
+ * texture, or two interpolated textures, possibly with alpha or
+ * margins that let the underlying background show through, blended
+ * over a solid color or a gradient. The result of that combination
+ * can then be affected by a "vignette" that darkens the background
+ * away from a central point (or as a no-GLSL fallback, simply darkens
+ * the background) and by overall opacity.
+ *
+ * As of GNOME 3.14, GNOME is only using a fraction of this when the
+ * user sets the background through the control center - what can be
+ * set is:
+ *
+ *  A single image without a border
+ *  An animation of images without a border that blend together,
+ *   with the blend changing every 4-5 minutes
+ *  A solid color with a repeated noise texture blended over it
+ *
+ * This all is pretty easy to do in a fragment shader, except when:
+ *
+ *  A) We don't have GLSL - in this case, the operation of
+ *     interpolating the two textures and blending the result over the
+ *     background can't be expressed with Cogl's fixed-function layer
+ *     combining (which is confined to what GL's texture environment
+ *     combining can do) So we can only handle the above directly if
+ *     there are no margins or alpha.
+ *
+ *  B) The image textures are sliced. Texture size limits on older
+ *     hardware (pre-965 intel hardware, r300, etc.)  is often 2048,
+ *     and it would be common to use a texture larger than this for a
+ *     background and expect it to be scaled down. Cogl can compensate
+ *     for this by breaking the texture up into multiple textures, but
+ *     can't multitexture with sliced textures. So we can only handle
+ *     the above if there's a single texture.
+ *
+ * However, even when we *can* represent everything in a single pass,
+ * it's not necessarily efficient. If we want to draw a 1024x768
+ * background, it's pretty inefficient to bilinearly texture from
+ * two 2560x1440 images and mix that. So the drawing model we take
+ * here is that MetaBackground generates a single texture (which
+ * might be a 1x1 texture for a solid color, or a 1x2 texture for a
+ * gradient, or a repeated texture for wallpaper, or a pre-rendered
+ * texture the size of the screen), and we draw with that, possibly
+ * adding the vignette and opacity.
+ */
 
-#include <cogl/cogl-texture-pixmap-x11.h>
+#include <config.h>
 
 #include <clutter/clutter.h>
 
-#include <X11/Xatom.h>
-
 #include "cogl-utils.h"
-#include "compositor-private.h"
+#include "clutter-utils.h"
 #include <meta/errors.h>
-#include <meta/meta-background.h>
 #include "meta-background-actor-private.h"
+#include "meta-background-private.h"
 #include "meta-cullable.h"
 
+enum
+{
+  PROP_META_SCREEN = 1,
+  PROP_MONITOR,
+  PROP_BACKGROUND,
+};
+
+typedef enum {
+  CHANGED_BACKGROUND = 1 << 0,
+  CHANGED_EFFECTS = 1 << 2,
+  CHANGED_VIGNETTE_PARAMETERS = 1 << 3,
+  CHANGED_ALL = 0xFFFF
+} ChangedFlags;
+
+#define VERTEX_SHADER_DECLARATIONS                                      \
+"uniform vec2 scale;\n"                                                 \
+"uniform vec2 offset;\n"                                                \
+"varying vec2 position;\n"                                              \
+
+#define VERTEX_SHADER_CODE                                              \
+"position = cogl_tex_coord0_in.xy * scale + offset;\n"                  \
+
+#define FRAGMENT_SHADER_DECLARATIONS                                    \
+"uniform float vignette_sharpness;\n"                                   \
+"varying vec2 position;\n"                                              \
+
+#define FRAGMENT_SHADER_CODE                                                   \
+"float t = 2.0 * length(position);\n"                                          \
+"t = min(t, 1.0);\n"                                                           \
+"float pixel_brightness = 1 - t * vignette_sharpness;\n"                       \
+"cogl_color_out.rgb = cogl_color_out.rgb * pixel_brightness;\n"                \
+
+typedef struct _MetaBackgroundLayer MetaBackgroundLayer;
+
+typedef enum {
+  PIPELINE_VIGNETTE = (1 << 0),
+  PIPELINE_BLEND = (1 << 1),
+} PipelineFlags;
+
 struct _MetaBackgroundActorPrivate
 {
+  MetaScreen *screen;
+  int monitor;
+
+  MetaBackground *background;
+
+  gboolean vignette;
+  float brightness;
+  float vignette_sharpness;
+
+  ChangedFlags changed;
+  CoglPipeline *pipeline;
+  PipelineFlags pipeline_flags;
+  cairo_rectangle_int_t texture_area;
+  gboolean force_bilinear;
+
   cairo_region_t *clip_region;
 };
 
@@ -66,27 +162,45 @@ static void
 meta_background_actor_dispose (GObject *object)
 {
   MetaBackgroundActor *self = META_BACKGROUND_ACTOR (object);
+  MetaBackgroundActorPrivate *priv = self->priv;
 
   set_clip_region (self, NULL);
+  meta_background_actor_set_background (self, NULL);
+  if (priv->pipeline)
+    {
+      cogl_object_unref (priv->pipeline);
+      priv->pipeline = NULL;
+    }
 
   G_OBJECT_CLASS (meta_background_actor_parent_class)->dispose (object);
 }
 
 static void
+get_preferred_size (MetaBackgroundActor *self,
+                    gfloat              *width,
+                    gfloat              *height)
+{
+  MetaBackgroundActorPrivate *priv = META_BACKGROUND_ACTOR (self)->priv;
+  MetaRectangle monitor_geometry;
+
+  meta_screen_get_monitor_geometry (priv->screen, priv->monitor, &monitor_geometry);
+
+  if (width != NULL)
+    *width = monitor_geometry.width;
+
+  if (height != NULL)
+    *height = monitor_geometry.height;
+}
+
+static void
 meta_background_actor_get_preferred_width (ClutterActor *actor,
                                            gfloat        for_height,
                                            gfloat       *min_width_p,
                                            gfloat       *natural_width_p)
 {
-  ClutterContent *content;
   gfloat width;
 
-  content = clutter_actor_get_content (actor);
-
-  if (content)
-    clutter_content_get_preferred_size (content, &width, NULL);
-  else
-    width = 0;
+  get_preferred_size (META_BACKGROUND_ACTOR (actor), &width, NULL);
 
   if (min_width_p)
     *min_width_p = width;
@@ -101,15 +215,9 @@ meta_background_actor_get_preferred_height (ClutterActor *actor,
                                             gfloat       *natural_height_p)
 
 {
-  ClutterContent *content;
   gfloat height;
 
-  content = clutter_actor_get_content (actor);
-
-  if (content)
-    clutter_content_get_preferred_size (content, NULL, &height);
-  else
-    height = 0;
+  get_preferred_size (META_BACKGROUND_ACTOR (actor), NULL, &height);
 
   if (min_height_p)
     *min_height_p = height;
@@ -117,18 +225,306 @@ meta_background_actor_get_preferred_height (ClutterActor *actor,
     *natural_height_p = height;
 }
 
+static CoglPipeline *
+make_pipeline (PipelineFlags pipeline_flags)
+{
+  static CoglPipeline *templates[4];
+  CoglPipeline **templatep;
+
+  templatep = &templates[pipeline_flags];
+  if (*templatep == NULL)
+    {
+      /* Cogl automatically caches pipelines with no eviction policy,
+       * so we need to prevent identical pipelines from getting cached
+       * separately, by reusing the same shader snippets.
+       */
+      *templatep = COGL_PIPELINE (meta_create_texture_pipeline (NULL));
+
+      if ((pipeline_flags & PIPELINE_VIGNETTE) != 0)
+        {
+          static CoglSnippet *snippet;
+
+          if (!snippet)
+            snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_VERTEX,
+                                        VERTEX_SHADER_DECLARATIONS, VERTEX_SHADER_CODE);
+
+          cogl_pipeline_add_snippet (*templatep, snippet);
+        }
+
+      if ((pipeline_flags & PIPELINE_VIGNETTE) != 0)
+        {
+          static CoglSnippet *snippet;
+
+          if (!snippet)
+            snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
+                                        FRAGMENT_SHADER_DECLARATIONS, FRAGMENT_SHADER_CODE);
+
+          cogl_pipeline_add_snippet (*templatep, snippet);
+        }
+
+      if ((pipeline_flags & PIPELINE_BLEND) == 0)
+        cogl_pipeline_set_blend (*templatep, "RGBA = ADD (SRC_COLOR, 0)", NULL);
+    }
+
+  return cogl_pipeline_copy (*templatep);
+}
+
+static void
+setup_pipeline (MetaBackgroundActor   *self,
+                cairo_rectangle_int_t *actor_pixel_rect)
+{
+  MetaBackgroundActorPrivate *priv = self->priv;
+  PipelineFlags pipeline_flags = 0;
+  guint8 opacity;
+  float color_component;
+  CoglPipelineFilter filter;
+
+  opacity = clutter_actor_get_paint_opacity (CLUTTER_ACTOR (self));
+  if (opacity < 255)
+    pipeline_flags |= PIPELINE_BLEND;
+  if (priv->vignette && clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
+    pipeline_flags |= PIPELINE_VIGNETTE;
+
+  if (priv->pipeline &&
+      pipeline_flags != priv->pipeline_flags)
+    {
+      cogl_object_unref (priv->pipeline);
+      priv->pipeline = NULL;
+    }
+
+  if (priv->pipeline == NULL)
+    {
+      priv->pipeline_flags = pipeline_flags;
+      priv->pipeline = make_pipeline (pipeline_flags);
+      priv->changed = CHANGED_ALL;
+    }
+
+  if ((priv->changed & CHANGED_BACKGROUND) != 0)
+    {
+      CoglPipelineWrapMode wrap_mode;
+      CoglTexture *texture = meta_background_get_texture (priv->background,
+                                                          priv->monitor,
+                                                          &priv->texture_area,
+                                                          &wrap_mode);
+      priv->force_bilinear = texture &&
+        (priv->texture_area.width != (int)cogl_texture_get_width (texture) ||
+         priv->texture_area.height != (int)cogl_texture_get_height (texture));
+
+      cogl_pipeline_set_layer_texture (priv->pipeline, 0, texture);
+      cogl_pipeline_set_layer_wrap_mode (priv->pipeline, 0, wrap_mode);
+    }
+
+  if ((priv->changed & CHANGED_VIGNETTE_PARAMETERS) != 0)
+    {
+      cogl_pipeline_set_uniform_1f (priv->pipeline,
+                                    cogl_pipeline_get_uniform_location (priv->pipeline,
+                                                                        "vignette_sharpness"),
+                                    priv->vignette_sharpness);
+    }
+
+  if (priv->vignette)
+    color_component = priv->brightness * opacity / 255.;
+  else
+    color_component = opacity / 255.;
+
+  cogl_pipeline_set_color4f (priv->pipeline,
+                             color_component,
+                             color_component,
+                             color_component,
+                             opacity / 255.);
+
+  if (!priv->force_bilinear &&
+      meta_actor_painting_untransformed (actor_pixel_rect->width, actor_pixel_rect->height, NULL, NULL))
+    filter = COGL_PIPELINE_FILTER_NEAREST;
+  else
+    filter = COGL_PIPELINE_FILTER_LINEAR;
+
+  cogl_pipeline_set_layer_filters (priv->pipeline, 0, filter, filter);
+}
+
+static void
+set_glsl_parameters (MetaBackgroundActor   *self,
+                     cairo_rectangle_int_t *actor_pixel_rect)
+{
+  MetaBackgroundActorPrivate *priv = self->priv;
+  float scale[2];
+  float offset[2];
+
+  /* Compute a scale and offset for transforming texture coordinates to the
+   * coordinate system from [-0.5 to 0.5] across the area of the actor
+   */
+  scale[0] = priv->texture_area.width / (float)actor_pixel_rect->width;
+  scale[1] = priv->texture_area.height / (float)actor_pixel_rect->height;
+  offset[0] = priv->texture_area.x / (float)actor_pixel_rect->width - 0.5;
+  offset[1] = priv->texture_area.y / (float)actor_pixel_rect->height - 0.5;
+
+  cogl_pipeline_set_uniform_float (priv->pipeline,
+                                   cogl_pipeline_get_uniform_location (priv->pipeline,
+                                                                       "scale"),
+                                   2, 1, scale);
+
+  cogl_pipeline_set_uniform_float (priv->pipeline,
+                                   cogl_pipeline_get_uniform_location (priv->pipeline,
+                                                                       "offset"),
+                                   2, 1, offset);
+}
+
+static void
+meta_background_actor_paint (ClutterActor *actor)
+{
+  MetaBackgroundActor *self = META_BACKGROUND_ACTOR (actor);
+  MetaBackgroundActorPrivate *priv = self->priv;
+  ClutterActorBox actor_box;
+  cairo_rectangle_int_t actor_pixel_rect;
+  cairo_region_t *paintable_region = NULL;
+  int n_texture_subareas;
+  int i;
+
+  if ((priv->clip_region && cairo_region_is_empty (priv->clip_region)))
+    return;
+
+  clutter_actor_get_content_box (actor, &actor_box);
+  actor_pixel_rect.x = actor_box.x1;
+  actor_pixel_rect.y = actor_box.y1;
+  actor_pixel_rect.width = actor_box.x2 - actor_box.x1;
+  actor_pixel_rect.height = actor_box.y2 - actor_box.y1;
+
+  /* Now figure out what to actually paint.
+   */
+  paintable_region = cairo_region_create_rectangle (&actor_pixel_rect);
+  if (priv->clip_region != NULL)
+    cairo_region_intersect (paintable_region, priv->clip_region);
+
+  if (cairo_region_is_empty (paintable_region))
+    goto out;
+
+  setup_pipeline (self, &actor_pixel_rect);
+  set_glsl_parameters (self, &actor_pixel_rect);
+
+  /* Finally, split the paintable region up into distinct areas
+   * and paint each area one by one
+   */
+  n_texture_subareas = cairo_region_num_rectangles (paintable_region);
+  for (i = 0; i < n_texture_subareas; i++)
+    {
+      cairo_rectangle_int_t rect;
+      float tx1, tx2, ty1, ty2;
+
+      cairo_region_get_rectangle (paintable_region, i, &rect);
+
+      tx1 = (rect.x - actor_pixel_rect.x - priv->texture_area.x) / (float)priv->texture_area.width;
+      ty1 = (rect.y - actor_pixel_rect.y - priv->texture_area.y) / (float)priv->texture_area.height;
+      tx2 = (rect.x + rect.width - actor_pixel_rect.x - priv->texture_area.x) / 
(float)priv->texture_area.width;
+      ty2 = (rect.y + rect.height - actor_pixel_rect.y - priv->texture_area.y) / 
(float)priv->texture_area.height;
+
+      cogl_framebuffer_draw_textured_rectangle (cogl_get_draw_framebuffer (),
+                                                priv->pipeline,
+                                                rect.x, rect.y,
+                                                rect.x + rect.width, rect.y + rect.height,
+                                                tx1, ty1, tx2, ty2);
+    }
+
+ out:
+  cairo_region_destroy (paintable_region);
+}
+
+static void
+meta_background_actor_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  MetaBackgroundActor *self = META_BACKGROUND_ACTOR (object);
+  MetaBackgroundActorPrivate *priv = self->priv;
+
+  switch (prop_id)
+    {
+    case PROP_META_SCREEN:
+      priv->screen = g_value_get_object (value);
+      break;
+    case PROP_MONITOR:
+      priv->monitor = g_value_get_int (value);
+      break;
+    case PROP_BACKGROUND:
+      meta_background_actor_set_background (self, g_value_get_object (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+meta_background_actor_get_property (GObject      *object,
+                                    guint         prop_id,
+                                    GValue       *value,
+                                    GParamSpec   *pspec)
+{
+  MetaBackgroundActorPrivate *priv = META_BACKGROUND_ACTOR (object)->priv;
+
+  switch (prop_id)
+    {
+    case PROP_META_SCREEN:
+      g_value_set_object (value, priv->screen);
+      break;
+    case PROP_MONITOR:
+      g_value_set_int (value, priv->monitor);
+      break;
+    case PROP_BACKGROUND:
+      g_value_set_object (value, priv->background);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
 static void
 meta_background_actor_class_init (MetaBackgroundActorClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+  GParamSpec *param_spec;
 
   g_type_class_add_private (klass, sizeof (MetaBackgroundActorPrivate));
 
   object_class->dispose = meta_background_actor_dispose;
+  object_class->set_property = meta_background_actor_set_property;
+  object_class->get_property = meta_background_actor_get_property;
 
   actor_class->get_preferred_width = meta_background_actor_get_preferred_width;
   actor_class->get_preferred_height = meta_background_actor_get_preferred_height;
+  actor_class->paint = meta_background_actor_paint;
+
+  param_spec = g_param_spec_object ("meta-screen",
+                                    "MetaScreen",
+                                    "MetaScreen",
+                                    META_TYPE_SCREEN,
+                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_property (object_class,
+                                   PROP_META_SCREEN,
+                                   param_spec);
+
+  param_spec = g_param_spec_int ("monitor",
+                                 "monitor",
+                                 "monitor",
+                                 0, G_MAXINT, 0,
+                                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_property (object_class,
+                                   PROP_MONITOR,
+                                   param_spec);
+
+  param_spec = g_param_spec_object ("background",
+                                    "Background",
+                                    "MetaBackground object holding background parameters",
+                                    META_TYPE_BACKGROUND,
+                                    G_PARAM_READWRITE);
+
+  g_object_class_install_property (object_class,
+                                   PROP_BACKGROUND,
+                                   param_spec);
 }
 
 static void
@@ -141,19 +537,22 @@ meta_background_actor_init (MetaBackgroundActor *self)
 
 /**
  * meta_background_actor_new:
+ * @monitor: Index of the monitor for which to draw the background
  *
  * Creates a new actor to draw the background for the given monitor.
- * This actor should be associated with a #MetaBackground using
- * clutter_actor_set_content()
  *
  * Return value: the newly created background actor
  */
 ClutterActor *
-meta_background_actor_new (void)
+meta_background_actor_new (MetaScreen *screen,
+                           int         monitor)
 {
   MetaBackgroundActor *self;
 
-  self = g_object_new (META_TYPE_BACKGROUND_ACTOR, NULL);
+  self = g_object_new (META_TYPE_BACKGROUND_ACTOR,
+                       "meta-screen", screen,
+                       "monitor", monitor,
+                       NULL);
 
   return CLUTTER_ACTOR (self);
 }
@@ -195,3 +594,95 @@ meta_background_actor_get_clip_region (MetaBackgroundActor *self)
   MetaBackgroundActorPrivate *priv = self->priv;
   return priv->clip_region;
 }
+
+static void
+invalidate_pipeline (MetaBackgroundActor *self,
+                     ChangedFlags         changed)
+{
+  MetaBackgroundActorPrivate *priv = self->priv;
+
+  priv->changed |= changed;
+}
+
+static void
+on_background_changed (MetaBackground      *background,
+                       MetaBackgroundActor *self)
+{
+  invalidate_pipeline (self, CHANGED_BACKGROUND);
+}
+
+void
+meta_background_actor_set_background (MetaBackgroundActor *self,
+                                      MetaBackground      *background)
+{
+  MetaBackgroundActorPrivate *priv;
+
+  g_return_if_fail (META_IS_BACKGROUND_ACTOR (self));
+  g_return_if_fail (background == NULL || META_IS_BACKGROUND (background));
+
+  priv = self->priv;
+
+  if (background == priv->background)
+    return;
+
+  if (priv->background)
+    {
+      g_signal_handlers_disconnect_by_func (priv->background,
+                                            (gpointer)on_background_changed,
+                                            self);
+      g_object_unref (priv->background);
+      priv->background = NULL;
+    }
+
+  if (background)
+    {
+      priv->background = g_object_ref (background);
+      g_signal_connect (priv->background, "changed",
+                        G_CALLBACK (on_background_changed), self);
+    }
+
+  invalidate_pipeline (self, CHANGED_BACKGROUND);
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
+}
+
+void
+meta_background_actor_add_vignette (MetaBackgroundActor *self,
+                                    double               brightness,
+                                    double               sharpness)
+{
+  MetaBackgroundActorPrivate *priv;
+
+  g_return_if_fail (META_IS_BACKGROUND_ACTOR (self));
+  g_return_if_fail (brightness >= 0. && brightness <= 1.);
+  g_return_if_fail (sharpness >= 0.);
+
+  priv = self->priv;
+
+  if (!priv->vignette)
+    invalidate_pipeline (self, CHANGED_EFFECTS);
+
+  priv->vignette = TRUE;
+  priv->brightness = brightness;
+  priv->vignette_sharpness = sharpness;
+  invalidate_pipeline (self, CHANGED_VIGNETTE_PARAMETERS);
+
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
+}
+
+void
+meta_background_actor_remove_vignette (MetaBackgroundActor *self)
+{
+  MetaBackgroundActorPrivate *priv;
+  priv = self->priv;
+
+  g_return_if_fail (META_IS_BACKGROUND_ACTOR (self));
+
+  if (!priv->vignette)
+    return;
+
+  priv->vignette = FALSE;
+
+  invalidate_pipeline (self, CHANGED_EFFECTS);
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
+}
+
diff --git a/src/compositor/meta-background-image.c b/src/compositor/meta-background-image.c
new file mode 100644
index 0000000..d932b6b
--- /dev/null
+++ b/src/compositor/meta-background-image.c
@@ -0,0 +1,342 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright 2014 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:meta-background-image
+ * @title: MetaBackgroundImage
+ * @short_description: objects holding images loaded from files, used for backgrounds
+ */
+
+#include <config.h>
+
+#include <gio/gio.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <clutter/clutter.h>
+#include <meta/meta-background-image.h>
+#include "cogl-utils.h"
+
+enum
+{
+  LOADED,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _MetaBackgroundImageCache
+{
+  GObject parent_instance;
+
+  GHashTable *images;
+};
+
+struct _MetaBackgroundImageCacheClass
+{
+  GObjectClass parent_class;
+};
+
+struct _MetaBackgroundImage
+{
+  GObject parent_instance;
+  char *filename;
+  MetaBackgroundImageCache *cache;
+  gboolean in_cache;
+  gboolean loaded;
+  CoglTexture *texture;
+};
+
+struct _MetaBackgroundImageClass
+{
+  GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (MetaBackgroundImageCache, meta_background_image_cache, G_TYPE_OBJECT);
+
+static void
+meta_background_image_cache_init (MetaBackgroundImageCache *cache)
+{
+  cache->images = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+static void
+meta_background_image_cache_finalize (GObject *object)
+{
+  MetaBackgroundImageCache *cache = META_BACKGROUND_IMAGE_CACHE (object);
+  GHashTableIter iter;
+  gpointer key, value;
+
+  g_hash_table_iter_init (&iter, cache->images);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      MetaBackgroundImage *image = value;
+      image->in_cache = FALSE;
+    }
+
+  g_hash_table_destroy (cache->images);
+
+  G_OBJECT_CLASS (meta_background_image_cache_parent_class)->finalize (object);
+}
+
+static void
+meta_background_image_cache_class_init (MetaBackgroundImageCacheClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_background_image_cache_finalize;
+}
+
+/**
+ * meta_background_image_cache_get_default:
+ *
+ * Return value: (transfer none): the global singleton background cache
+ */
+MetaBackgroundImageCache *
+meta_background_image_cache_get_default (void)
+{
+  static MetaBackgroundImageCache *cache;
+
+  if (cache == NULL)
+    cache = g_object_new (META_TYPE_BACKGROUND_IMAGE_CACHE, NULL);
+
+  return cache;
+}
+
+static void
+load_file (GTask               *task,
+           MetaBackgroundImage *image,
+           gpointer             task_data,
+           GCancellable        *cancellable)
+{
+  GError *error = NULL;
+  GdkPixbuf *pixbuf;
+
+  pixbuf = gdk_pixbuf_new_from_file (image->filename,
+                                     &error);
+
+  if (pixbuf == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_return_pointer (task, pixbuf, (GDestroyNotify) g_object_unref);
+}
+
+static void
+file_loaded (GObject      *source_object,
+             GAsyncResult *result,
+             gpointer      user_data)
+{
+  MetaBackgroundImage *image = META_BACKGROUND_IMAGE (source_object);
+  GError *error = NULL;
+  GTask *task;
+  CoglTexture *texture;
+  GdkPixbuf *pixbuf;
+  int width, height, row_stride;
+  guchar *pixels;
+  gboolean has_alpha;
+
+  task = G_TASK (result);
+  pixbuf = g_task_propagate_pointer (task, &error);
+
+  if (pixbuf == NULL)
+    {
+      g_warning ("Failed to load background '%s': %s",
+                 image->filename, error->message);
+      g_clear_error (&error);
+      goto out;
+    }
+
+  width = gdk_pixbuf_get_width (pixbuf);
+  height = gdk_pixbuf_get_height (pixbuf);
+  row_stride = gdk_pixbuf_get_rowstride (pixbuf);
+  pixels = gdk_pixbuf_get_pixels (pixbuf);
+  has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
+
+  texture = meta_create_large_texture (width, height,
+                                       has_alpha ? COGL_TEXTURE_COMPONENTS_RGBA : 
COGL_TEXTURE_COMPONENTS_RGB);
+  if (!cogl_texture_set_data (texture,
+                              has_alpha ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888,
+                              row_stride,
+                              pixels, 0,
+                              NULL))
+    {
+      g_warning ("Failed to create texture for background");
+      cogl_object_unref (texture);
+    }
+
+  image->texture = texture;
+
+out:
+  image->loaded = TRUE;
+  g_signal_emit (image, signals[LOADED], 0);
+}
+
+/**
+ * meta_background_image_cache_load:
+ * @cache: a #MetaBackgroundImageCache
+ * @filename: filename to load
+ *
+ * Loads an image to use as a background, or returns a reference to an
+ * image that is already in the process of loading or loaded. In either
+ * case, what is returned is a #MetaBackgroundImage which can be derefenced
+ * to get a #CoglTexture. If meta_background_image_is_loaded() returns %TRUE,
+ * the background is loaded, otherwise the MetaBackgroundImage::loaded
+ * signal will be emitted exactly once. The 'loaded' state means that the
+ * loading process finished, whether it succeeded or failed.
+ *
+ * Return value: (transfer full): a #MetaBackgroundImage to dereference to get the loaded texture
+ */
+MetaBackgroundImage *
+meta_background_image_cache_load (MetaBackgroundImageCache *cache,
+                                  const char               *filename)
+{
+  MetaBackgroundImage *image;
+  GTask *task;
+
+  g_return_val_if_fail (META_IS_BACKGROUND_IMAGE_CACHE (cache), NULL);
+  g_return_val_if_fail (filename != NULL, NULL);
+
+  image = g_hash_table_lookup (cache->images, filename);
+  if (image != NULL)
+    return g_object_ref (image);
+
+  image = g_object_new (META_TYPE_BACKGROUND_IMAGE, NULL);
+  image->cache = cache;
+  image->in_cache = TRUE;
+  image->filename = g_strdup (filename);
+  g_hash_table_insert (cache->images, image->filename, image);
+
+  task = g_task_new (image, NULL, file_loaded, NULL);
+
+  g_task_run_in_thread (task, (GTaskThreadFunc) load_file);
+  g_object_unref (task);
+
+  return image;
+}
+
+/**
+ * meta_background_image_cache_purge:
+ * @cache: a #MetaBackgroundImageCache
+ * @filename: filename to remove from the cache
+ *
+ * Remove an entry from the cache; this would be used if monitoring
+ * showed that the file changed.
+ */
+void
+meta_background_image_cache_purge (MetaBackgroundImageCache *cache,
+                                   const char               *filename)
+{
+  MetaBackgroundImage *image;
+
+  g_return_val_if_fail (META_IS_BACKGROUND_IMAGE_CACHE (cache), NULL);
+  g_return_val_if_fail (filename != NULL, NULL);
+
+  image = g_hash_table_lookup (cache->images, filename);
+  if (image == NULL)
+    return;
+
+  g_hash_table_remove (cache->images, image->filename);
+  image->in_cache = FALSE;
+}
+
+G_DEFINE_TYPE (MetaBackgroundImage, meta_background_image, G_TYPE_OBJECT);
+
+static void
+meta_background_image_init (MetaBackgroundImage *image)
+{
+}
+
+static void
+meta_background_image_finalize (GObject *object)
+{
+  MetaBackgroundImage *image = META_BACKGROUND_IMAGE (object);
+
+  if (image->in_cache)
+    g_hash_table_remove (image->cache->images, image->filename);
+
+  if (image->texture)
+    cogl_object_unref (image->texture);
+  if (image->filename)
+    g_free (image->filename);
+
+  G_OBJECT_CLASS (meta_background_image_parent_class)->finalize (object);
+}
+
+static void
+meta_background_image_class_init (MetaBackgroundImageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_background_image_finalize;
+
+  signals[LOADED] =
+    g_signal_new ("loaded",
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+}
+
+/**
+ * meta_background_image_is_loaded:
+ * @image: a #MetaBackgroundImage
+ *
+ * Return value: %TRUE if loading has already completed, %FALSE otherwise
+ */
+gboolean
+meta_background_image_is_loaded (MetaBackgroundImage *image)
+{
+  g_return_val_if_fail (META_IS_BACKGROUND_IMAGE (image), FALSE);
+
+  return image->loaded;
+}
+
+/**
+ * meta_background_image_get_success:
+ * @image: a #MetaBackgroundImage
+ *
+ * This function is a convenience function for checking for success,
+ * without having to call meta_background_image_get_success() and
+ * handle the return of a Cogl type.
+ *
+ * Return value: %TRUE if loading completed successfully, otherwise %FALSE
+ */
+gboolean
+meta_background_image_get_success (MetaBackgroundImage *image)
+{
+  g_return_val_if_fail (META_IS_BACKGROUND_IMAGE (image), FALSE);
+
+  return image->texture != NULL;
+}
+
+/**
+ * meta_background_image_get_texture:
+ * @image: a #MetaBackgroundImage
+ *
+ * Return value: (transfer none): a #CoglTexture if loading succeeded; if
+ *  loading failed or has not yet finished, %NULL.
+ */
+CoglTexture *
+meta_background_image_get_texture (MetaBackgroundImage *image)
+{
+  g_return_val_if_fail (META_IS_BACKGROUND_IMAGE (image), NULL);
+
+  return image->texture;
+}
diff --git a/src/compositor/meta-background-private.h b/src/compositor/meta-background-private.h
new file mode 100644
index 0000000..2799b7c
--- /dev/null
+++ b/src/compositor/meta-background-private.h
@@ -0,0 +1,15 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#ifndef META_BACKGROUND_PRIVATE_H
+#define META_BACKGROUND_PRIVATE_H
+
+#include <config.h>
+
+#include "meta-background-private.h"
+
+CoglTexture *meta_background_get_texture (MetaBackground         *self,
+                                          int                     monitor_index,
+                                          cairo_rectangle_int_t  *texture_area,
+                                          CoglPipelineWrapMode   *wrap_mode);
+
+#endif /* META_BACKGROUND_PRIVATE_H */
diff --git a/src/compositor/meta-background.c b/src/compositor/meta-background.c
index 0f6220f..94f6736 100644
--- a/src/compositor/meta-background.c
+++ b/src/compositor/meta-background.c
@@ -17,680 +17,279 @@
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-/**
- * SECTION:meta-background
- * @title: MetaBackground
- * @short_description: ClutterContent for painting the system background
- *
- */
+#include <meta/meta-background.h>
+#include <meta/meta-background-image.h>
+#include "meta-background-private.h"
+#include "cogl-utils.h"
 
-#include <config.h>
+enum
+{
+  CHANGED,
+  LAST_SIGNAL
+};
 
-#include <clutter/clutter.h>
+static guint signals[LAST_SIGNAL] = { 0 };
 
-#include "cogl-utils.h"
-#include "compositor-private.h"
-#include "mutter-enum-types.h"
-#include <meta/errors.h>
-#include <meta/meta-background.h>
-#include "util-private.h"
-#include "meta-background-actor-private.h"
-
-#define FRAGMENT_SHADER_DECLARATIONS                                           \
-"uniform vec2 texture_scale;\n"                                                \
-"uniform vec2 actor_size;\n"                                                   \
-"uniform vec2 offset;\n"                                                       \
-"uniform float brightness;\n"                                                  \
-"uniform float vignette_sharpness;\n"                                          \
-
-#define VIGNETTE_CODE                                                          \
-"vec2 position = cogl_tex_coord_in[0].xy * texture_scale - offset;\n"          \
-"float t = length(2.0 * (position / actor_size));\n"                           \
-"t = clamp(t, 0.0, 1.0);\n"                                                    \
-"float pixel_brightness = mix(1.0, 1.0 - vignette_sharpness, t);\n"            \
-"cogl_color_out.rgb = cogl_color_out.rgb * pixel_brightness * brightness;\n"
-
-/* We allow creating multiple MetaBackgrounds for the same monitor to
- * allow different rendering options to be set for different copies.
- * But we want to share the same underlying CoglTextures for efficiency and
- * to avoid driver bugs that might occur if we created multiple CoglTexturePixmaps
- * for the same pixmap.
- *
- * This object provides a ClutterContent object to assist in sharing between actors.
- */
+typedef struct _MetaBackgroundMonitor MetaBackgroundMonitor;
 
-struct _MetaBackgroundPrivate
+struct _MetaBackgroundMonitor
 {
-  MetaScreen   *screen;
-  CoglTexture  *texture;
-  CoglPipeline *pipeline;
-  int           monitor;
+  gboolean dirty;
+  CoglTexture *texture;
+  CoglOffscreen *fbo;
+};
 
-  MetaBackgroundEffects effects;
+struct _MetaBackgroundPrivate
+{
+  MetaScreen *screen;
+  MetaBackgroundMonitor *monitors;
+  int n_monitors;
 
   GDesktopBackgroundStyle   style;
   GDesktopBackgroundShading shading_direction;
   ClutterColor              color;
   ClutterColor              second_color;
 
-  char  *filename;
+  char *filename1;
+  MetaBackgroundImage *background_image1;
+  char *filename2;
+  MetaBackgroundImage *background_image2;
 
-  float brightness;
-  float vignette_sharpness;
+  CoglTexture *color_texture;
+  CoglTexture *wallpaper_texture;
+
+  float blend_factor;
 };
 
 enum
 {
   PROP_META_SCREEN = 1,
   PROP_MONITOR,
-  PROP_EFFECTS,
-  PROP_BRIGHTNESS,
-  PROP_VIGNETTE_SHARPNESS,
 };
 
-static void clutter_content_iface_init (ClutterContentIface *iface);
-static void unset_texture (MetaBackground *self);
-
-G_DEFINE_TYPE_WITH_CODE (MetaBackground, meta_background, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
-                                                clutter_content_iface_init))
-
-static gboolean
-meta_background_get_preferred_size (ClutterContent *content,
-                                    gfloat         *width,
-                                    gfloat         *height)
-{
-  MetaBackgroundPrivate *priv = META_BACKGROUND (content)->priv;
-  MetaRectangle monitor_geometry;
-
-  if (priv->texture == NULL)
-    return FALSE;
-
-  meta_screen_get_monitor_geometry (priv->screen, priv->monitor, &monitor_geometry);
-
-  if (width != NULL)
-    *width = monitor_geometry.width;
-
-  if (height != NULL)
-    *height = monitor_geometry.height;
-
-  return TRUE;
-}
+G_DEFINE_TYPE (MetaBackground, meta_background, G_TYPE_OBJECT)
 
 static void
-get_texture_area_and_scale (MetaBackground        *self,
-                            ClutterActorBox       *actor_box,
-                            cairo_rectangle_int_t *texture_area,
-                            float                 *texture_x_scale,
-                            float                 *texture_y_scale)
+free_fbos (MetaBackground *self)
 {
   MetaBackgroundPrivate *priv = self->priv;
-  MetaRectangle monitor_geometry;
-  cairo_rectangle_int_t actor_pixel_rect;
-  cairo_rectangle_int_t image_area;
-  int screen_width, screen_height;
-  float texture_width, texture_height;
-  float actor_x_scale, actor_y_scale;
-  float monitor_x_scale, monitor_y_scale;
-  float x_offset, y_offset;
-
-  meta_screen_get_monitor_geometry (priv->screen, priv->monitor, &monitor_geometry);
 
-  actor_pixel_rect.x = actor_box->x1;
-  actor_pixel_rect.y = actor_box->y1;
-  actor_pixel_rect.width = actor_box->x2 - actor_box->x1;
-  actor_pixel_rect.height = actor_box->y2 - actor_box->y1;
-
-  texture_width = cogl_texture_get_width (priv->texture);
-  actor_x_scale = (1.0 * actor_pixel_rect.width / monitor_geometry.width);
-
-  texture_height = cogl_texture_get_height (priv->texture);
-  actor_y_scale = (1.0 * actor_pixel_rect.height / monitor_geometry.height);
+  int i;
 
-  switch (priv->style)
+  for (i = 0; i < priv->n_monitors; i++)
     {
-      case G_DESKTOP_BACKGROUND_STYLE_STRETCHED:
-      default:
-          /* paint region is whole actor, and the texture
-           * is scaled disproportionately to fit the actor
-           */
-          *texture_area = actor_pixel_rect;
-          *texture_x_scale = 1.0 / actor_pixel_rect.width;
-          *texture_y_scale = 1.0 / actor_pixel_rect.height;
-          break;
-      case G_DESKTOP_BACKGROUND_STYLE_WALLPAPER:
-          /* The wallpaper should be centered in the middle of all monitors.
-           * Therefore, the textured area is the union of all monitors plus
-           * an additional bit to make up for the texture getting centered.  */
-          meta_screen_get_size (priv->screen, &screen_width, &screen_height);
-
-          /* so start by making the unclipped texture area the whole screen */
-          image_area.width = screen_width;
-          image_area.height = screen_height;
-
-          /* If one of the tiles is already centered in the screen, then that tile
-           * will start tile_size/2.0 before the center of the screen. So find out
-           * how far we are from that ideal and adjust by that offset.
-           */
-          x_offset = texture_width - ((int) ((screen_width / 2.0) - (texture_width / 2.0))) % ((int) 
texture_width);
-          y_offset = texture_height - ((int) ((screen_height / 2.0) - (texture_height / 2.0))) % ((int) 
texture_height);
-
-          image_area.width += x_offset;
-          image_area.height += y_offset;
-          image_area.x = -x_offset;
-          image_area.y = -y_offset;
-
-          /* now line up with the appropriate monitor */
-          image_area.x -= monitor_geometry.x;
-          image_area.y -= monitor_geometry.y;
-
-          /* and scale to actor */
-          image_area.x *= actor_x_scale;
-          image_area.y *= actor_y_scale;
-          image_area.width *= actor_x_scale;
-          image_area.height *= actor_y_scale;
-
-          *texture_area = image_area;
-          *texture_x_scale = 1.0 / texture_width;
-          *texture_y_scale = 1.0 / texture_height;
-          break;
-      case G_DESKTOP_BACKGROUND_STYLE_CENTERED:
-          /* paint region is the original image size centered in the actor,
-           * and the texture is scaled to the original image size */
-          image_area.width = texture_width;
-          image_area.height = texture_height;
-          image_area.x = actor_pixel_rect.x + actor_pixel_rect.width / 2 - image_area.width / 2;
-          image_area.y = actor_pixel_rect.y + actor_pixel_rect.height / 2 - image_area.height / 2;
-
-          *texture_area = image_area;
-          *texture_x_scale = 1.0 / texture_width;
-          *texture_y_scale = 1.0 / texture_height;
-          break;
-      case G_DESKTOP_BACKGROUND_STYLE_SCALED:
-      case G_DESKTOP_BACKGROUND_STYLE_ZOOM:
-          /* paint region is the actor size in one dimension, and centered and
-           * scaled by proportional amount in the other dimension.
-           *
-           * SCALED forces the centered dimension to fit on screen.
-           * ZOOM forces the centered dimension to grow off screen
-           */
-          monitor_x_scale = monitor_geometry.width / texture_width;
-          monitor_y_scale = monitor_geometry.height / texture_height;
-
-          if ((priv->style == G_DESKTOP_BACKGROUND_STYLE_SCALED &&
-                (monitor_x_scale < monitor_y_scale)) ||
-              (priv->style == G_DESKTOP_BACKGROUND_STYLE_ZOOM &&
-                (monitor_x_scale > monitor_y_scale)))
-            {
-              /* Fill image to exactly fit actor horizontally */
-              image_area.width = actor_pixel_rect.width;
-              image_area.height = texture_height * monitor_x_scale * actor_y_scale;
-
-              /* Position image centered vertically in actor */
-              image_area.x = actor_pixel_rect.x;
-              image_area.y = actor_pixel_rect.y + actor_pixel_rect.height / 2 - image_area.height / 2;
-            }
-          else
-            {
-              /* Scale image to exactly fit actor vertically */
-              image_area.width = texture_width * monitor_y_scale * actor_x_scale;
-              image_area.height = actor_pixel_rect.height;
-
-              /* Position image centered horizontally in actor */
-              image_area.x = actor_pixel_rect.x + actor_pixel_rect.width / 2 - image_area.width / 2;
-              image_area.y = actor_pixel_rect.y;
-            }
-
-          *texture_area = image_area;
-          *texture_x_scale = 1.0 / image_area.width;
-          *texture_y_scale = 1.0 / image_area.height;
-          break;
-
-      case G_DESKTOP_BACKGROUND_STYLE_SPANNED:
+      MetaBackgroundMonitor *monitor = &priv->monitors[i];
+      if (monitor->fbo)
         {
-          /* paint region is the union of all monitors, with the origin
-           * of the region set to align with monitor associated with the background.
-           */
-          meta_screen_get_size (priv->screen, &screen_width, &screen_height);
-
-          /* unclipped texture area is whole screen */
-          image_area.width = screen_width * actor_x_scale;
-          image_area.height = screen_height * actor_y_scale;
-
-          /* But make (0,0) line up with the appropriate monitor */
-          image_area.x = -monitor_geometry.x * actor_x_scale;
-          image_area.y = -monitor_geometry.y * actor_y_scale;
-
-          *texture_area = image_area;
-          *texture_x_scale = 1.0 / image_area.width;
-          *texture_y_scale = 1.0 / image_area.height;
-          break;
+          cogl_object_unref (monitor->fbo);
+          monitor->fbo = NULL;
+        }
+      if (monitor->texture)
+        {
+          cogl_object_unref (monitor->texture);
+          monitor->texture = NULL;
         }
     }
 }
 
-static CoglPipelineWrapMode
-get_wrap_mode (MetaBackground *self)
+static void
+free_color_texture (MetaBackground *self)
 {
   MetaBackgroundPrivate *priv = self->priv;
-  switch (priv->style)
-    {
-      case G_DESKTOP_BACKGROUND_STYLE_WALLPAPER:
-          return COGL_PIPELINE_WRAP_MODE_REPEAT;
-      case G_DESKTOP_BACKGROUND_STYLE_NONE:
-      case G_DESKTOP_BACKGROUND_STYLE_STRETCHED:
-      case G_DESKTOP_BACKGROUND_STYLE_CENTERED:
-      case G_DESKTOP_BACKGROUND_STYLE_SCALED:
-      case G_DESKTOP_BACKGROUND_STYLE_ZOOM:
-      case G_DESKTOP_BACKGROUND_STYLE_SPANNED:
-      default:
-          return COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE;
-    }
-}
 
-static gboolean
-texture_has_alpha (CoglTexture *texture)
-{
-  if (!texture)
-    return FALSE;
-
-  switch (cogl_texture_get_components (texture))
+  if (priv->color_texture != NULL)
     {
-    case COGL_TEXTURE_COMPONENTS_A:
-    case COGL_TEXTURE_COMPONENTS_RGBA:
-      return TRUE;
-    case COGL_TEXTURE_COMPONENTS_RG:
-    case COGL_TEXTURE_COMPONENTS_RGB:
-    case COGL_TEXTURE_COMPONENTS_DEPTH:
-      return FALSE;
-    default:
-      g_assert_not_reached ();
+      cogl_object_unref (priv->color_texture);
+      priv->color_texture = NULL;
     }
 }
 
-static ClutterPaintNode *
-meta_background_paint_node_new (MetaBackground *self,
-                                ClutterActor   *actor)
+static void
+free_wallpaper_texture (MetaBackground *self)
 {
   MetaBackgroundPrivate *priv = self->priv;
-  ClutterPaintNode *node;
-  guint8 opacity;
-  guint8 color_component;
-  gboolean needs_blending;
 
-  opacity = clutter_actor_get_paint_opacity (actor);
-  color_component = (guint8) (0.5 + opacity * priv->brightness);
-
-  cogl_pipeline_set_color4ub (priv->pipeline,
-                              color_component,
-                              color_component,
-                              color_component,
-                              opacity);
-
-  node = clutter_pipeline_node_new (priv->pipeline);
-
-  needs_blending = (opacity < 255) || (texture_has_alpha (priv->texture));
-
-  if (needs_blending)
-    cogl_pipeline_set_blend (priv->pipeline, "RGBA = ADD (SRC_COLOR, DST_COLOR*(1-SRC_COLOR[A]))", NULL);
-  else
-    cogl_pipeline_set_blend (priv->pipeline, "RGBA = ADD (SRC_COLOR, 0)", NULL);
-
-  return node;
+  if (priv->wallpaper_texture != NULL)
+    {
+      cogl_object_unref (priv->wallpaper_texture);
+      priv->wallpaper_texture = NULL;
+    }
 }
 
 static void
-clip_region_to_actor_box (cairo_region_t  *region,
-                          ClutterActorBox *actor_box)
+on_monitors_changed (MetaScreen     *screen,
+                     MetaBackground *self)
 {
-  cairo_rectangle_int_t clip_rect;
-
-  clip_rect.x = actor_box->x1;
-  clip_rect.y = actor_box->y1;
-  clip_rect.width = actor_box->x2 - actor_box->x1;
-  clip_rect.height = actor_box->y2 - actor_box->y1;
+  MetaBackgroundPrivate *priv = self->priv;
 
-  cairo_region_intersect_rectangle (region, &clip_rect);
-}
+  free_fbos (self);
+  g_free (priv->monitors);
+  priv->monitors = NULL;
+  priv->n_monitors = 0;
 
-static void
-set_vignette_parameters (MetaBackground        *self,
-                         ClutterActorBox       *actor_box,
-                         cairo_rectangle_int_t *texture_area,
-                         float                  texture_x_scale,
-                         float                  texture_y_scale)
-{
-  MetaBackgroundPrivate *priv = self->priv;
-  float                  texture_scale[2];
-  float                  actor_size[2];
-  float                  offset[2];
-
-  if (!(priv->effects & META_BACKGROUND_EFFECTS_VIGNETTE))
-    return;
-
-  texture_scale[0] = 1.0 / texture_x_scale;
-  texture_scale[1] = 1.0 / texture_y_scale;
-  actor_size[0] = actor_box->x2 - actor_box->x1;
-  actor_size[1] = actor_box->y2 - actor_box->y1;
-  offset[0] = -texture_area->x + (actor_size[0] / 2.0);
-  offset[1] = -texture_area->y + (actor_size[1] / 2.0);
-
-  cogl_pipeline_set_uniform_float (priv->pipeline,
-                                   cogl_pipeline_get_uniform_location (priv->pipeline,
-                                                                       "texture_scale"),
-                                   2, 1, texture_scale);
-
-  cogl_pipeline_set_uniform_float (priv->pipeline,
-                                   cogl_pipeline_get_uniform_location (priv->pipeline,
-                                                                       "actor_size"),
-                                   2, 1, actor_size);
-
-  cogl_pipeline_set_uniform_float (priv->pipeline,
-                                   cogl_pipeline_get_uniform_location (priv->pipeline,
-                                                                       "offset"),
-                                   2, 1, offset);
+  if (priv->screen)
+    {
+      priv->n_monitors = meta_screen_get_n_monitors (screen);
+      priv->monitors = g_new0 (MetaBackgroundMonitor, priv->n_monitors);
+    }
 }
 
 static void
-meta_background_paint_content (ClutterContent   *content,
-                               ClutterActor     *actor,
-                               ClutterPaintNode *root)
+set_screen (MetaBackground *self,
+            MetaScreen     *screen)
 {
-  MetaBackground *self = META_BACKGROUND (content);
   MetaBackgroundPrivate *priv = self->priv;
-  ClutterPaintNode *node;
-  ClutterActorBox actor_box;
-  cairo_rectangle_int_t texture_area;
-  cairo_region_t *paintable_region = NULL;
-  int n_texture_subareas;
-  int i;
-  float texture_x_scale, texture_y_scale;
-  float tx1 = 0.0, ty1 = 0.0, tx2 = 1.0, ty2 = 1.0;
-
-  if (priv->texture == NULL)
-    return;
-
-  clutter_actor_get_content_box (actor, &actor_box);
 
-  /* First figure out where on the monitor the texture is supposed to be painted.
-   * If the actor is not the size of the monitor, this function makes sure to scale
-   * everything down to fit in the actor.
-   */
-  get_texture_area_and_scale (self,
-                              &actor_box,
-                              &texture_area,
-                              &texture_x_scale,
-                              &texture_y_scale);
-
-  set_vignette_parameters (self, &actor_box, &texture_area, texture_x_scale, texture_y_scale);
-
-  /* Now figure out what to actually paint. We start by clipping the texture area to
-   * the actor's bounds.
-   */
-  paintable_region = cairo_region_create_rectangle (&texture_area);
-
-  clip_region_to_actor_box (paintable_region, &actor_box);
-
-  /* And then cut out any parts occluded by window actors
-   */
-  if (META_IS_BACKGROUND_ACTOR (actor))
+  if (priv->screen != NULL)
     {
-      cairo_region_t *clip_region;
-      clip_region = meta_background_actor_get_clip_region (META_BACKGROUND_ACTOR (actor));
-
-      if (clip_region != NULL)
-        cairo_region_intersect (paintable_region, clip_region);
+      g_signal_handlers_disconnect_by_func (priv->screen,
+                                            (gpointer)on_monitors_changed,
+                                            self);
     }
 
-  if (cairo_region_is_empty (paintable_region))
-    goto out;
-
-  node = meta_background_paint_node_new (self, actor);
+  priv->screen = screen;
 
-  /* Finally, split the paintable region up into distinct areas
-   * and paint each area one by one
-   */
-  n_texture_subareas = cairo_region_num_rectangles (paintable_region);
-  for (i = 0; i < n_texture_subareas; i++)
+  if (priv->screen != NULL)
     {
-      cairo_rectangle_int_t texture_subarea;
-      ClutterActorBox texture_rectangle;
-
-      cairo_region_get_rectangle (paintable_region, i, &texture_subarea);
-
-      tx1 = (texture_subarea.x - texture_area.x) * texture_x_scale;
-      ty1 = (texture_subarea.y - texture_area.y) * texture_y_scale;
-      tx2 = (texture_subarea.x + texture_subarea.width - texture_area.x) * texture_x_scale;
-      ty2 = (texture_subarea.y + texture_subarea.height - texture_area.y) * texture_y_scale;
-      texture_rectangle.x1 = texture_subarea.x;
-      texture_rectangle.y1 = texture_subarea.y;
-      texture_rectangle.x2 = texture_subarea.x + texture_subarea.width;
-      texture_rectangle.y2 = texture_subarea.y + texture_subarea.height;
-
-      clutter_paint_node_add_texture_rectangle (node, &texture_rectangle, tx1, ty1, tx2, ty2);
+      g_signal_connect (priv->screen, "monitors-changed",
+                        G_CALLBACK (on_monitors_changed), self);
     }
-  clutter_paint_node_add_child (root, node);
-  clutter_paint_node_unref (node);
 
- out:
-  cairo_region_destroy (paintable_region);
+  on_monitors_changed (priv->screen, self);
 }
 
 static void
-clutter_content_iface_init (ClutterContentIface *iface)
+meta_background_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
 {
-  iface->get_preferred_size = meta_background_get_preferred_size;
-  iface->paint_content = meta_background_paint_content;
+  switch (prop_id)
+    {
+    case PROP_META_SCREEN:
+      set_screen (META_BACKGROUND (object), g_value_get_object (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
 }
 
 static void
-meta_background_dispose (GObject *object)
+meta_background_get_property (GObject      *object,
+                              guint         prop_id,
+                              GValue       *value,
+                              GParamSpec   *pspec)
 {
-  MetaBackground        *self = META_BACKGROUND (object);
-  MetaBackgroundPrivate *priv = self->priv;
-
-  unset_texture (self);
-
-  g_clear_pointer (&priv->pipeline,
-                   (GDestroyNotify)
-                   cogl_object_unref);
+  MetaBackgroundPrivate *priv = META_BACKGROUND (object)->priv;
 
-  G_OBJECT_CLASS (meta_background_parent_class)->dispose (object);
+  switch (prop_id)
+    {
+    case PROP_META_SCREEN:
+      g_value_set_object (value, priv->screen);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
 }
 
-static void
-meta_background_finalize (GObject *object)
+static gboolean
+need_prerender (MetaBackground *self)
 {
-  MetaBackground        *self = META_BACKGROUND (object);
   MetaBackgroundPrivate *priv = self->priv;
+  CoglTexture *texture1 = priv->background_image1 ? meta_background_image_get_texture 
(priv->background_image1) : NULL;
+  CoglTexture *texture2 = priv->background_image2 ? meta_background_image_get_texture 
(priv->background_image2) : NULL;
 
-  g_free (priv->filename);
+  if (texture1 == NULL && texture2 == NULL)
+    return FALSE;
 
-  G_OBJECT_CLASS (meta_background_parent_class)->finalize (object);
-}
+  if (texture2 == NULL && priv->style == G_DESKTOP_BACKGROUND_STYLE_WALLPAPER)
+    return FALSE;
 
-static void
-ensure_pipeline (MetaBackground *self)
-{
-  if (self->priv->pipeline == NULL)
-    self->priv->pipeline = COGL_PIPELINE (meta_create_texture_pipeline (NULL));
+  return TRUE;
 }
 
 static void
-set_brightness (MetaBackground *self,
-                gfloat          brightness)
+mark_changed (MetaBackground *self)
 {
   MetaBackgroundPrivate *priv = self->priv;
+  int i;
 
-  if (priv->brightness == brightness)
-    return;
-
-  priv->brightness = brightness;
+  if (!need_prerender (self))
+    free_fbos (self);
 
-  if (clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL) &&
-      priv->effects & META_BACKGROUND_EFFECTS_VIGNETTE)
-    {
-      ensure_pipeline (self);
-      cogl_pipeline_set_uniform_1f (priv->pipeline,
-                                    cogl_pipeline_get_uniform_location (priv->pipeline,
-                                                                        "brightness"),
-                                    priv->brightness);
-    }
-  else
-    {
-      ensure_pipeline (self);
-      CoglColor blend_color;
-      cogl_color_init_from_4f (&blend_color, brightness, brightness, brightness, 1.0);
-      cogl_pipeline_set_layer_combine (priv->pipeline, 1, "RGB=MODULATE(PREVIOUS, CONSTANT) 
A=REPLACE(PREVIOUS)", NULL);
-      cogl_pipeline_set_layer_combine_constant (priv->pipeline, 1, &blend_color);
-    }
-
-  clutter_content_invalidate (CLUTTER_CONTENT (self));
+  for (i = 0; i < priv->n_monitors; i++)
+    priv->monitors[i].dirty = TRUE;
 
-  g_object_notify (G_OBJECT (self), "brightness");
+  g_signal_emit (self, signals[CHANGED], 0);
 }
 
 static void
-set_vignette_sharpness (MetaBackground *self,
-                        gfloat          sharpness)
+on_background_loaded (MetaBackgroundImage *image,
+                      MetaBackground      *self)
 {
-  MetaBackgroundPrivate *priv = self->priv;
-
-  if (priv->vignette_sharpness == sharpness)
-    return;
-
-  priv->vignette_sharpness = sharpness;
-
-  if (!clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
-    return;
-
-  if (priv->effects & META_BACKGROUND_EFFECTS_VIGNETTE)
-    {
-      ensure_pipeline (self);
-      cogl_pipeline_set_uniform_1f (priv->pipeline,
-                                    cogl_pipeline_get_uniform_location (priv->pipeline,
-                                                                        "vignette_sharpness"),
-                                    priv->vignette_sharpness);
-    }
-
-  clutter_content_invalidate (CLUTTER_CONTENT (self));
-
-  g_object_notify (G_OBJECT (self), "vignette-sharpness");
+  mark_changed (self);
 }
 
 static void
-add_vignette (MetaBackground *self)
+set_filename (MetaBackground       *self,
+              char                **filenamep,
+              MetaBackgroundImage **imagep,
+              const char           *filename)
 {
-  MetaBackgroundPrivate *priv = self->priv;
-  static CoglSnippet *snippet = NULL;
-
-  if (!clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
-    return;
-
-  ensure_pipeline (self);
-
-  /* Cogl automatically caches pipelines with no eviction policy,
-   * so we need to prevent identical pipelines from getting cached
-   * separately, by reusing the same fragement shader snippet.
-   */
-  if (snippet == NULL)
-    snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT, FRAGMENT_SHADER_DECLARATIONS, VIGNETTE_CODE);
-
-  cogl_pipeline_add_snippet (priv->pipeline, snippet);
+  if (g_strcmp0 (filename, *filenamep) != 0)
+    {
+      g_free (*filenamep);
+      *filenamep = g_strdup (filename);
 
-  cogl_pipeline_set_uniform_1f (priv->pipeline,
-                                cogl_pipeline_get_uniform_location (priv->pipeline,
-                                                                    "brightness"),
-                                priv->brightness);
+      if (*imagep)
+        {
+          g_signal_handlers_disconnect_by_func (*imagep,
+                                                (gpointer)on_background_loaded,
+                                                self);
+          g_object_unref (*imagep);
+          *imagep = NULL;
+        }
 
-  cogl_pipeline_set_uniform_1f (priv->pipeline,
-                                cogl_pipeline_get_uniform_location (priv->pipeline,
-                                                                    "vignette_sharpness"),
-                                priv->vignette_sharpness);
+      if (filename)
+        {
+          MetaBackgroundImageCache *cache = meta_background_image_cache_get_default ();
+          *imagep = meta_background_image_cache_load (cache, filename);
+          g_signal_connect (*imagep, "loaded",
+                            G_CALLBACK (on_background_loaded), self);
+        }
+    }
 }
 
 static void
-set_effects (MetaBackground        *self,
-             MetaBackgroundEffects  effects)
+meta_background_dispose (GObject *object)
 {
+  MetaBackground        *self = META_BACKGROUND (object);
   MetaBackgroundPrivate *priv = self->priv;
 
-  priv->effects = effects;
+  free_color_texture (self);
+  free_wallpaper_texture (self);
 
-  if ((priv->effects & META_BACKGROUND_EFFECTS_VIGNETTE))
-    add_vignette (self);
+  set_filename (self, &priv->filename1, &priv->background_image1, NULL);
+  set_filename (self, &priv->filename2, &priv->background_image2, NULL);
 
-  clutter_content_invalidate (CLUTTER_CONTENT (self));
-}
+  set_screen (self, NULL);
 
-static void
-meta_background_set_property (GObject      *object,
-                              guint         prop_id,
-                              const GValue *value,
-                              GParamSpec   *pspec)
-{
-  MetaBackground        *self = META_BACKGROUND (object);
-  MetaBackgroundPrivate *priv = self->priv;
-
-  switch (prop_id)
-    {
-    case PROP_META_SCREEN:
-      priv->screen = g_value_get_object (value);
-      break;
-    case PROP_MONITOR:
-      priv->monitor = g_value_get_int (value);
-      break;
-    case PROP_EFFECTS:
-      set_effects (self, g_value_get_flags (value));
-      break;
-    case PROP_BRIGHTNESS:
-      set_brightness (self, g_value_get_float (value));
-      break;
-    case PROP_VIGNETTE_SHARPNESS:
-      set_vignette_sharpness (self, g_value_get_float (value));
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-    }
+  G_OBJECT_CLASS (meta_background_parent_class)->dispose (object);
 }
 
 static void
-meta_background_get_property (GObject      *object,
-                              guint         prop_id,
-                              GValue       *value,
-                              GParamSpec   *pspec)
+meta_background_finalize (GObject *object)
 {
-  MetaBackgroundPrivate *priv = META_BACKGROUND (object)->priv;
-
-  switch (prop_id)
-    {
-    case PROP_META_SCREEN:
-      g_value_set_object (value, priv->screen);
-      break;
-    case PROP_MONITOR:
-      g_value_set_int (value, priv->monitor);
-      break;
-    case PROP_EFFECTS:
-      g_value_set_flags (value, priv->effects);
-      break;
-    case PROP_BRIGHTNESS:
-      g_value_set_float (value, priv->brightness);
-      break;
-    case PROP_VIGNETTE_SHARPNESS:
-      g_value_set_float (value, priv->vignette_sharpness);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-      break;
-    }
+  G_OBJECT_CLASS (meta_background_parent_class)->finalize (object);
 }
 
 static void
 meta_background_class_init (MetaBackgroundClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GParamSpec   *param_spec;
+  GParamSpec *param_spec;
 
   g_type_class_add_private (klass, sizeof (MetaBackgroundPrivate));
 
@@ -699,49 +298,24 @@ meta_background_class_init (MetaBackgroundClass *klass)
   object_class->set_property = meta_background_set_property;
   object_class->get_property = meta_background_get_property;
 
+  signals[CHANGED] =
+    g_signal_new ("changed",
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+
   param_spec = g_param_spec_object ("meta-screen",
                                     "MetaScreen",
                                     "MetaScreen",
                                     META_TYPE_SCREEN,
-                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
 
   g_object_class_install_property (object_class,
                                    PROP_META_SCREEN,
                                    param_spec);
 
-  param_spec = g_param_spec_int ("monitor",
-                                 "monitor",
-                                 "monitor",
-                                 0, G_MAXINT, 0,
-                                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
-
-  g_object_class_install_property (object_class,
-                                   PROP_MONITOR,
-                                   param_spec);
-
-  param_spec = g_param_spec_float ("brightness",
-                                   "brightness",
-                                   "Values less than 1.0 dim background",
-                                   0.0, 1.0,
-                                   1.0,
-                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
-  g_object_class_install_property (object_class, PROP_BRIGHTNESS, param_spec);
-
-  param_spec = g_param_spec_float ("vignette-sharpness",
-                                   "vignette-sharpness",
-                                   "How obvious the vignette fringe is",
-                                   0.0, 1.0,
-                                   0.7,
-                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
-  g_object_class_install_property (object_class, PROP_VIGNETTE_SHARPNESS, param_spec);
-
-  param_spec = g_param_spec_flags ("effects",
-                                   "Effects",
-                                   "Set to enable vignette",
-                                  meta_background_effects_get_type (),
-                                   META_BACKGROUND_EFFECTS_NONE,
-                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
-  g_object_class_install_property (object_class, PROP_EFFECTS, param_spec);
 }
 
 static void
@@ -753,492 +327,535 @@ meta_background_init (MetaBackground *self)
 }
 
 static void
-unset_texture (MetaBackground *self)
+get_texture_area (MetaBackground          *self,
+                  cairo_rectangle_int_t   *monitor_rect,
+                  CoglTexture             *texture,
+                  cairo_rectangle_int_t   *texture_area)
 {
   MetaBackgroundPrivate *priv = self->priv;
-  if (priv->pipeline != NULL)
-    cogl_pipeline_set_layer_texture (priv->pipeline, 0, NULL);
+  cairo_rectangle_int_t image_area;
+  int screen_width, screen_height;
+  float texture_width, texture_height;
+  float monitor_x_scale, monitor_y_scale;
 
-  g_clear_pointer (&priv->texture,
-                   (GDestroyNotify)
-                   cogl_object_unref);
-}
+  texture_width = cogl_texture_get_width (texture);
+  texture_height = cogl_texture_get_height (texture);
 
-static void
-set_texture (MetaBackground *self,
-             CoglTexture    *texture)
-{
-  MetaBackgroundPrivate *priv = self->priv;
+  switch (priv->style)
+    {
+    case G_DESKTOP_BACKGROUND_STYLE_STRETCHED:
+    default:
+      /* paint region is whole actor, and the texture
+       * is scaled disproportionately to fit the actor
+       */
+      *texture_area = *monitor_rect;
+      break;
+    case G_DESKTOP_BACKGROUND_STYLE_WALLPAPER:
+      meta_screen_get_size (priv->screen, &screen_width, &screen_height);
+
+      /* Start off by centering a tile in the middle of the
+       * total screen area.
+       */
+      image_area.x = (screen_width - texture_width) / 2.0;
+      image_area.y = (screen_height - texture_height) / 2.0;
+      image_area.width = texture_width;
+      image_area.height = texture_height;
+
+      /* Translate into the coordinate system of the particular monitor */
+      image_area.x -= monitor_rect->x;
+      image_area.y -= monitor_rect->y;
+
+      *texture_area = image_area;
+      break;
+    case G_DESKTOP_BACKGROUND_STYLE_CENTERED:
+      /* paint region is the original image size centered in the actor,
+       * and the texture is scaled to the original image size */
+      image_area.width = texture_width;
+      image_area.height = texture_height;
+      image_area.x = monitor_rect->x + monitor_rect->width / 2 - image_area.width / 2;
+      image_area.y = monitor_rect->y + monitor_rect->height / 2 - image_area.height / 2;
+
+      *texture_area = image_area;
+      break;
+    case G_DESKTOP_BACKGROUND_STYLE_SCALED:
+    case G_DESKTOP_BACKGROUND_STYLE_ZOOM:
+      /* paint region is the actor size in one dimension, and centered and
+       * scaled by proportional amount in the other dimension.
+       *
+       * SCALED forces the centered dimension to fit on screen.
+       * ZOOM forces the centered dimension to grow off screen
+       */
+      monitor_x_scale = monitor_rect->width / texture_width;
+      monitor_y_scale = monitor_rect->height / texture_height;
+
+      if ((priv->style == G_DESKTOP_BACKGROUND_STYLE_SCALED &&
+           (monitor_x_scale < monitor_y_scale)) ||
+          (priv->style == G_DESKTOP_BACKGROUND_STYLE_ZOOM &&
+           (monitor_x_scale > monitor_y_scale)))
+        {
+          /* Fill image to exactly fit actor horizontally */
+          image_area.width = monitor_rect->width;
+          image_area.height = texture_height * monitor_x_scale;
 
-  priv->texture = texture;
-  cogl_pipeline_set_layer_texture (priv->pipeline, 0, priv->texture);
-}
+          /* Position image centered vertically in actor */
+          image_area.x = monitor_rect->x;
+          image_area.y = monitor_rect->y + monitor_rect->height / 2 - image_area.height / 2;
+        }
+      else
+        {
+          /* Scale image to exactly fit actor vertically */
+          image_area.width = texture_width * monitor_y_scale;
+          image_area.height = monitor_rect->height;
 
-static void
-set_style (MetaBackground          *self,
-           GDesktopBackgroundStyle  style)
-{
-  MetaBackgroundPrivate *priv = self->priv;
-  CoglPipelineWrapMode   wrap_mode;
+          /* Position image centered horizontally in actor */
+          image_area.x = monitor_rect->x + monitor_rect->width / 2 - image_area.width / 2;
+          image_area.y = monitor_rect->y;
+        }
 
-  priv->style = style;
+      *texture_area = image_area;
+      break;
 
-  wrap_mode = get_wrap_mode (self);
-  cogl_pipeline_set_layer_wrap_mode (priv->pipeline, 0, wrap_mode);
-}
+    case G_DESKTOP_BACKGROUND_STYLE_SPANNED:
+      {
+        /* paint region is the union of all monitors, with the origin
+         * of the region set to align with monitor associated with the background.
+         */
+        meta_screen_get_size (priv->screen, &screen_width, &screen_height);
 
-static void
-set_filename (MetaBackground *self,
-              const char     *filename)
-{
-  MetaBackgroundPrivate *priv = self->priv;
+        /* unclipped texture area is whole screen */
+        image_area.width = screen_width;
+        image_area.height = screen_height;
 
-  g_free (priv->filename);
-  priv->filename = g_strdup (filename);
+        /* But make (0,0) line up with the appropriate monitor */
+        image_area.x = -monitor_rect->x;
+        image_area.y = -monitor_rect->y;
+
+        *texture_area = image_area;
+        break;
+      }
+    }
 }
 
-/**
- * meta_background_load_gradient:
- * @self: the #MetaBackground
- * @shading_direction: the orientation of the gradient
- * @color: the start color of the gradient
- * @second_color: the end color of the gradient
- *
- * Clears any previously set background, and sets the background gradient.
- * The gradient starts with @color and
- * progresses toward @second_color in the direction of @shading_direction.
- */
-void
-meta_background_load_gradient (MetaBackground             *self,
-                               GDesktopBackgroundShading   shading_direction,
-                               ClutterColor               *color,
-                               ClutterColor               *second_color)
+static void
+draw_texture (MetaBackground        *self,
+              CoglFramebuffer       *framebuffer,
+              CoglPipeline          *pipeline,
+              CoglTexture           *texture,
+              cairo_rectangle_int_t *monitor_area)
 {
-  ClutterBackend *backend = clutter_get_default_backend ();
-  CoglContext *ctx = clutter_backend_get_cogl_context (backend);
   MetaBackgroundPrivate *priv = self->priv;
-  CoglTexture *texture;
-  guint width, height;
-  uint8_t pixels[8];
-
-  ensure_pipeline (self);
-
-  unset_texture (self);
-  set_style (self, G_DESKTOP_BACKGROUND_STYLE_NONE);
+  cairo_rectangle_int_t texture_area;
 
-  priv->shading_direction = shading_direction;
+  get_texture_area (self, monitor_area, texture, &texture_area);
 
-  switch (priv->shading_direction)
+  switch (priv->style)
     {
-      case G_DESKTOP_BACKGROUND_SHADING_VERTICAL:
-          width = 1;
-          height = 2;
-          break;
-      case G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL:
-          width = 2;
-          height = 1;
-          break;
-      default:
-          g_return_if_reached ();
+    case G_DESKTOP_BACKGROUND_STYLE_STRETCHED:
+    case G_DESKTOP_BACKGROUND_STYLE_WALLPAPER:
+    case G_DESKTOP_BACKGROUND_STYLE_ZOOM:
+    case G_DESKTOP_BACKGROUND_STYLE_SPANNED:
+      /* Draw the entire monitor */
+      cogl_framebuffer_draw_textured_rectangle (framebuffer,
+                                                pipeline,
+                                                0,
+                                                0,
+                                                monitor_area->width,
+                                                monitor_area->height,
+                                                - texture_area.x / (float)texture_area.width,
+                                                - texture_area.y / (float)texture_area.height,
+                                                (monitor_area->width - texture_area.x) / 
(float)texture_area.width,
+                                                (monitor_area->height - texture_area.y) / 
(float)texture_area.height);
+      /* Draw just the texture */
+      break;
+    case G_DESKTOP_BACKGROUND_STYLE_CENTERED:
+    case G_DESKTOP_BACKGROUND_STYLE_SCALED:
+      cogl_framebuffer_draw_textured_rectangle (framebuffer,
+                                                pipeline,
+                                                texture_area.x, texture_area.y,
+                                                texture_area.x + texture_area.width,
+                                                texture_area.y + texture_area.height,
+                                                0, 0, 1.0, 1.0);
+    case G_DESKTOP_BACKGROUND_STYLE_NONE:
+      break;
+    default:
+      g_return_if_reached();
     }
-
-  pixels[0] = color->red;
-  pixels[1] = color->green;
-  pixels[2] = color->blue;
-  pixels[3] = 0xFF;
-  pixels[4] = second_color->red;
-  pixels[5] = second_color->green;
-  pixels[6] = second_color->blue;
-  pixels[7] = 0xFF;
-
-  texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx, width, height,
-                                                         COGL_PIXEL_FORMAT_RGB_888,
-                                                         4,
-                                                         pixels,
-                                                         NULL));
-  set_texture (self, COGL_TEXTURE (texture));
 }
 
-/**
- * meta_background_load_color:
- * @self: the #MetaBackground
- * @color: a #ClutterColor to solid fill background with
- *
- * Clears any previously set background, and sets the
- * background to a solid color
- *
- * If @color is %NULL the stage color will be used.
- */
-void
-meta_background_load_color (MetaBackground *self,
-                            ClutterColor   *color)
+static void
+ensure_color_texture (MetaBackground *self)
 {
-  ClutterBackend *backend = clutter_get_default_backend ();
-  CoglContext *ctx = clutter_backend_get_cogl_context (backend);
   MetaBackgroundPrivate *priv = self->priv;
-  CoglTexture  *texture;
-  ClutterActor *stage = meta_get_stage_for_screen (priv->screen);
-  ClutterColor  stage_color;
-  uint8_t pixels[4];
 
-  ensure_pipeline (self);
-
-  unset_texture (self);
-  set_style (self, G_DESKTOP_BACKGROUND_STYLE_NONE);
-
-  if (color == NULL)
+  if (priv->color_texture == NULL)
     {
-      clutter_actor_get_background_color (stage, &stage_color);
-      color = &stage_color;
-    }
+      ClutterBackend *backend = clutter_get_default_backend ();
+      CoglContext *ctx = clutter_backend_get_cogl_context (backend);
+      uint8_t pixels[8];
+      int width, height;
 
-  pixels[0] = color->red;
-  pixels[1] = color->green;
-  pixels[2] = color->blue;
-  pixels[3] = 0xFF;
-
-  texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx, 1, 1,
-                                                         COGL_PIXEL_FORMAT_RGB_888,
-                                                         4,
-                                                         pixels,
-                                                         NULL));
-  set_texture (self, COGL_TEXTURE (texture));
-}
-
-typedef struct
-{
-  GDesktopBackgroundStyle style;
-  char *filename;
-} LoadFileTaskData;
+      if (priv->shading_direction == G_DESKTOP_BACKGROUND_SHADING_SOLID)
+        {
+          width = 1;
+          height = 1;
 
-static LoadFileTaskData *
-load_file_task_data_new (const char              *filename,
-                         GDesktopBackgroundStyle  style)
-{
-  LoadFileTaskData *task_data;
+          pixels[0] = priv->color.red;
+          pixels[1] = priv->color.green;
+          pixels[2] = priv->color.blue;
+          pixels[3] = 0xFF;
+        }
+      else
+        {
+          switch (priv->shading_direction)
+            {
+            case G_DESKTOP_BACKGROUND_SHADING_VERTICAL:
+              width = 1;
+              height = 2;
+              break;
+            case G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL:
+              width = 2;
+              height = 1;
+              break;
+            default:
+              g_return_if_reached ();
+            }
 
-  task_data = g_slice_new (LoadFileTaskData);
-  task_data->style = style;
-  task_data->filename = g_strdup (filename);
+          pixels[0] = priv->color.red;
+          pixels[1] = priv->color.green;
+          pixels[2] = priv->color.blue;
+          pixels[3] = 0xFF;
+          pixels[4] = priv->second_color.red;
+          pixels[5] = priv->second_color.green;
+          pixels[6] = priv->second_color.blue;
+          pixels[7] = 0xFF;
+        }
 
-  return task_data;
+      priv->color_texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx, width, height,
+                                                                         COGL_PIXEL_FORMAT_RGB_888,
+                                                                         4,
+                                                                         pixels,
+                                                                         NULL));
+    }
 }
 
-static void
-load_file_task_data_free (LoadFileTaskData *task_data)
-{
-  g_free (task_data->filename);
-  g_slice_free (LoadFileTaskData, task_data);
-}
+typedef enum {
+  PIPELINE_REPLACE,
+  PIPELINE_ADD,
+  PIPELINE_OVER_REVERSE,
+} PipelineType;
 
-static void
-load_file (GTask            *task,
-           MetaBackground   *self,
-           LoadFileTaskData *task_data,
-           GCancellable     *cancellable)
+static CoglPipeline *
+create_pipeline (PipelineType type)
 {
-  GError *error = NULL;
-  GdkPixbuf *pixbuf;
-
-  pixbuf = gdk_pixbuf_new_from_file (task_data->filename,
-                                     &error);
+  const char * const blend_strings[3] = {
+    [PIPELINE_REPLACE] = "RGBA = ADD (SRC_COLOR, 0)",
+    [PIPELINE_ADD] = "RGBA = ADD (SRC_COLOR, DST_COLOR)",
+    [PIPELINE_OVER_REVERSE] = "RGBA = ADD (SRC_COLOR * (1 - DST_COLOR[A]), DST_COLOR)",
+  };
+  static CoglPipeline *templates[3];
 
-  if (pixbuf == NULL)
+  if (templates[type] == NULL)
     {
-      g_task_return_error (task, error);
-      return;
+      templates[type] = meta_create_texture_pipeline (NULL);
+      cogl_pipeline_set_blend (templates[type], blend_strings[type], NULL);
     }
 
-  g_task_return_pointer (task, pixbuf, (GDestroyNotify) g_object_unref);
+  return cogl_pipeline_copy (templates[type]);
 }
 
-/**
- * meta_background_load_file_async:
- * @self: the #MetaBackground
- * @filename: the image file to load
- * @style: a #GDesktopBackgroundStyle to specify how background is laid out
- * @cancellable: a #GCancellable
- * @callback: call back to call when file is loaded or failed to load
- * @user_data: user data for callback
- *
- * Loads the specified image and uses it as the background source.
- */
-void
-meta_background_load_file_async (MetaBackground          *self,
-                                 const char              *filename,
-                                 GDesktopBackgroundStyle  style,
-                                 GCancellable            *cancellable,
-                                 GAsyncReadyCallback      callback,
-                                 gpointer                 user_data)
+static gboolean
+texture_has_alpha (CoglTexture *texture)
 {
-    LoadFileTaskData *task_data;
-    GTask *task;
-
-    task = g_task_new (self, cancellable, callback, user_data);
-
-    task_data = load_file_task_data_new (filename, style);
-    g_task_set_task_data (task, task_data, (GDestroyNotify) load_file_task_data_free);
+  if (!texture)
+    return FALSE;
 
-    g_task_run_in_thread (task, (GTaskThreadFunc) load_file);
-    g_object_unref (task);
+  switch (cogl_texture_get_components (texture))
+    {
+    case COGL_TEXTURE_COMPONENTS_A:
+    case COGL_TEXTURE_COMPONENTS_RGBA:
+      return TRUE;
+    case COGL_TEXTURE_COMPONENTS_RG:
+    case COGL_TEXTURE_COMPONENTS_RGB:
+    case COGL_TEXTURE_COMPONENTS_DEPTH:
+      return FALSE;
+    default:
+      g_assert_not_reached ();
+    }
 }
 
-/**
- * meta_background_load_file_finish:
- * @self: the #MetaBackground
- * @result: the result from the #GAsyncReadyCallback passed
- *          to meta_background_load_file_async()
- * @error: a #GError
- *
- * The finish function for meta_background_load_file_async().
- *
- * Returns: whether or not the image was loaded
- */
-gboolean
-meta_background_load_file_finish (MetaBackground  *self,
-                                  GAsyncResult    *result,
-                                  GError         **error)
+static void
+ensure_wallpaper_texture (MetaBackground *self,
+                          CoglTexture    *texture)
 {
-  ClutterBackend *backend = clutter_get_default_backend ();
-  CoglContext *ctx = clutter_backend_get_cogl_context (backend);
-  GTask *task;
-  LoadFileTaskData *task_data;
-  CoglTexture *texture;
-  GdkPixbuf *pixbuf;
-  int width, height, row_stride;
-  guchar *pixels;
-  gboolean has_alpha;
-  gboolean loaded = FALSE;
-  CoglPixelFormat pixel_format;
+  MetaBackgroundPrivate *priv = self->priv;
 
-  g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+  if (priv->wallpaper_texture == NULL)
+    {
+      int width = cogl_texture_get_width (texture);
+      int height = cogl_texture_get_height (texture);
+      CoglFramebuffer *fbo;
+      CoglError *catch_error = NULL;
+      CoglPipeline *pipeline;
 
-  task = G_TASK (result);
+      priv->wallpaper_texture = meta_create_large_texture (width, height,
+                                                           COGL_TEXTURE_COMPONENTS_RGBA);
+      fbo = cogl_offscreen_new_with_texture (priv->wallpaper_texture);
 
-  pixbuf = g_task_propagate_pointer (task, error);
+      if (!cogl_framebuffer_allocate (fbo, &catch_error))
+        {
+          cogl_error_free (catch_error);
+          return;
+        }
 
-  if (pixbuf == NULL)
-    goto out;
+      cogl_framebuffer_orthographic (fbo, 0, 0,
+                                     width, height, -1., 1.);
 
-  task_data = g_task_get_task_data (task);
+      pipeline = create_pipeline (PIPELINE_REPLACE);
+      cogl_pipeline_set_layer_texture (pipeline, 0, texture);
+      cogl_framebuffer_draw_textured_rectangle (fbo, pipeline, 0, 0, width, height,
+                                                0., 0., 1., 1.);
+      cogl_object_unref (pipeline);
 
-  width = gdk_pixbuf_get_width (pixbuf);
-  height = gdk_pixbuf_get_height (pixbuf);
-  row_stride = gdk_pixbuf_get_rowstride (pixbuf);
-  pixels = gdk_pixbuf_get_pixels (pixbuf);
-  has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
+      if (texture_has_alpha (texture))
+        {
+          ensure_color_texture (self);
 
-  pixel_format = has_alpha ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888;
+          pipeline = create_pipeline (PIPELINE_OVER_REVERSE);
+          cogl_pipeline_set_layer_texture (pipeline, 0, priv->color_texture);
+          cogl_framebuffer_draw_rectangle (fbo, pipeline, 0, 0, width, height);
+          cogl_object_unref (pipeline);
+        }
 
-  texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx, width, height,
-                                                         pixel_format,
-                                                         row_stride,
-                                                         pixels,
-                                                         NULL));
+      cogl_object_unref (fbo);
+    }
+}
 
-  if (texture == NULL)
+static CoglPipelineWrapMode
+get_wrap_mode (GDesktopBackgroundStyle style)
+{
+  switch (style)
     {
-      g_set_error_literal (error,
-                           COGL_BITMAP_ERROR,
-                           COGL_BITMAP_ERROR_FAILED,
-                           _("background texture could not be created from file"));
-      goto out;
+      case G_DESKTOP_BACKGROUND_STYLE_WALLPAPER:
+          return COGL_PIPELINE_WRAP_MODE_REPEAT;
+      case G_DESKTOP_BACKGROUND_STYLE_NONE:
+      case G_DESKTOP_BACKGROUND_STYLE_STRETCHED:
+      case G_DESKTOP_BACKGROUND_STYLE_CENTERED:
+      case G_DESKTOP_BACKGROUND_STYLE_SCALED:
+      case G_DESKTOP_BACKGROUND_STYLE_ZOOM:
+      case G_DESKTOP_BACKGROUND_STYLE_SPANNED:
+      default:
+          return COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE;
     }
+}
 
-  ensure_pipeline (self);
-  unset_texture (self);
-  set_style (self, task_data->style);
-  set_filename (self, task_data->filename);
-  set_texture (self, texture);
+CoglTexture *
+meta_background_get_texture (MetaBackground         *self,
+                             int                     monitor_index,
+                             cairo_rectangle_int_t  *texture_area,
+                             CoglPipelineWrapMode   *wrap_mode)
+{
+  MetaBackgroundPrivate *priv;
+  MetaBackgroundMonitor *monitor;
+  MetaRectangle geometry;
+  cairo_rectangle_int_t monitor_area;
+  CoglTexture *texture1, *texture2;
 
-  clutter_content_invalidate (CLUTTER_CONTENT (self));
-  loaded = TRUE;
+  g_return_if_fail (META_IS_BACKGROUND (self));
+  priv = self->priv;
+  g_return_if_fail (monitor_index <= 0 && monitor_index < priv->n_monitors);
 
-out:
-  if (pixbuf != NULL)
-    g_object_unref (pixbuf);
-  return loaded;
-}
+  monitor = &priv->monitors[monitor_index];
 
-/**
- * meta_background_copy:
- * @self: a #MetaBackground to copy
- * @monitor: a monitor
- * @effects: effects to use on copy of @self
- *
- * Creates a new #MetaBackground to draw the background for the given monitor.
- * Background will be loaded from @self and will share state
- * with @self, but may have different effects applied to it.
- *
- * Return value: (transfer full): the newly created background content
- */
-MetaBackground *
-meta_background_copy (MetaBackground        *self,
-                      int                    monitor,
-                      MetaBackgroundEffects  effects)
-{
-  MetaBackground *background;
+  meta_screen_get_monitor_geometry (priv->screen, monitor_index, &geometry);
+  monitor_area.x = geometry.x;
+  monitor_area.y = geometry.y;
+  monitor_area.width = geometry.width;
+  monitor_area.height = geometry.height;
 
-  background = META_BACKGROUND (g_object_new (META_TYPE_BACKGROUND,
-                                              "meta-screen", self->priv->screen,
-                                              "monitor", monitor,
-                                              NULL));
+  texture1 = priv->background_image1 ? meta_background_image_get_texture (priv->background_image1) : NULL;
+  texture2 = priv->background_image2 ? meta_background_image_get_texture (priv->background_image2) : NULL;
 
-  background->priv->brightness = self->priv->brightness;
+  if (texture1 == NULL && texture2 == NULL)
+    {
+      ensure_color_texture (self);
+      if (texture_area)
+        *texture_area = monitor_area;
+      if (wrap_mode)
+        *wrap_mode = COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE;
+      return priv->color_texture;
+    }
 
-  background->priv->shading_direction = self->priv->shading_direction;
-  background->priv->color = self->priv->color;
-  background->priv->second_color = self->priv->second_color;
-  background->priv->filename = g_strdup (self->priv->filename);
+  if (texture2 == NULL && priv->style == G_DESKTOP_BACKGROUND_STYLE_WALLPAPER &&
+      priv->shading_direction == G_DESKTOP_BACKGROUND_SHADING_SOLID)
+    {
+      ensure_wallpaper_texture (self, texture1);
+      if (texture_area)
+        get_texture_area (self, &monitor_area, priv->wallpaper_texture,
+                          texture_area);
+      if (wrap_mode)
+        *wrap_mode = COGL_PIPELINE_WRAP_MODE_REPEAT;
+      return priv->wallpaper_texture;
+    }
 
-  /* we can reuse the pipeline if it has no effects applied, or
-   * if it has the same effects applied
-   */
-  if (effects == self->priv->effects ||
-      self->priv->effects == META_BACKGROUND_EFFECTS_NONE)
+  if (monitor->dirty)
     {
-      ensure_pipeline (self);
-      background->priv->pipeline = cogl_pipeline_copy (self->priv->pipeline);
-      background->priv->texture = cogl_object_ref (self->priv->texture);
-      background->priv->style = self->priv->style;
+      CoglError *catch_error = NULL;
 
-      if (effects != self->priv->effects)
+      if (monitor->texture == NULL)
         {
-          set_effects (background, effects);
+          monitor->texture = meta_create_large_texture (monitor_area.width, monitor_area.height,
+                                                        COGL_TEXTURE_COMPONENTS_RGBA);
+          monitor->fbo = cogl_offscreen_new_with_texture (monitor->texture);
+        }
 
-          if (effects & META_BACKGROUND_EFFECTS_VIGNETTE)
-            {
-              set_brightness (background, self->priv->brightness);
-              set_vignette_sharpness (background, self->priv->vignette_sharpness);
-            }
+      if (!cogl_framebuffer_allocate (monitor->fbo, &catch_error))
+        {
+          cogl_error_free (catch_error);
+          return NULL;
+        }
+
+      cogl_framebuffer_orthographic (monitor->fbo, 0, 0,
+                                     monitor_area.width, monitor_area.height, -1., 1.);
+
+      if (texture2 != NULL && priv->blend_factor != 0.0)
+        {
+          CoglPipeline *pipeline = create_pipeline (PIPELINE_REPLACE);
+          cogl_pipeline_set_color4f (pipeline,
+                                      priv->blend_factor, priv->blend_factor, priv->blend_factor, 
priv->blend_factor);
+          cogl_pipeline_set_layer_texture (pipeline, 0, texture2);
+          cogl_pipeline_set_layer_wrap_mode (pipeline, 0, get_wrap_mode (priv->style));
+
+          draw_texture (self,
+                        monitor->fbo, pipeline,
+                        texture2, &monitor_area);
+
+          cogl_object_unref (pipeline);
         }
       else
         {
-          background->priv->effects = self->priv->effects;
+          cogl_framebuffer_clear4f (monitor->fbo,
+                                    COGL_BUFFER_BIT_COLOR,
+                                    0.0, 0.0, 0.0, 0.0);
         }
 
-    }
-  else
-    {
-      ensure_pipeline (background);
-      if (self->priv->texture != NULL)
-        set_texture (background, cogl_object_ref (self->priv->texture));
-      set_style (background, self->priv->style);
-      set_effects (background, effects);
+      if (texture1 != NULL &&
+          !(texture2 != NULL && priv->blend_factor == 1.0 && !texture_has_alpha (texture2)))
+        {
+          CoglPipeline *pipeline = create_pipeline (PIPELINE_ADD);
+          cogl_pipeline_set_color4f (pipeline,
+                                     (1 - priv->blend_factor),
+                                     (1 - priv->blend_factor),
+                                     (1 - priv->blend_factor),
+                                     (1 - priv->blend_factor));;
+          cogl_pipeline_set_layer_texture (pipeline, 0, texture1);
+          cogl_pipeline_set_layer_wrap_mode (pipeline, 0, get_wrap_mode (priv->style));
+
+          draw_texture (self,
+                        monitor->fbo, pipeline,
+                        texture1, &monitor_area);
+
+          cogl_object_unref (pipeline);
+        }
 
-      if (effects & META_BACKGROUND_EFFECTS_VIGNETTE)
+      if (!((texture2 != NULL && priv->blend_factor == 1.0 && !texture_has_alpha (texture2)) ||
+            (texture1 != NULL && !texture_has_alpha (texture1))))
         {
-          set_brightness (background, self->priv->brightness);
-          set_vignette_sharpness (background, self->priv->vignette_sharpness);
+          CoglPipeline *pipeline = create_pipeline (PIPELINE_OVER_REVERSE);
+
+          ensure_color_texture (self);
+          cogl_pipeline_set_layer_texture (pipeline, 0, priv->color_texture);
+          cogl_framebuffer_draw_rectangle (monitor->fbo,
+                                           pipeline,
+                                           0, 0,
+                                           monitor_area.width, monitor_area.height);
+          cogl_object_unref (pipeline);
         }
-    }
 
-  clutter_content_invalidate (CLUTTER_CONTENT (background));
+      monitor->dirty = FALSE;
+    }
 
-  return background;
+  if (texture_area)
+    *texture_area = monitor_area;
+  if (wrap_mode)
+    *wrap_mode = COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE;
+  return monitor->texture;
 }
-/**
- * meta_background_new:
- * @screen: the #MetaScreen
- * @monitor: a monitor in @screen
- * @effects: which effect flags to enable
- *
- * Creates a new #MetaBackground to draw the background for the given monitor.
- * The returned object should be set on a #MetaBackgroundActor with
- * clutter_actor_set_content().
- *
- * The background may be given a vignette by setting @effects
- *
- * Return value: the newly created background content
- */
+
 MetaBackground *
-meta_background_new (MetaScreen            *screen,
-                     int                    monitor,
-                     MetaBackgroundEffects  effects)
+meta_background_new  (MetaScreen *screen)
 {
-  MetaBackground *background;
-
-  background = META_BACKGROUND (g_object_new (META_TYPE_BACKGROUND,
-                                              "meta-screen", screen,
-                                              "monitor", monitor,
-                                              "effects", effects,
-                                              NULL));
-  return background;
+  return g_object_new (META_TYPE_BACKGROUND,
+                       "screen", screen,
+                       NULL);
 }
 
-/**
- * meta_background_get_style:
- * @self: a #MetaBackground
- *
- * Returns the current background style.
- *
- * Return value: a #GDesktopBackgroundStyle
- */
-GDesktopBackgroundStyle
-meta_background_get_style (MetaBackground *self)
+void
+meta_background_set_color (MetaBackground *self,
+                           ClutterColor   *color)
 {
-    return self->priv->style;
-}
+  ClutterColor dummy = { 0 };
 
-/**
- * meta_background_get_shading:
- * @self: a #MetaBackground
- *
- * Returns whether @self is a solid color,
- * vertical gradient, horizontal gradient,
- * or none of the above.
- *
- * Return value: a #GDesktopBackgroundShading
- */
-GDesktopBackgroundShading
-meta_background_get_shading (MetaBackground *self)
-{
-    return self->priv->shading_direction;
+  g_return_if_fail (META_IS_BACKGROUND (self));
+  g_return_if_fail (color != NULL);
+
+  meta_background_set_gradient (self,
+                                G_DESKTOP_BACKGROUND_SHADING_SOLID,
+                                color, &dummy);
 }
 
-/**
- * meta_background_get_color:
- * @self: a #MetaBackground
- *
- * Returns the first color of @self. If self
- * is a gradient, the second color can be returned
- * with meta_background_get_second_color().
- *
- * Return value: (transfer none): a #ClutterColor
- */
-const ClutterColor *
-meta_background_get_color (MetaBackground *self)
+void
+meta_background_set_gradient (MetaBackground            *self,
+                              GDesktopBackgroundShading  shading_direction,
+                              ClutterColor              *color,
+                              ClutterColor              *second_color)
 {
-    return &self->priv->color;
+  MetaBackgroundPrivate *priv;
+
+  g_return_if_fail (META_IS_BACKGROUND (self));
+  g_return_if_fail (color != NULL);
+  g_return_if_fail (second_color != NULL);
+
+  priv = self->priv;
+
+  priv->shading_direction = shading_direction;
+  priv->color = *color;
+  priv->second_color = *second_color;
+
+  free_color_texture (self);
+  free_wallpaper_texture (self);
+  mark_changed (self);
 }
 
-/**
- * meta_background_get_second_color:
- * @self: a #MetaBackground
- *
- * Returns the second color of @self. If @self
- * is not a gradient this function is undefined.
- *
- * Return value: (transfer none): a #ClutterColor
- */
-const ClutterColor *
-meta_background_get_second_color (MetaBackground *self)
+void
+meta_background_set_filename (MetaBackground            *self,
+                              const char                *filename,
+                              GDesktopBackgroundStyle    style)
 {
-    return &self->priv->second_color;
+  g_return_if_fail (META_IS_BACKGROUND (self));
+
+  meta_background_set_blend (self, filename, NULL, 0.0, style);
 }
 
-/**
- * meta_background_get_filename:
- * @self: a #MetaBackground
- *
- * Returns the filename of the currently loaded file.
- * IF @self is not loaded from a file this function is
- * undefined.
- *
- * Return value: (transfer none): the filename
- */
-const char *
-meta_background_get_filename (MetaBackground *self)
+void
+meta_background_set_blend (MetaBackground          *self,
+                           const char              *filename1,
+                           const char              *filename2,
+                           double                   blend_factor,
+                           GDesktopBackgroundStyle  style)
 {
-    return self->priv->filename;
+  MetaBackgroundPrivate *priv;
+
+  g_return_if_fail (META_IS_BACKGROUND (self));
+  g_return_if_fail (blend_factor >= 0.0 && blend_factor <= 1.0);
+
+  priv = self->priv;
+
+  set_filename (self, &priv->filename1, &priv->background_image1, filename1);
+  set_filename (self, &priv->filename2, &priv->background_image2, filename2);
+
+  priv->blend_factor = blend_factor;
+  priv->style = style;
+
+  free_wallpaper_texture (self);
+  mark_changed (self);
 }
diff --git a/src/compositor/plugins/default.c b/src/compositor/plugins/default.c
index 7c2fccf..38a66e7 100644
--- a/src/compositor/plugins/default.c
+++ b/src/compositor/plugins/default.c
@@ -303,15 +303,16 @@ on_monitors_changed (MetaScreen *screen,
   for (i = 0; i < n; i++)
     {
       MetaRectangle rect;
-      ClutterActor *background;
+      ClutterActor *background_actor;
+      MetaBackground *background;
       ClutterColor color;
 
       meta_screen_get_monitor_geometry (screen, i, &rect);
 
-      background = meta_background_actor_new ();
+      background_actor = meta_background_actor_new (screen, i);
 
-      clutter_actor_set_position (background, rect.x, rect.y);
-      clutter_actor_set_size (background, rect.width, rect.height);
+      clutter_actor_set_position (background_actor, rect.x, rect.y);
+      clutter_actor_set_size (background_actor, rect.width, rect.height);
 
       /* Don't use rand() here, mesa calls srand() internally when
          parsing the driconf XML, but it's nice if the colors are
@@ -322,9 +323,13 @@ on_monitors_changed (MetaScreen *screen,
                           g_rand_int_range (rand, 0, 255),
                           g_rand_int_range (rand, 0, 255),
                           255);
-      clutter_actor_set_background_color (background, &color);
 
-      clutter_actor_add_child (self->priv->background_group, background);
+      background = meta_background_new (screen);
+      meta_background_set_color (background, &color);
+      meta_background_actor_set_background (META_BACKGROUND_ACTOR (background_actor), background);
+      g_object_unref (background);
+
+      clutter_actor_add_child (self->priv->background_group, background_actor);
     }
 
   g_rand_free (rand);
diff --git a/src/meta/meta-background-actor.h b/src/meta/meta-background-actor.h
index 56e9768..08dfc21 100644
--- a/src/meta/meta-background-actor.h
+++ b/src/meta/meta-background-actor.h
@@ -22,10 +22,8 @@
 #define META_BACKGROUND_ACTOR_H
 
 #include <clutter/clutter.h>
-#include <cogl/cogl.h>
-
-#include <meta/gradient.h>
 #include <meta/screen.h>
+#include <meta/meta-background.h>
 
 #include <gsettings-desktop-schemas/gdesktop-enums.h>
 
@@ -63,6 +61,15 @@ struct _MetaBackgroundActor
 
 GType meta_background_actor_get_type (void);
 
-ClutterActor *meta_background_actor_new (void);
+ClutterActor *meta_background_actor_new    (MetaScreen *screen,
+                                            int         monitor);
+
+void meta_background_actor_set_background  (MetaBackgroundActor *self,
+                                            MetaBackground      *background);
+
+void meta_background_actor_add_vignette    (MetaBackgroundActor *self,
+                                            double               brightness,
+                                            double               sharpness);
+void meta_background_actor_remove_vignette (MetaBackgroundActor *self);
 
 #endif /* META_BACKGROUND_ACTOR_H */
diff --git a/src/meta/meta-background-image.h b/src/meta/meta-background-image.h
new file mode 100644
index 0000000..f0af93e
--- /dev/null
+++ b/src/meta/meta-background-image.h
@@ -0,0 +1,76 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * MetaBackgroundImageCache:
+ *
+ * Simple cache for background textures loaded from files
+ *
+ * Copyright 2014 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __META_BACKGROUND_IMAGE_H__
+#define __META_BACKGROUND_IMAGE_H__
+
+#include <glib-object.h>
+#include <cogl/cogl.h>
+
+#define META_TYPE_BACKGROUND_IMAGE            (meta_background_image_get_type ())
+#define META_BACKGROUND_IMAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
META_TYPE_BACKGROUND_IMAGE, MetaBackgroundImage))
+#define META_BACKGROUND_IMAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  
META_TYPE_BACKGROUND_IMAGE, MetaBackgroundImageClass))
+#define META_IS_BACKGROUND_IMAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
META_TYPE_BACKGROUND_IMAGE))
+#define META_IS_BACKGROUND_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  
META_TYPE_BACKGROUND_IMAGE))
+#define META_BACKGROUND_IMAGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  
META_TYPE_BACKGROUND_IMAGE, MetaBackgroundImageClass))
+
+/**
+ * MetaBackgroundImage:
+ *
+ * #MetaBackgroundImage is an object that represents a loaded or loading background image.
+ */
+typedef struct _MetaBackgroundImage      MetaBackgroundImage;
+typedef struct _MetaBackgroundImageClass MetaBackgroundImageClass;
+
+GType meta_background_image_get_type (void);
+
+gboolean     meta_background_image_is_loaded   (MetaBackgroundImage *image);
+gboolean     meta_background_image_get_success (MetaBackgroundImage *image);
+CoglTexture *meta_background_image_get_texture (MetaBackgroundImage *image);
+
+#define META_TYPE_BACKGROUND_IMAGE_CACHE            (meta_background_image_cache_get_type ())
+#define META_BACKGROUND_IMAGE_CACHE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
META_TYPE_BACKGROUND_IMAGE_CACHE, MetaBackgroundImageCache))
+#define META_BACKGROUND_IMAGE_CACHE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  
META_TYPE_BACKGROUND_IMAGE_CACHE, MetaBackgroundImageCacheClass))
+#define META_IS_BACKGROUND_IMAGE_CACHE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
META_TYPE_BACKGROUND_IMAGE_CACHE))
+#define META_IS_BACKGROUND_IMAGE_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  
META_TYPE_BACKGROUND_IMAGE_CACHE))
+#define META_BACKGROUND_IMAGE_CACHE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  
META_TYPE_BACKGROUND_IMAGE_CACHE, MetaBackgroundImageCacheClass))
+
+/**
+ * MetaBackgroundImageCache:
+ *
+ * #MetaBackgroundImageCache caches loading of textures for backgrounds; there's actually
+ * nothing background specific about it, other than it is tuned to work well for
+ * large images as typically are used for backgrounds.
+ */
+typedef struct _MetaBackgroundImageCache      MetaBackgroundImageCache;
+typedef struct _MetaBackgroundImageCacheClass MetaBackgroundImageCacheClass;
+
+MetaBackgroundImageCache *meta_background_image_cache_get_default (void);
+
+GType meta_background_image_cache_get_type (void);
+
+MetaBackgroundImage *meta_background_image_cache_load  (MetaBackgroundImageCache *cache,
+                                                        const char               *filename);
+void                 meta_background_image_cache_purge (MetaBackgroundImageCache *cache,
+                                                        const char               *filename);
+
+#endif /* __META_BACKGROUND_IMAGE_H__ */
diff --git a/src/meta/meta-background.h b/src/meta/meta-background.h
index a861600..38706dc 100644
--- a/src/meta/meta-background.h
+++ b/src/meta/meta-background.h
@@ -1,8 +1,8 @@
 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
 /*
- * meta-background.h: CoglTexture for paintnig the system background
+ * meta-background-actor.h:  for painting the root window background
  *
- * Copyright 2013 Red Hat, Inc.
+ * Copyright 2010 Red Hat, Inc.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -21,20 +21,16 @@
 #ifndef META_BACKGROUND_H
 #define META_BACKGROUND_H
 
-#include <cogl/cogl.h>
 #include <clutter/clutter.h>
-
-#include <meta/gradient.h>
-#include <meta/screen.h>
-
 #include <gsettings-desktop-schemas/gdesktop-enums.h>
+#include <meta/screen.h>
 
 /**
  * MetaBackground:
  *
- * This class handles loading a background from file, screenshot, or
- * color scheme. The resulting object can be associated with one or
- * more #MetaBackgroundActor objects to handle loading the background.
+ * This class handles tracking and painting the root window background.
+ * By integrating with #MetaWindowGroup we can avoid painting parts of
+ * the background that are obscured by other windows.
  */
 
 #define META_TYPE_BACKGROUND            (meta_background_get_type ())
@@ -48,20 +44,6 @@ typedef struct _MetaBackground        MetaBackground;
 typedef struct _MetaBackgroundClass   MetaBackgroundClass;
 typedef struct _MetaBackgroundPrivate MetaBackgroundPrivate;
 
-/**
- * MetaBackgroundEffects:
- * @META_BACKGROUND_EFFECTS_NONE: No effect
- * @META_BACKGROUND_EFFECTS_VIGNETTE: Vignette
- *
- * Which effects to enable on the background
- */
-
-typedef enum
-{
-  META_BACKGROUND_EFFECTS_NONE       = 0,
-  META_BACKGROUND_EFFECTS_VIGNETTE   = 1 << 1,
-} MetaBackgroundEffects;
-
 struct _MetaBackgroundClass
 {
   /*< private >*/
@@ -70,7 +52,6 @@ struct _MetaBackgroundClass
 
 struct _MetaBackground
 {
-  /*< private >*/
   GObject parent;
 
   MetaBackgroundPrivate *priv;
@@ -78,33 +59,21 @@ struct _MetaBackground
 
 GType meta_background_get_type (void);
 
-MetaBackground *meta_background_new (MetaScreen           *screen,
-                                     int                   monitor,
-                                    MetaBackgroundEffects effects);
-MetaBackground *meta_background_copy (MetaBackground        *self,
-                                      int                    monitor,
-                                     MetaBackgroundEffects  effects);
-
-void meta_background_load_gradient (MetaBackground            *self,
-                                    GDesktopBackgroundShading  shading_direction,
-                                    ClutterColor              *color,
-                                    ClutterColor              *second_color);
-void meta_background_load_color (MetaBackground *self,
-                                 ClutterColor   *color);
-void meta_background_load_file_async (MetaBackground          *self,
-                                      const char              *filename,
-                                      GDesktopBackgroundStyle  style,
-                                      GCancellable            *cancellable,
-                                      GAsyncReadyCallback      callback,
-                                      gpointer                 user_data);
-gboolean meta_background_load_file_finish (MetaBackground       *self,
-                                           GAsyncResult         *result,
-                                           GError              **error);
-
-const char *meta_background_get_filename (MetaBackground *self);
-GDesktopBackgroundStyle meta_background_get_style (MetaBackground *self);
-GDesktopBackgroundShading meta_background_get_shading (MetaBackground *self);
-const ClutterColor *meta_background_get_color (MetaBackground *self);
-const ClutterColor *meta_background_get_second_color (MetaBackground *self);
+MetaBackground *meta_background_new  (MetaScreen *screen);
+
+void meta_background_set_color    (MetaBackground            *self,
+                                   ClutterColor              *color);
+void meta_background_set_gradient (MetaBackground            *self,
+                                   GDesktopBackgroundShading  shading_direction,
+                                   ClutterColor              *color,
+                                   ClutterColor              *second_color);
+void meta_background_set_filename (MetaBackground            *self,
+                                   const char                *filename,
+                                   GDesktopBackgroundStyle    style);
+void meta_background_set_blend    (MetaBackground            *self,
+                                   const char                *filename1,
+                                   const char                *filename2,
+                                   double                     blend_factor,
+                                   GDesktopBackgroundStyle    style);
 
 #endif /* META_BACKGROUND_H */


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