diff --git a/src/compositor/meta-background-content-private.h b/src/compositor/meta-background-content-private.h
new file mode 100644
index 000000000..284cddb3a
--- /dev/null
+++ b/src/compositor/meta-background-content-private.h
@@ -0,0 +1,16 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#ifndef META_BACKGROUND_CONTENT_PRIVATE_H
+#define META_BACKGROUND_CONTENT_PRIVATE_H
+
+#include "meta/meta-background-content.h"
+
+cairo_region_t *meta_background_content_get_clip_region (MetaBackgroundContent *self);
+
+void meta_background_content_cull_out (MetaBackgroundContent *self,
+ cairo_region_t *unobscured_region,
+ cairo_region_t *clip_region);
+
+void meta_background_content_reset_culling (MetaBackgroundContent *self);
+
+#endif /* META_BACKGROUND_CONTENT_PRIVATE_H */
diff --git a/src/compositor/meta-background-content.c b/src/compositor/meta-background-content.c
new file mode 100644
index 000000000..37555616f
--- /dev/null
+++ b/src/compositor/meta-background-content.c
@@ -0,0 +1,935 @@
+/*
+ * Copyright 2009 Sander Dijkhuis
+ * Copyright 2014 Red Hat, Inc.
+ * Copyright 2020 Endless Foundation.
+ *
+ * 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 .
+ *
+ * Portions adapted from gnome-shell/src/shell-global.c
+ */
+
+
+/**
+ * SECTION:meta-background-content
+ * @title: MetaBackgroundContent
+ * @short_description: ClutterContent for painting the root window background
+ *
+ */
+
+/*
+ * The overall model drawing model of this content 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 "config.h"
+
+#include "compositor/meta-background-content-private.h"
+
+#include "clutter/clutter.h"
+#include "compositor/clutter-utils.h"
+#include "compositor/cogl-utils.h"
+#include "compositor/meta-background-private.h"
+#include "compositor/meta-cullable.h"
+#include "meta/display.h"
+
+typedef enum
+{
+ CHANGED_BACKGROUND = 1 << 0,
+ CHANGED_EFFECTS = 1 << 2,
+ CHANGED_VIGNETTE_PARAMETERS = 1 << 3,
+ CHANGED_GRADIENT_PARAMETERS = 1 << 4,
+ CHANGED_ALL = 0xFFFF
+} ChangedFlags;
+
+#define GRADIENT_VERTEX_SHADER_DECLARATIONS \
+"uniform vec2 scale;\n" \
+"varying vec2 position;\n" \
+
+#define GRADIENT_VERTEX_SHADER_CODE \
+"position = cogl_tex_coord0_in.xy * scale;\n" \
+
+#define GRADIENT_FRAGMENT_SHADER_DECLARATIONS \
+"uniform float gradient_height_perc;\n" \
+"uniform float gradient_max_darkness;\n" \
+"varying vec2 position;\n" \
+
+#define GRADIENT_FRAGMENT_SHADER_CODE \
+"float min_brightness = 1.0 - gradient_max_darkness;\n" \
+"float gradient_y_pos = min(position.y, gradient_height_perc) / gradient_height_perc;\n" \
+"float pixel_brightness = (1.0 - min_brightness) * gradient_y_pos + min_brightness;\n" \
+"cogl_color_out.rgb = cogl_color_out.rgb * pixel_brightness;\n" \
+
+#define VIGNETTE_VERTEX_SHADER_DECLARATIONS \
+"uniform vec2 scale;\n" \
+"uniform vec2 offset;\n" \
+"varying vec2 position;\n" \
+
+#define VIGNETTE_VERTEX_SHADER_CODE \
+"position = cogl_tex_coord0_in.xy * scale + offset;\n" \
+
+#define VIGNETTE_SQRT_2 "1.4142"
+
+#define VIGNETTE_FRAGMENT_SHADER_DECLARATIONS \
+"uniform float vignette_sharpness;\n" \
+"varying vec2 position;\n" \
+"float rand(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453123); }\n" \
+
+#define VIGNETTE_FRAGMENT_SHADER_CODE \
+"float t = " VIGNETTE_SQRT_2 " * length(position);\n" \
+"t = min(t, 1.0);\n" \
+"float pixel_brightness = 1.0 - t * vignette_sharpness;\n" \
+"cogl_color_out.rgb = cogl_color_out.rgb * pixel_brightness;\n" \
+"cogl_color_out.rgb += (rand(position) - 0.5) / 255.0;\n" \
+
+typedef struct _MetaBackgroundLayer MetaBackgroundLayer;
+
+typedef enum
+{
+ PIPELINE_VIGNETTE = (1 << 0),
+ PIPELINE_BLEND = (1 << 1),
+ PIPELINE_GRADIENT = (1 << 2),
+} PipelineFlags;
+
+struct _MetaBackgroundContent
+{
+ GObject parent;
+
+ MetaDisplay *display;
+ int monitor;
+
+ MetaBackground *background;
+
+ gboolean gradient;
+ double gradient_max_darkness;
+ int gradient_height;
+
+ gboolean vignette;
+ double vignette_brightness;
+ double vignette_sharpness;
+
+ ChangedFlags changed;
+ CoglPipeline *pipeline;
+ PipelineFlags pipeline_flags;
+ cairo_rectangle_int_t texture_area;
+ gboolean force_bilinear;
+
+ cairo_region_t *clip_region;
+ cairo_region_t *unobscured_region;
+};
+
+static void clutter_content_iface_init (ClutterContentInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (MetaBackgroundContent,
+ meta_background_content,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
+ clutter_content_iface_init));
+
+enum
+{
+ PROP_0,
+ PROP_META_DISPLAY,
+ PROP_MONITOR,
+ PROP_BACKGROUND,
+ PROP_GRADIENT,
+ PROP_GRADIENT_HEIGHT,
+ PROP_GRADIENT_MAX_DARKNESS,
+ PROP_VIGNETTE,
+ PROP_VIGNETTE_SHARPNESS,
+ PROP_VIGNETTE_BRIGHTNESS,
+ N_PROPS,
+};
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+static void
+set_clip_region (MetaBackgroundContent *self,
+ cairo_region_t *clip_region)
+{
+ g_clear_pointer (&self->clip_region, cairo_region_destroy);
+ if (clip_region)
+ {
+ if (cairo_region_is_empty (clip_region))
+ self->clip_region = cairo_region_reference (clip_region);
+ else
+ self->clip_region = cairo_region_copy (clip_region);
+ }
+}
+
+static void
+set_unobscured_region (MetaBackgroundContent *self,
+ cairo_region_t *unobscured_region)
+{
+ g_clear_pointer (&self->unobscured_region, cairo_region_destroy);
+ if (unobscured_region)
+ {
+ if (cairo_region_is_empty (unobscured_region))
+ self->unobscured_region = cairo_region_reference (unobscured_region);
+ else
+ self->unobscured_region = cairo_region_copy (unobscured_region);
+ }
+}
+
+static void
+invalidate_pipeline (MetaBackgroundContent *self,
+ ChangedFlags changed)
+{
+ self->changed |= changed;
+}
+
+static void
+on_background_changed (MetaBackground *background,
+ MetaBackgroundContent *self)
+{
+ invalidate_pipeline (self, CHANGED_BACKGROUND);
+ clutter_content_invalidate (CLUTTER_CONTENT (self));
+}
+
+static CoglPipeline *
+make_pipeline (PipelineFlags pipeline_flags)
+{
+ static CoglPipeline *templates[8];
+ 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 *vignette_vertex_snippet;
+ static CoglSnippet *vignette_fragment_snippet;
+
+ if (!vignette_vertex_snippet)
+ vignette_vertex_snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_VERTEX,
+ VIGNETTE_VERTEX_SHADER_DECLARATIONS,
+ VIGNETTE_VERTEX_SHADER_CODE);
+
+ cogl_pipeline_add_snippet (*templatep, vignette_vertex_snippet);
+
+ if (!vignette_fragment_snippet)
+ vignette_fragment_snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
+ VIGNETTE_FRAGMENT_SHADER_DECLARATIONS,
+ VIGNETTE_FRAGMENT_SHADER_CODE);
+
+ cogl_pipeline_add_snippet (*templatep, vignette_fragment_snippet);
+ }
+
+ if ((pipeline_flags & PIPELINE_GRADIENT) != 0)
+ {
+ static CoglSnippet *gradient_vertex_snippet;
+ static CoglSnippet *gradient_fragment_snippet;
+
+ if (!gradient_vertex_snippet)
+ gradient_vertex_snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_VERTEX,
+ GRADIENT_VERTEX_SHADER_DECLARATIONS,
+ GRADIENT_VERTEX_SHADER_CODE);
+
+ cogl_pipeline_add_snippet (*templatep, gradient_vertex_snippet);
+
+ if (!gradient_fragment_snippet)
+ gradient_fragment_snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
+ GRADIENT_FRAGMENT_SHADER_DECLARATIONS,
+ GRADIENT_FRAGMENT_SHADER_CODE);
+
+ cogl_pipeline_add_snippet (*templatep, gradient_fragment_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 (MetaBackgroundContent *self,
+ ClutterActor *actor,
+ ClutterPaintContext *paint_context,
+ cairo_rectangle_int_t *actor_pixel_rect)
+{
+ PipelineFlags pipeline_flags = 0;
+ guint8 opacity;
+ float color_component;
+ CoglFramebuffer *fb;
+ CoglPipelineFilter filter;
+
+ opacity = clutter_actor_get_paint_opacity (actor);
+ if (opacity < 255)
+ pipeline_flags |= PIPELINE_BLEND;
+ if (self->vignette && clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
+ pipeline_flags |= PIPELINE_VIGNETTE;
+ if (self->gradient && clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
+ pipeline_flags |= PIPELINE_GRADIENT;
+
+ if (pipeline_flags != self->pipeline_flags)
+ g_clear_pointer (&self->pipeline, cogl_object_unref);
+
+ if (self->pipeline == NULL)
+ {
+ self->pipeline_flags = pipeline_flags;
+ self->pipeline = make_pipeline (pipeline_flags);
+ self->changed = CHANGED_ALL;
+ }
+
+ if (self->changed & CHANGED_BACKGROUND)
+ {
+ CoglPipelineWrapMode wrap_mode;
+ CoglTexture *texture = meta_background_get_texture (self->background,
+ self->monitor,
+ &self->texture_area,
+ &wrap_mode);
+ self->force_bilinear = texture &&
+ (self->texture_area.width != (int)cogl_texture_get_width (texture) ||
+ self->texture_area.height != (int)cogl_texture_get_height (texture));
+
+ cogl_pipeline_set_layer_texture (self->pipeline, 0, texture);
+ cogl_pipeline_set_layer_wrap_mode (self->pipeline, 0, wrap_mode);
+
+ self->changed &= ~CHANGED_BACKGROUND;
+ }
+
+ if (self->changed & CHANGED_VIGNETTE_PARAMETERS)
+ {
+ cogl_pipeline_set_uniform_1f (self->pipeline,
+ cogl_pipeline_get_uniform_location (self->pipeline,
+ "vignette_sharpness"),
+ self->vignette_sharpness);
+
+ self->changed &= ~CHANGED_VIGNETTE_PARAMETERS;
+ }
+
+ if (self->changed & CHANGED_GRADIENT_PARAMETERS)
+ {
+ MetaRectangle monitor_geometry;
+ float gradient_height_perc;
+
+ meta_display_get_monitor_geometry (self->display,
+ self->monitor, &monitor_geometry);
+ gradient_height_perc = MAX (0.0001, self->gradient_height / (float)monitor_geometry.height);
+ cogl_pipeline_set_uniform_1f (self->pipeline,
+ cogl_pipeline_get_uniform_location (self->pipeline,
+ "gradient_height_perc"),
+ gradient_height_perc);
+ cogl_pipeline_set_uniform_1f (self->pipeline,
+ cogl_pipeline_get_uniform_location (self->pipeline,
+ "gradient_max_darkness"),
+ self->gradient_max_darkness);
+
+ self->changed &= ~CHANGED_GRADIENT_PARAMETERS;
+ }
+
+ if (self->vignette)
+ {
+ color_component = self->vignette_brightness * opacity / 255.;
+
+ if (!clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
+ {
+ /* Darken everything to match the average brightness that would
+ * be there if we were drawing the vignette, which is
+ * (1 - (pi/12.) * vignette_sharpness) [exercise for the reader :]
+ */
+ color_component *= (1 - 0.74 * self->vignette_sharpness);
+ }
+ }
+ else
+ {
+ color_component = opacity / 255.;
+ }
+
+ cogl_pipeline_set_color4f (self->pipeline,
+ color_component,
+ color_component,
+ color_component,
+ opacity / 255.);
+
+ fb = clutter_paint_context_get_framebuffer (paint_context);
+ if (!self->force_bilinear &&
+ meta_actor_painting_untransformed (fb,
+ actor_pixel_rect->width,
+ actor_pixel_rect->height,
+ 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 (self->pipeline, 0, filter, filter);
+}
+
+static void
+set_glsl_parameters (MetaBackgroundContent *self,
+ cairo_rectangle_int_t *actor_pixel_rect)
+{
+ 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] = self->texture_area.width / (float)actor_pixel_rect->width;
+ scale[1] = self->texture_area.height / (float)actor_pixel_rect->height;
+ offset[0] = self->texture_area.x / (float)actor_pixel_rect->width - 0.5;
+ offset[1] = self->texture_area.y / (float)actor_pixel_rect->height - 0.5;
+
+ cogl_pipeline_set_uniform_float (self->pipeline,
+ cogl_pipeline_get_uniform_location (self->pipeline,
+ "scale"),
+ 2, 1, scale);
+
+ cogl_pipeline_set_uniform_float (self->pipeline,
+ cogl_pipeline_get_uniform_location (self->pipeline,
+ "offset"),
+ 2, 1, offset);
+}
+
+static void
+paint_clipped_rectangle (ClutterPaintNode *node,
+ CoglPipeline *pipeline,
+ cairo_rectangle_int_t *rect,
+ cairo_rectangle_int_t *texture_area)
+{
+ g_autoptr (ClutterPaintNode) pipeline_node = NULL;
+ float x1, y1, x2, y2;
+ float tx1, ty1, tx2, ty2;
+
+ x1 = rect->x;
+ y1 = rect->y;
+ x2 = rect->x + rect->width;
+ y2 = rect->y + rect->height;
+
+ tx1 = (x1 - texture_area->x) / texture_area->width;
+ ty1 = (y1 - texture_area->y) / texture_area->height;
+ tx2 = (x2 - texture_area->x) / texture_area->width;
+ ty2 = (y2 - texture_area->y) / texture_area->height;
+
+ pipeline_node = clutter_pipeline_node_new (pipeline);
+ clutter_paint_node_set_name (pipeline_node, "MetaBackgroundContent (Slice)");
+ clutter_paint_node_add_texture_rectangle (pipeline_node,
+ &(ClutterActorBox) {
+ .x1 = x1,
+ .y1 = y1,
+ .x2 = x2,
+ .y2 = y2,
+ },
+ tx1, ty1,
+ tx2, ty2);
+
+ clutter_paint_node_add_child (node, pipeline_node);
+}
+
+static void
+meta_background_content_paint_content (ClutterContent *content,
+ ClutterActor *actor,
+ ClutterPaintNode *node,
+ ClutterPaintContext *paint_context)
+{
+ MetaBackgroundContent *self = META_BACKGROUND_CONTENT (content);
+ ClutterActorBox actor_box;
+ cairo_rectangle_int_t actor_pixel_rect;
+ cairo_region_t *region;
+ int i, n_rects;
+
+ if ((self->clip_region && cairo_region_is_empty (self->clip_region)))
+ return;
+
+ clutter_actor_get_content_box (actor, &actor_box);
+ actor_pixel_rect.x = actor_box.x1;
+ actor_pixel_rect.y = actor_box.x1;
+ actor_pixel_rect.width = actor_box.x2 - actor_box.x1;
+ actor_pixel_rect.height = actor_box.y2 - actor_box.y1;
+
+ setup_pipeline (self, actor, paint_context, &actor_pixel_rect);
+ set_glsl_parameters (self, &actor_pixel_rect);
+
+ /* Limit to how many separate rectangles we'll draw; beyond this just
+ * fall back and draw the whole thing */
+#define MAX_RECTS 64
+
+ /* Now figure out what to actually paint */
+ if (self->clip_region)
+ {
+ region = cairo_region_copy (self->clip_region);
+ cairo_region_intersect_rectangle (region, &actor_pixel_rect);
+ }
+ else
+ {
+ region = cairo_region_create_rectangle (&actor_pixel_rect);
+ }
+
+ if (self->unobscured_region)
+ cairo_region_intersect (region, self->unobscured_region);
+
+ if (cairo_region_is_empty (region))
+ {
+ cairo_region_destroy (region);
+ return;
+ }
+
+ n_rects = cairo_region_num_rectangles (region);
+ if (n_rects <= MAX_RECTS)
+ {
+ for (i = 0; i < n_rects; i++)
+ {
+ cairo_rectangle_int_t rect;
+ cairo_region_get_rectangle (region, i, &rect);
+ paint_clipped_rectangle (node, self->pipeline, &rect,
+ &self->texture_area);
+ }
+ }
+ else
+ {
+ cairo_rectangle_int_t rect;
+ cairo_region_get_extents (region, &rect);
+ paint_clipped_rectangle (node, self->pipeline, &rect,
+ &self->texture_area);
+ }
+
+ cairo_region_destroy (region);
+}
+
+static gboolean
+meta_background_content_get_preferred_size (ClutterContent *content,
+ float *width,
+ float *height)
+
+{
+ MetaBackgroundContent *background_content = META_BACKGROUND_CONTENT (content);
+ MetaRectangle monitor_geometry;
+
+ meta_display_get_monitor_geometry (background_content->display,
+ background_content->monitor,
+ &monitor_geometry);
+
+ if (width)
+ *width = monitor_geometry.width;
+
+ if (height)
+ *height = monitor_geometry.height;
+
+ return TRUE;
+}
+
+static void
+clutter_content_iface_init (ClutterContentInterface *iface)
+{
+ iface->paint_content = meta_background_content_paint_content;
+ iface->get_preferred_size = meta_background_content_get_preferred_size;
+}
+
+static void
+set_monitor (MetaBackgroundContent *self,
+ int monitor)
+{
+ MetaRectangle old_monitor_geometry;
+ MetaRectangle new_monitor_geometry;
+ MetaDisplay *display = self->display;
+
+ if(self->monitor == monitor)
+ return;
+
+ meta_display_get_monitor_geometry (display, self->monitor, &old_monitor_geometry);
+ meta_display_get_monitor_geometry (display, monitor, &new_monitor_geometry);
+ if(old_monitor_geometry.height != new_monitor_geometry.height)
+ invalidate_pipeline (self, CHANGED_GRADIENT_PARAMETERS);
+
+ self->monitor = monitor;
+}
+
+static void
+meta_background_content_dispose (GObject *object)
+{
+ MetaBackgroundContent *self = META_BACKGROUND_CONTENT (object);
+
+ set_clip_region (self, NULL);
+ set_unobscured_region (self, NULL);
+ meta_background_content_set_background (self, NULL);
+
+ g_clear_pointer (&self->pipeline, cogl_object_unref);
+
+ G_OBJECT_CLASS (meta_background_content_parent_class)->dispose (object);
+}
+
+static void
+meta_background_content_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MetaBackgroundContent *self = META_BACKGROUND_CONTENT (object);
+
+ switch (prop_id)
+ {
+ case PROP_META_DISPLAY:
+ self->display = g_value_get_object (value);
+ break;
+ case PROP_MONITOR:
+ set_monitor (self, g_value_get_int (value));
+ break;
+ case PROP_BACKGROUND:
+ meta_background_content_set_background (self, g_value_get_object (value));
+ break;
+ case PROP_GRADIENT:
+ meta_background_content_set_gradient (self,
+ g_value_get_boolean (value),
+ self->gradient_height,
+ self->gradient_max_darkness);
+ break;
+ case PROP_GRADIENT_HEIGHT:
+ meta_background_content_set_gradient (self,
+ self->gradient,
+ g_value_get_int (value),
+ self->gradient_max_darkness);
+ break;
+ case PROP_GRADIENT_MAX_DARKNESS:
+ meta_background_content_set_gradient (self,
+ self->gradient,
+ self->gradient_height,
+ g_value_get_double (value));
+ break;
+ case PROP_VIGNETTE:
+ meta_background_content_set_vignette (self,
+ g_value_get_boolean (value),
+ self->vignette_brightness,
+ self->vignette_sharpness);
+ break;
+ case PROP_VIGNETTE_SHARPNESS:
+ meta_background_content_set_vignette (self,
+ self->vignette,
+ self->vignette_brightness,
+ g_value_get_double (value));
+ break;
+ case PROP_VIGNETTE_BRIGHTNESS:
+ meta_background_content_set_vignette (self,
+ self->vignette,
+ g_value_get_double (value),
+ self->vignette_sharpness);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+meta_background_content_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MetaBackgroundContent *self = META_BACKGROUND_CONTENT (object);
+
+ switch (prop_id)
+ {
+ case PROP_META_DISPLAY:
+ g_value_set_object (value, self->display);
+ break;
+ case PROP_MONITOR:
+ g_value_set_int (value, self->monitor);
+ break;
+ case PROP_BACKGROUND:
+ g_value_set_object (value, self->background);
+ break;
+ case PROP_GRADIENT:
+ g_value_set_boolean (value, self->gradient);
+ break;
+ case PROP_GRADIENT_HEIGHT:
+ g_value_set_int (value, self->gradient_height);
+ break;
+ case PROP_GRADIENT_MAX_DARKNESS:
+ g_value_set_double (value, self->gradient_max_darkness);
+ break;
+ case PROP_VIGNETTE:
+ g_value_set_boolean (value, self->vignette);
+ break;
+ case PROP_VIGNETTE_BRIGHTNESS:
+ g_value_set_double (value, self->vignette_brightness);
+ break;
+ case PROP_VIGNETTE_SHARPNESS:
+ g_value_set_double (value, self->vignette_sharpness);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+meta_background_content_class_init (MetaBackgroundContentClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = meta_background_content_dispose;
+ object_class->set_property = meta_background_content_set_property;
+ object_class->get_property = meta_background_content_get_property;
+
+ properties[PROP_META_DISPLAY] =
+ g_param_spec_object ("meta-display",
+ "MetaDisplay",
+ "MetaDisplay",
+ META_TYPE_DISPLAY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ properties[PROP_MONITOR] =
+ g_param_spec_int ("monitor",
+ "monitor",
+ "monitor",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ properties[PROP_BACKGROUND] =
+ g_param_spec_object ("background",
+ "Background",
+ "MetaBackground object holding background parameters",
+ META_TYPE_BACKGROUND,
+ G_PARAM_READWRITE);
+
+ properties[PROP_GRADIENT] =
+ g_param_spec_boolean ("gradient",
+ "Gradient",
+ "Whether gradient effect is enabled",
+ FALSE,
+ G_PARAM_READWRITE);
+
+ properties[PROP_GRADIENT_HEIGHT] =
+ g_param_spec_int ("gradient-height",
+ "Gradient Height",
+ "Height of gradient effect",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE);
+
+ properties[PROP_GRADIENT_MAX_DARKNESS] =
+ g_param_spec_double ("gradient-max-darkness",
+ "Gradient Max Darkness",
+ "How dark is the gradient initially",
+ 0.0, 1.0, 0.0,
+ G_PARAM_READWRITE);
+
+ properties[PROP_VIGNETTE] =
+ g_param_spec_boolean ("vignette",
+ "Vignette",
+ "Whether vignette effect is enabled",
+ FALSE,
+ G_PARAM_READWRITE);
+
+ properties[PROP_VIGNETTE_BRIGHTNESS] =
+ g_param_spec_double ("brightness",
+ "Vignette Brightness",
+ "Brightness of vignette effect",
+ 0.0, 1.0, 1.0,
+ G_PARAM_READWRITE);
+
+ properties[PROP_VIGNETTE_SHARPNESS] =
+ g_param_spec_double ("vignette-sharpness",
+ "Vignette Sharpness",
+ "Sharpness of vignette effect",
+ 0.0, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+meta_background_content_init (MetaBackgroundContent *self)
+{
+ self->gradient = FALSE;
+ self->gradient_height = 0;
+ self->gradient_max_darkness = 0.0;
+
+ self->vignette = FALSE;
+ self->vignette_brightness = 1.0;
+ self->vignette_sharpness = 0.0;
+}
+
+/**
+ * meta_background_content_new:
+ * @monitor: Index of the monitor for which to draw the background
+ *
+ * Creates a new actor to draw the background for the given monitor.
+ *
+ * Return value: the newly created background actor
+ */
+ClutterContent *
+meta_background_content_new (MetaDisplay *display,
+ int monitor)
+{
+ return g_object_new (META_TYPE_BACKGROUND_CONTENT,
+ "meta-display", display,
+ "monitor", monitor,
+ NULL);
+}
+
+void
+meta_background_content_set_background (MetaBackgroundContent *self,
+ MetaBackground *background)
+{
+ g_return_if_fail (META_IS_BACKGROUND_CONTENT (self));
+ g_return_if_fail (background == NULL || META_IS_BACKGROUND (background));
+
+ if (background == self->background)
+ return;
+
+ if (self->background)
+ {
+ g_signal_handlers_disconnect_by_func (self->background,
+ (gpointer)on_background_changed,
+ self);
+ g_clear_object (&self->background);
+ }
+
+ if (background)
+ {
+ self->background = g_object_ref (background);
+ g_signal_connect (self->background, "changed",
+ G_CALLBACK (on_background_changed), self);
+ }
+
+ invalidate_pipeline (self, CHANGED_BACKGROUND);
+ clutter_content_invalidate (CLUTTER_CONTENT (self));
+}
+
+void
+meta_background_content_set_gradient (MetaBackgroundContent *self,
+ gboolean enabled,
+ int height,
+ double max_darkness)
+{
+ gboolean changed = FALSE;
+
+ g_return_if_fail (META_IS_BACKGROUND_CONTENT (self));
+ g_return_if_fail (height >= 0);
+ g_return_if_fail (max_darkness >= 0. && max_darkness <= 1.);
+
+ enabled = enabled != FALSE && height != 0;
+
+ if (enabled != self->gradient)
+ {
+ self->gradient = enabled;
+ invalidate_pipeline (self, CHANGED_EFFECTS);
+ changed = TRUE;
+ }
+
+ if (height != self->gradient_height || max_darkness != self->gradient_max_darkness)
+ {
+ self->gradient_height = height;
+ self->gradient_max_darkness = max_darkness;
+ invalidate_pipeline (self, CHANGED_GRADIENT_PARAMETERS);
+ changed = TRUE;
+ }
+
+ if (changed)
+ clutter_content_invalidate (CLUTTER_CONTENT (self));
+}
+
+void
+meta_background_content_set_vignette (MetaBackgroundContent *self,
+ gboolean enabled,
+ double brightness,
+ double sharpness)
+{
+ gboolean changed = FALSE;
+
+ g_return_if_fail (META_IS_BACKGROUND_CONTENT (self));
+ g_return_if_fail (brightness >= 0. && brightness <= 1.);
+ g_return_if_fail (sharpness >= 0.);
+
+ enabled = enabled != FALSE;
+
+ if (enabled != self->vignette)
+ {
+ self->vignette = enabled;
+ invalidate_pipeline (self, CHANGED_EFFECTS);
+ changed = TRUE;
+ }
+
+ if (brightness != self->vignette_brightness || sharpness != self->vignette_sharpness)
+ {
+ self->vignette_brightness = brightness;
+ self->vignette_sharpness = sharpness;
+ invalidate_pipeline (self, CHANGED_VIGNETTE_PARAMETERS);
+ changed = TRUE;
+ }
+
+ if (changed)
+ clutter_content_invalidate (CLUTTER_CONTENT (self));
+}
+
+cairo_region_t *
+meta_background_content_get_clip_region (MetaBackgroundContent *self)
+{
+ return self->clip_region;
+}
+
+void
+meta_background_content_cull_out (MetaBackgroundContent *self,
+ cairo_region_t *unobscured_region,
+ cairo_region_t *clip_region)
+{
+ set_unobscured_region (self, unobscured_region);
+ set_clip_region (self, clip_region);
+}
+
+void
+meta_background_content_reset_culling (MetaBackgroundContent *self)
+{
+ set_unobscured_region (self, NULL);
+ set_clip_region (self, NULL);
+}
diff --git a/src/meson.build b/src/meson.build
index ba8b63cdf..433f811d2 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -289,6 +289,8 @@ mutter_sources = [
'compositor/compositor-private.h',
'compositor/meta-background-actor.c',
'compositor/meta-background-actor-private.h',
+ 'compositor/meta-background-content.c',
+ 'compositor/meta-background-content-private.h',
'compositor/meta-background.c',
'compositor/meta-background-group.c',
'compositor/meta-background-image.c',
diff --git a/src/meta/meson.build b/src/meta/meson.build
index 279a6d9b1..67a914a76 100644
--- a/src/meta/meson.build
+++ b/src/meta/meson.build
@@ -11,6 +11,7 @@ mutter_public_headers = [
'meta-backend.h',
'meta-background.h',
'meta-background-actor.h',
+ 'meta-background-content.h',
'meta-background-group.h',
'meta-background-image.h',
'meta-close-dialog.h',
diff --git a/src/meta/meta-background-content.h b/src/meta/meta-background-content.h
new file mode 100644
index 000000000..48a44dfba
--- /dev/null
+++ b/src/meta/meta-background-content.h
@@ -0,0 +1,66 @@
+/*
+ * meta-background-content.h: ClutterContent for painting the wallpaper
+ *
+ * Copyright 2010 Red Hat, Inc.
+ * Copyright 2020 Endless Foundation
+ *
+ * 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 .
+ */
+
+#ifndef META_BACKGROUND_CONTENT_H
+#define META_BACKGROUND_CONTENT_H
+
+#include
+
+#include "clutter/clutter.h"
+#include "meta/meta-background.h"
+
+/**
+ * MetaBackgroundContent:
+ *
+ * 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_CONTENT (meta_background_content_get_type ())
+
+META_EXPORT
+G_DECLARE_FINAL_TYPE (MetaBackgroundContent,
+ meta_background_content,
+ META, BACKGROUND_CONTENT,
+ GObject)
+
+
+META_EXPORT
+ClutterContent *meta_background_content_new (MetaDisplay *display,
+ int monitor);
+
+META_EXPORT
+void meta_background_content_set_background (MetaBackgroundContent *self,
+ MetaBackground *background);
+
+META_EXPORT
+void meta_background_content_set_gradient (MetaBackgroundContent *self,
+ gboolean enabled,
+ int height,
+ double tone_start);
+
+META_EXPORT
+void meta_background_content_set_vignette (MetaBackgroundContent *self,
+ gboolean enabled,
+ double brightness,
+ double sharpness);
+
+#endif /* META_BACKGROUND_CONTENT_H */