From a4a688ed83983fb5b9d30f612b939c992b7f9749 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Sun, 10 Aug 2014 17:26:14 +0200 Subject: [PATCH] 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 | 8 +- src/compositor/cogl-utils.c | 75 + src/compositor/cogl-utils.h | 10 + src/compositor/meta-background-actor.c | 555 ++++++- src/compositor/meta-background-image.c | 344 ++++ src/compositor/meta-background-private.h | 15 + src/compositor/meta-background.c | 1878 +++++++++------------- 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 | 73 +- 11 files changed, 1860 insertions(+), 1206 deletions(-) create mode 100644 src/compositor/meta-background-image.c create mode 100644 src/compositor/meta-background-private.h create mode 100644 src/meta/meta-background-image.h diff --git a/src/Makefile.am b/src/Makefile.am index 1e336089e..75b694b64 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-actor.h \ - meta/meta-background-group.h \ meta/meta-background.h \ + meta/meta-background-actor.h \ + meta/meta-background-image.h \ + meta/meta-background-group.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 68c2127c0..82bd60eec 100644 --- a/src/compositor/cogl-utils.c +++ b/src/compositor/cogl-utils.c @@ -64,3 +64,78 @@ meta_create_texture_pipeline (CoglTexture *src_texture) return pipeline; } + +static gboolean is_pot(int x) +{ + return x > 0 && (x & (x - 1)) == 0; +} + +/** + * meta_create_texture: + * @width: width of the texture to create + * @height: height of the texture to create + * @components; components to store in the texture (color or alpha) + * @flags: flags that affect the allocation behavior + * + * Creates a texture of the given size with the specified components + * for use as a frame buffer object. + * + * If non-power-of-two textures are not supported on the system, then + * the texture will be created as a texture rectangle; in this case, + * hardware repeating isn't possible, and texture coordinates are also + * different, but Cogl hides these issues from the application, except from + * GLSL shaders. Since GLSL is never (or at least almost never) + * present on such a system, this is not typically an issue. + * + * If %META_TEXTURE_ALLOW_SLICING is present in @flags, and the texture + * is larger than the texture size limits of the system, then the texture + * will be created as a sliced texture. This also will cause problems + * with using the texture with GLSL, and is more likely to be an issue + * since all GL implementations have texture size limits, and they can + * be as small as 2048x2048 on reasonably current systems. + */ +CoglTexture * +meta_create_texture (int width, + int height, + CoglTextureComponents components, + MetaTextureFlags flags) +{ + 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; + else + g_error ("Cannot create texture. Support for GL_ARB_texture_non_power_of_two or " + "ARB_texture_rectangle is required"); + } + + 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); + + if ((flags & META_TEXTURE_ALLOW_SLICING) != 0) + { + /* To find out if we need to slice the texture, we have to go ahead and force storage + * to be allocated + */ + CoglError *catch_error = NULL; + if (!cogl_texture_allocate (texture, &catch_error)) + { + cogl_error_free (catch_error); + cogl_object_unref (texture); + texture = COGL_TEXTURE (cogl_texture_2d_sliced_new_with_size (ctx, width, height, COGL_TEXTURE_MAX_WASTE)); + cogl_texture_set_components (texture, components); + } + } + + return texture; +} diff --git a/src/compositor/cogl-utils.h b/src/compositor/cogl-utils.h index 281f9b4c2..32113f8a4 100644 --- a/src/compositor/cogl-utils.h +++ b/src/compositor/cogl-utils.h @@ -25,4 +25,14 @@ CoglPipeline * meta_create_texture_pipeline (CoglTexture *texture); +typedef enum { + META_TEXTURE_FLAGS_NONE = 0, + META_TEXTURE_ALLOW_SLICING = 1 << 1 +} MetaTextureFlags; + +CoglTexture *meta_create_texture (int width, + int height, + CoglTextureComponents components, + MetaTextureFlags flags); + #endif /* __META_COGL_UTILS_H__ */ diff --git a/src/compositor/meta-background-actor.c b/src/compositor/meta-background-actor.c index b7282875a..ad82f201e 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 +/* + * 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 +#include #include -#include - #include "cogl-utils.h" -#include "compositor-private.h" +#include "clutter-utils.h" #include -#include #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,320 @@ 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 *vertex_snippet; + static CoglSnippet *fragment_snippet; + + if (!vertex_snippet) + vertex_snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_VERTEX, + VERTEX_SHADER_DECLARATIONS, VERTEX_SHADER_CODE); + + cogl_pipeline_add_snippet (*templatep, vertex_snippet); + + if (!fragment_snippet) + fragment_snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT, + FRAGMENT_SHADER_DECLARATIONS, FRAGMENT_SHADER_CODE); + + cogl_pipeline_add_snippet (*templatep, 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 (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 +paint_clipped_rectangle (CoglFramebuffer *fb, + CoglPipeline *pipeline, + cairo_rectangle_int_t *rect, + cairo_rectangle_int_t *texture_area) +{ + 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; + + cogl_framebuffer_draw_textured_rectangle (fb, pipeline, + x1, y1, x2, y2, + tx1, ty1, tx2, ty2); +} + +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; + CoglFramebuffer *fb; + 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; + + setup_pipeline (self, &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 + + fb = cogl_get_draw_framebuffer (); + + /* Now figure out what to actually paint. + */ + if (priv->clip_region != NULL) + { + int n_rects = cairo_region_num_rectangles (priv->clip_region); + if (n_rects <= MAX_RECTS) + { + for (i = 0; i < n_rects; i++) + { + cairo_rectangle_int_t rect; + cairo_region_get_rectangle (priv->clip_region, i, &rect); + + if (!gdk_rectangle_intersect (&actor_pixel_rect, &rect, &rect)) + continue; + + paint_clipped_rectangle (fb, priv->pipeline, &rect, &priv->texture_area); + } + + return; + } + } + + paint_clipped_rectangle (fb, priv->pipeline, &actor_pixel_rect, &priv->texture_area); +} + +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 +551,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 +608,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_set_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_unset_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 000000000..dff315404 --- /dev/null +++ b/src/compositor/meta-background-image.c @@ -0,0 +1,344 @@ +/* -*- 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 . + */ + +/** + * SECTION:meta-background-image + * @title: MetaBackgroundImage + * @short_description: objects holding images loaded from files, used for backgrounds + */ + +#include + +#include +#include +#include +#include +#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_texture (width, height, + has_alpha ? COGL_TEXTURE_COMPONENTS_RGBA : COGL_TEXTURE_COMPONENTS_RGB, + META_TEXTURE_ALLOW_SLICING); + + 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_texture() 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 000000000..2799b7c10 --- /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 + +#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 0f6220fd2..bc1a0f978 100644 --- a/src/compositor/meta-background.c +++ b/src/compositor/meta-background.c @@ -17,277 +17,556 @@ * along with this program; if not, see . */ -/** - * SECTION:meta-background - * @title: MetaBackground - * @short_description: ClutterContent for painting the system background - * - */ - -#include - -#include - -#include "cogl-utils.h" -#include "compositor-private.h" -#include "mutter-enum-types.h" -#include #include -#include "util-private.h" -#include "meta-background-actor-private.h" +#include +#include "meta-background-private.h" +#include "cogl-utils.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" \ +enum +{ + CHANGED, + LAST_SIGNAL +}; -#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" +static guint signals[LAST_SIGNAL] = { 0 }; -/* 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 _MetaBackgroundMonitor +{ + gboolean dirty; + CoglTexture *texture; + CoglOffscreen *fbo; +}; struct _MetaBackgroundPrivate { - MetaScreen *screen; - CoglTexture *texture; - CoglPipeline *pipeline; - int monitor; - - MetaBackgroundEffects effects; + 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; + + guint wallpaper_allocation_failed : 1; }; 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 (MetaBackground, meta_background, G_TYPE_OBJECT) -G_DEFINE_TYPE_WITH_CODE (MetaBackground, meta_background, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT, - clutter_content_iface_init)) +static void +free_fbos (MetaBackground *self) +{ + MetaBackgroundPrivate *priv = self->priv; + + int i; + + for (i = 0; i < priv->n_monitors; i++) + { + MetaBackgroundMonitor *monitor = &priv->monitors[i]; + if (monitor->fbo) + { + cogl_object_unref (monitor->fbo); + monitor->fbo = NULL; + } + if (monitor->texture) + { + cogl_object_unref (monitor->texture); + monitor->texture = NULL; + } + } +} + +static void +free_color_texture (MetaBackground *self) +{ + MetaBackgroundPrivate *priv = self->priv; + + if (priv->color_texture != NULL) + { + cogl_object_unref (priv->color_texture); + priv->color_texture = NULL; + } +} + +static void +free_wallpaper_texture (MetaBackground *self) +{ + MetaBackgroundPrivate *priv = self->priv; + + if (priv->wallpaper_texture != NULL) + { + cogl_object_unref (priv->wallpaper_texture); + priv->wallpaper_texture = NULL; + } + + priv->wallpaper_allocation_failed = FALSE; +} + +static void +on_monitors_changed (MetaScreen *screen, + MetaBackground *self) +{ + MetaBackgroundPrivate *priv = self->priv; + + free_fbos (self); + g_free (priv->monitors); + priv->monitors = NULL; + priv->n_monitors = 0; + + if (priv->screen) + { + priv->n_monitors = meta_screen_get_n_monitors (screen); + priv->monitors = g_new0 (MetaBackgroundMonitor, priv->n_monitors); + } +} + +static void +set_screen (MetaBackground *self, + MetaScreen *screen) +{ + MetaBackgroundPrivate *priv = self->priv; + + if (priv->screen != NULL) + { + g_signal_handlers_disconnect_by_func (priv->screen, + (gpointer)on_monitors_changed, + self); + } + + priv->screen = screen; + + if (priv->screen != NULL) + { + g_signal_connect (priv->screen, "monitors-changed", + G_CALLBACK (on_monitors_changed), self); + } + + on_monitors_changed (priv->screen, self); +} + +static void +meta_background_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + 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_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MetaBackgroundPrivate *priv = META_BACKGROUND (object)->priv; + + 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 gboolean -meta_background_get_preferred_size (ClutterContent *content, - gfloat *width, - gfloat *height) +need_prerender (MetaBackground *self) { - MetaBackgroundPrivate *priv = META_BACKGROUND (content)->priv; - MetaRectangle monitor_geometry; + 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; - if (priv->texture == NULL) + if (texture1 == NULL && texture2 == 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; + if (texture2 == NULL && priv->style == G_DESKTOP_BACKGROUND_STYLE_WALLPAPER) + return FALSE; return TRUE; } 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) +mark_changed (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; + int i; - meta_screen_get_monitor_geometry (priv->screen, priv->monitor, &monitor_geometry); + if (!need_prerender (self)) + free_fbos (self); - 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; + for (i = 0; i < priv->n_monitors; i++) + priv->monitors[i].dirty = TRUE; - texture_width = cogl_texture_get_width (priv->texture); - actor_x_scale = (1.0 * actor_pixel_rect.width / monitor_geometry.width); + g_signal_emit (self, signals[CHANGED], 0); +} - texture_height = cogl_texture_get_height (priv->texture); - actor_y_scale = (1.0 * actor_pixel_rect.height / monitor_geometry.height); +static void +on_background_loaded (MetaBackgroundImage *image, + MetaBackground *self) +{ + mark_changed (self); +} - switch (priv->style) +static void +set_filename (MetaBackground *self, + char **filenamep, + MetaBackgroundImage **imagep, + const char *filename) +{ + if (g_strcmp0 (filename, *filenamep) != 0) { - 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); + g_free (*filenamep); + *filenamep = g_strdup (filename); - /* 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: + if (*imagep) { - /* 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); + g_signal_handlers_disconnect_by_func (*imagep, + (gpointer)on_background_loaded, + self); + g_object_unref (*imagep); + *imagep = NULL; + } - /* 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; + 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 CoglPipelineWrapMode -get_wrap_mode (MetaBackground *self) +static void +meta_background_dispose (GObject *object) +{ + MetaBackground *self = META_BACKGROUND (object); + MetaBackgroundPrivate *priv = self->priv; + + free_color_texture (self); + free_wallpaper_texture (self); + + set_filename (self, &priv->filename1, &priv->background_image1, NULL); + set_filename (self, &priv->filename2, &priv->background_image2, NULL); + + set_screen (self, NULL); + + G_OBJECT_CLASS (meta_background_parent_class)->dispose (object); +} + +static void +meta_background_finalize (GObject *object) +{ + 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; + + g_type_class_add_private (klass, sizeof (MetaBackgroundPrivate)); + + object_class->dispose = meta_background_dispose; + object_class->finalize = meta_background_finalize; + 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_ONLY); + + g_object_class_install_property (object_class, + PROP_META_SCREEN, + param_spec); + +} + +static void +meta_background_init (MetaBackground *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + META_TYPE_BACKGROUND, + MetaBackgroundPrivate); +} + +static void +get_texture_area (MetaBackground *self, + cairo_rectangle_int_t *monitor_rect, + CoglTexture *texture, + cairo_rectangle_int_t *texture_area) { MetaBackgroundPrivate *priv = self->priv; + cairo_rectangle_int_t image_area; + int screen_width, screen_height; + float texture_width, texture_height; + float monitor_x_scale, monitor_y_scale; + + texture_width = cogl_texture_get_width (texture); + texture_height = cogl_texture_get_height (texture); + 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; + 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; + + /* 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; + + /* 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; + } + + *texture_area = image_area; + break; + + 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); + + /* unclipped texture area is whole screen */ + image_area.width = screen_width; + image_area.height = screen_height; + + /* 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; + } } } +static void +draw_texture (MetaBackground *self, + CoglFramebuffer *framebuffer, + CoglPipeline *pipeline, + CoglTexture *texture, + cairo_rectangle_int_t *monitor_area) +{ + MetaBackgroundPrivate *priv = self->priv; + cairo_rectangle_int_t texture_area; + + get_texture_area (self, monitor_area, texture, &texture_area); + + switch (priv->style) + { + 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(); + } +} + +static void +ensure_color_texture (MetaBackground *self) +{ + MetaBackgroundPrivate *priv = self->priv; + + if (priv->color_texture == NULL) + { + ClutterBackend *backend = clutter_get_default_backend (); + CoglContext *ctx = clutter_backend_get_cogl_context (backend); + uint8_t pixels[8]; + int width, height; + + if (priv->shading_direction == G_DESKTOP_BACKGROUND_SHADING_SOLID) + { + width = 1; + height = 1; + + 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 (); + } + + 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; + } + + priv->color_texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx, width, height, + COGL_PIXEL_FORMAT_RGB_888, + 4, + pixels, + NULL)); + } +} + +typedef enum { + PIPELINE_REPLACE, + PIPELINE_ADD, + PIPELINE_OVER_REVERSE, +} PipelineType; + +static CoglPipeline * +create_pipeline (PipelineType type) +{ + 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 (templates[type] == NULL) + { + templates[type] = meta_create_texture_pipeline (NULL); + cogl_pipeline_set_blend (templates[type], blend_strings[type], NULL); + } + + return cogl_pipeline_copy (templates[type]); +} + static gboolean texture_has_alpha (CoglTexture *texture) { @@ -308,937 +587,302 @@ texture_has_alpha (CoglTexture *texture) } } -static ClutterPaintNode * -meta_background_paint_node_new (MetaBackground *self, - ClutterActor *actor) +static gboolean +ensure_wallpaper_texture (MetaBackground *self, + CoglTexture *texture) { 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; -} - -static void -clip_region_to_actor_box (cairo_region_t *region, - ClutterActorBox *actor_box) -{ - 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; - - cairo_region_intersect_rectangle (region, &clip_rect); -} - -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); -} - -static void -meta_background_paint_content (ClutterContent *content, - ClutterActor *actor, - ClutterPaintNode *root) -{ - 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->wallpaper_texture == NULL && !priv->wallpaper_allocation_failed) { - 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); - } - - if (cairo_region_is_empty (paintable_region)) - goto out; - - node = meta_background_paint_node_new (self, actor); - - /* 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 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); - } - clutter_paint_node_add_child (root, node); - clutter_paint_node_unref (node); - - out: - cairo_region_destroy (paintable_region); -} - -static void -clutter_content_iface_init (ClutterContentIface *iface) -{ - iface->get_preferred_size = meta_background_get_preferred_size; - iface->paint_content = meta_background_paint_content; -} - -static void -meta_background_dispose (GObject *object) -{ - MetaBackground *self = META_BACKGROUND (object); - MetaBackgroundPrivate *priv = self->priv; - - unset_texture (self); - - g_clear_pointer (&priv->pipeline, - (GDestroyNotify) - cogl_object_unref); - - G_OBJECT_CLASS (meta_background_parent_class)->dispose (object); -} - -static void -meta_background_finalize (GObject *object) -{ - MetaBackground *self = META_BACKGROUND (object); - MetaBackgroundPrivate *priv = self->priv; - - g_free (priv->filename); - - G_OBJECT_CLASS (meta_background_parent_class)->finalize (object); -} - -static void -ensure_pipeline (MetaBackground *self) -{ - if (self->priv->pipeline == NULL) - self->priv->pipeline = COGL_PIPELINE (meta_create_texture_pipeline (NULL)); -} - -static void -set_brightness (MetaBackground *self, - gfloat brightness) -{ - MetaBackgroundPrivate *priv = self->priv; - - if (priv->brightness == brightness) - return; - - priv->brightness = brightness; - - 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)); - - g_object_notify (G_OBJECT (self), "brightness"); -} - -static void -set_vignette_sharpness (MetaBackground *self, - gfloat sharpness) -{ - 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"); -} - -static void -add_vignette (MetaBackground *self) -{ - 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); - - cogl_pipeline_set_uniform_1f (priv->pipeline, - cogl_pipeline_get_uniform_location (priv->pipeline, - "brightness"), - priv->brightness); - - cogl_pipeline_set_uniform_1f (priv->pipeline, - cogl_pipeline_get_uniform_location (priv->pipeline, - "vignette_sharpness"), - priv->vignette_sharpness); -} - -static void -set_effects (MetaBackground *self, - MetaBackgroundEffects effects) -{ - MetaBackgroundPrivate *priv = self->priv; - - priv->effects = effects; - - if ((priv->effects & META_BACKGROUND_EFFECTS_VIGNETTE)) - add_vignette (self); - - clutter_content_invalidate (CLUTTER_CONTENT (self)); -} - -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; - } -} - -static void -meta_background_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - 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; - } -} - -static void -meta_background_class_init (MetaBackgroundClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GParamSpec *param_spec; - - g_type_class_add_private (klass, sizeof (MetaBackgroundPrivate)); - - object_class->dispose = meta_background_dispose; - object_class->finalize = meta_background_finalize; - object_class->set_property = meta_background_set_property; - object_class->get_property = meta_background_get_property; - - param_spec = g_param_spec_object ("meta-screen", - "MetaScreen", - "MetaScreen", - META_TYPE_SCREEN, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT); - - 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 -meta_background_init (MetaBackground *self) -{ - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, - META_TYPE_BACKGROUND, - MetaBackgroundPrivate); -} - -static void -unset_texture (MetaBackground *self) -{ - MetaBackgroundPrivate *priv = self->priv; - if (priv->pipeline != NULL) - cogl_pipeline_set_layer_texture (priv->pipeline, 0, NULL); - - g_clear_pointer (&priv->texture, - (GDestroyNotify) - cogl_object_unref); -} - -static void -set_texture (MetaBackground *self, - CoglTexture *texture) -{ - MetaBackgroundPrivate *priv = self->priv; - - priv->texture = texture; - cogl_pipeline_set_layer_texture (priv->pipeline, 0, priv->texture); -} - -static void -set_style (MetaBackground *self, - GDesktopBackgroundStyle style) -{ - MetaBackgroundPrivate *priv = self->priv; - CoglPipelineWrapMode wrap_mode; - - priv->style = style; - - wrap_mode = get_wrap_mode (self); - cogl_pipeline_set_layer_wrap_mode (priv->pipeline, 0, wrap_mode); -} - -static void -set_filename (MetaBackground *self, - const char *filename) -{ - MetaBackgroundPrivate *priv = self->priv; - - g_free (priv->filename); - priv->filename = g_strdup (filename); -} - -/** - * 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) -{ - 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); - - priv->shading_direction = shading_direction; - - 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 (); - } - - 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) -{ - 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) - { - clutter_actor_get_background_color (stage, &stage_color); - color = &stage_color; - } - - 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; - -static LoadFileTaskData * -load_file_task_data_new (const char *filename, - GDesktopBackgroundStyle style) -{ - LoadFileTaskData *task_data; - - task_data = g_slice_new (LoadFileTaskData); - task_data->style = style; - task_data->filename = g_strdup (filename); - - return task_data; -} - -static void -load_file_task_data_free (LoadFileTaskData *task_data) -{ - g_free (task_data->filename); - g_slice_free (LoadFileTaskData, task_data); -} - -static void -load_file (GTask *task, - MetaBackground *self, - LoadFileTaskData *task_data, - GCancellable *cancellable) -{ - GError *error = NULL; - GdkPixbuf *pixbuf; - - pixbuf = gdk_pixbuf_new_from_file (task_data->filename, - &error); - - if (pixbuf == NULL) - { - g_task_return_error (task, error); - return; - } - - g_task_return_pointer (task, pixbuf, (GDestroyNotify) g_object_unref); -} - -/** - * 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) -{ - 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); - - g_task_run_in_thread (task, (GTaskThreadFunc) load_file); - g_object_unref (task); -} - -/** - * 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) -{ - 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; - - g_return_val_if_fail (g_task_is_valid (result, self), FALSE); - - task = G_TASK (result); - - pixbuf = g_task_propagate_pointer (task, error); - - if (pixbuf == NULL) - goto out; - - task_data = g_task_get_task_data (task); - - 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); - - pixel_format = has_alpha ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888; - - texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx, width, height, - pixel_format, - row_stride, - pixels, - NULL)); - - if (texture == NULL) - { - g_set_error_literal (error, - COGL_BITMAP_ERROR, - COGL_BITMAP_ERROR_FAILED, - _("background texture could not be created from file")); - goto out; - } - - ensure_pipeline (self); - unset_texture (self); - set_style (self, task_data->style); - set_filename (self, task_data->filename); - set_texture (self, texture); - - clutter_content_invalidate (CLUTTER_CONTENT (self)); - loaded = TRUE; - -out: - if (pixbuf != NULL) - g_object_unref (pixbuf); - return loaded; -} - -/** - * 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; - - background = META_BACKGROUND (g_object_new (META_TYPE_BACKGROUND, - "meta-screen", self->priv->screen, - "monitor", monitor, - NULL)); - - background->priv->brightness = self->priv->brightness; - - 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); - - /* 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) - { - 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; - - if (effects != self->priv->effects) + int width = cogl_texture_get_width (texture); + int height = cogl_texture_get_height (texture); + CoglFramebuffer *fbo; + CoglError *catch_error = NULL; + CoglPipeline *pipeline; + + priv->wallpaper_texture = meta_create_texture (width, height, + COGL_TEXTURE_COMPONENTS_RGBA, + META_TEXTURE_FLAGS_NONE); + fbo = cogl_offscreen_new_with_texture (priv->wallpaper_texture); + + if (!cogl_framebuffer_allocate (fbo, &catch_error)) { - set_effects (background, effects); + /* This probably means that the size of the wallpapered texture is larger + * than the maximum texture size; we treat it as permanent until the + * background is changed again. + */ + cogl_error_free (catch_error); - if (effects & META_BACKGROUND_EFFECTS_VIGNETTE) - { - set_brightness (background, self->priv->brightness); - set_vignette_sharpness (background, self->priv->vignette_sharpness); - } + cogl_object_unref (priv->wallpaper_texture); + priv->wallpaper_texture = NULL; + cogl_object_unref (fbo); + + priv->wallpaper_allocation_failed = TRUE; + return FALSE; + } + + cogl_framebuffer_orthographic (fbo, 0, 0, + width, height, -1., 1.); + + 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); + + if (texture_has_alpha (texture)) + { + ensure_color_texture (self); + + 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); + } + + cogl_object_unref (fbo); + } + + return priv->wallpaper_texture != NULL; +} + +static CoglPipelineWrapMode +get_wrap_mode (GDesktopBackgroundStyle style) +{ + switch (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; + } +} + +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; + + g_return_if_fail (META_IS_BACKGROUND (self)); + priv = self->priv; + g_return_if_fail (monitor_index <= 0 && monitor_index < priv->n_monitors); + + monitor = &priv->monitors[monitor_index]; + + 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; + + 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; + + 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; + } + + 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; + } + + if (monitor->dirty) + { + CoglError *catch_error = NULL; + + if (monitor->texture == NULL) + { + monitor->texture = meta_create_texture (monitor_area.width, monitor_area.height, + COGL_TEXTURE_COMPONENTS_RGBA, + META_TEXTURE_FLAGS_NONE); + monitor->fbo = cogl_offscreen_new_with_texture (monitor->texture); + } + + if (!cogl_framebuffer_allocate (monitor->fbo, &catch_error)) + { + /* Texture or framebuffer allocation failed; it's unclear why this happened; + * we'll try again the next time this is called. (MetaBackgroundActor + * caches the result, so user might be left without a background.) + */ + cogl_object_unref (monitor->texture); + monitor->texture = NULL; + cogl_object_unref (monitor->fbo); + monitor->fbo = NULL; + + 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 (effects & META_BACKGROUND_EFFECTS_VIGNETTE) + if (texture1 != NULL && + !(texture2 != NULL && priv->blend_factor == 1.0 && !texture_has_alpha (texture2))) { - set_brightness (background, self->priv->brightness); - set_vignette_sharpness (background, self->priv->vignette_sharpness); + 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 (!((texture2 != NULL && priv->blend_factor == 1.0 && !texture_has_alpha (texture2)) || + (texture1 != NULL && !texture_has_alpha (texture1)))) + { + 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); + } + + monitor->dirty = FALSE; } - clutter_content_invalidate (CLUTTER_CONTENT (background)); - - 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 }; + + 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_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) +void +meta_background_set_gradient (MetaBackground *self, + GDesktopBackgroundShading shading_direction, + ClutterColor *color, + ClutterColor *second_color) { - return self->priv->shading_direction; + 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_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_filename (MetaBackground *self, + const char *filename, + GDesktopBackgroundStyle style) { - return &self->priv->color; + g_return_if_fail (META_IS_BACKGROUND (self)); + + meta_background_set_blend (self, filename, NULL, 0.0, style); } -/** - * 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_blend (MetaBackground *self, + const char *filename1, + const char *filename2, + double blend_factor, + GDesktopBackgroundStyle style) { - return &self->priv->second_color; -} + MetaBackgroundPrivate *priv; -/** - * 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) -{ - return self->priv->filename; + 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 7c2fccf01..38a66e7df 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 56e97688f..63558a1e1 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 -#include - -#include #include +#include #include @@ -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_set_vignette (MetaBackgroundActor *self, + double brightness, + double sharpness); +void meta_background_actor_unset_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 000000000..f0af93eb9 --- /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 . + */ + +#ifndef __META_BACKGROUND_IMAGE_H__ +#define __META_BACKGROUND_IMAGE_H__ + +#include +#include + +#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 a861600f8..38706dc6e 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 #include - -#include -#include - #include +#include /** * 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); +MetaBackground *meta_background_new (MetaScreen *screen); -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); +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 */