mutter/src/compositor/meta-background-content.c

1313 lines
48 KiB
C
Raw Normal View History

/*
* Copyright 2009 Sander Dijkhuis
* Copyright 2014 Red Hat, Inc.
* Copyright 2020 Endless Foundation.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
* Portions adapted from gnome-shell/src/shell-global.c
*/
/**
* SECTION:meta-background-content
* @title: MetaBackgroundContent
* @short_description: ClutterContent for painting the root window background
*
*/
/*
* The overall model drawing model of this content is that we have one
* texture, or two interpolated textures, possibly with alpha or
* margins that let the underlying background show through, blended
* over a solid color or a gradient. The result of that combination
* can then be affected by a "vignette" that darkens the background
* away from a central point (or as a no-GLSL fallback, simply darkens
* the background) and by overall opacity.
*
* As of GNOME 3.14, GNOME is only using a fraction of this when the
* user sets the background through the control center - what can be
* set is:
*
* A single image without a border
* An animation of images without a border that blend together,
* with the blend changing every 4-5 minutes
* A solid color with a repeated noise texture blended over it
*
* This all is pretty easy to do in a fragment shader, except when:
*
* A) We don't have GLSL - in this case, the operation of
* interpolating the two textures and blending the result over the
* background can't be expressed with Cogl's fixed-function layer
* combining (which is confined to what GL's texture environment
* combining can do) So we can only handle the above directly if
* there are no margins or alpha.
*
* B) The image textures are sliced. Texture size limits on older
* hardware (pre-965 intel hardware, r300, etc.) is often 2048,
* and it would be common to use a texture larger than this for a
* background and expect it to be scaled down. Cogl can compensate
* for this by breaking the texture up into multiple textures, but
* can't multitexture with sliced textures. So we can only handle
* the above if there's a single texture.
*
* However, even when we *can* represent everything in a single pass,
* it's not necessarily efficient. If we want to draw a 1024x768
* background, it's pretty inefficient to bilinearly texture from
* two 2560x1440 images and mix that. So the drawing model we take
* here is that MetaBackground generates a single texture (which
* might be a 1x1 texture for a solid color, or a 1x2 texture for a
* gradient, or a repeated texture for wallpaper, or a pre-rendered
* texture the size of the screen), and we draw with that, possibly
* adding the vignette and opacity.
*/
#include "config.h"
#include "compositor/meta-background-content-private.h"
#include "backends/meta-backend-private.h"
#include "clutter/clutter.h"
#include "compositor/clutter-utils.h"
#include "compositor/cogl-utils.h"
#include "compositor/meta-background-private.h"
#include "compositor/meta-cullable.h"
#include "meta/display.h"
typedef enum
{
CHANGED_BACKGROUND = 1 << 0,
CHANGED_EFFECTS = 1 << 2,
CHANGED_VIGNETTE_PARAMETERS = 1 << 3,
CHANGED_GRADIENT_PARAMETERS = 1 << 4,
CHANGED_ROUNDED_CLIP_PARAMETERS = 1 << 5,
CHANGED_ALL = 0xFFFF
} ChangedFlags;
#define GRADIENT_VERTEX_SHADER_DECLARATIONS \
"uniform vec2 scale;\n" \
"varying vec2 position;\n" \
#define GRADIENT_VERTEX_SHADER_CODE \
"position = cogl_tex_coord0_in.xy * scale;\n" \
#define GRADIENT_FRAGMENT_SHADER_DECLARATIONS \
"uniform float gradient_height_perc;\n" \
"uniform float gradient_max_darkness;\n" \
"varying vec2 position;\n" \
#define GRADIENT_FRAGMENT_SHADER_CODE \
"float min_brightness = 1.0 - gradient_max_darkness;\n" \
"float gradient_y_pos = min(position.y, gradient_height_perc) / gradient_height_perc;\n" \
"float pixel_brightness = (1.0 - min_brightness) * gradient_y_pos + min_brightness;\n" \
"cogl_color_out.rgb = cogl_color_out.rgb * pixel_brightness;\n" \
#define VIGNETTE_VERTEX_SHADER_DECLARATIONS \
"uniform vec2 scale;\n" \
"uniform vec2 offset;\n" \
"varying vec2 position;\n" \
#define VIGNETTE_VERTEX_SHADER_CODE \
"position = cogl_tex_coord0_in.xy * scale + offset;\n" \
#define VIGNETTE_SQRT_2 "1.4142"
#define VIGNETTE_FRAGMENT_SHADER_DECLARATIONS \
"uniform float vignette_sharpness;\n" \
"varying vec2 position;\n" \
"float rand(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453123); }\n" \
#define VIGNETTE_FRAGMENT_SHADER_CODE \
"float t = " VIGNETTE_SQRT_2 " * length(position);\n" \
"t = min(t, 1.0);\n" \
"float pixel_brightness = 1.0 - t * vignette_sharpness;\n" \
"cogl_color_out.rgb = cogl_color_out.rgb * pixel_brightness;\n" \
"cogl_color_out.rgb += (rand(position) - 0.5) / 255.0;\n" \
/* The ellipsis_dist(), ellipsis_coverage() and rounded_rect_coverage() are
* copied from GSK, see gsk_ellipsis_dist(), gsk_ellipsis_coverage(), and
* gsk_rounded_rect_coverage() here:
* https://gitlab.gnome.org/GNOME/gtk/-/blob/master/gsk/resources/glsl/preamble.fs.glsl
*/
#define ROUNDED_CLIP_FRAGMENT_SHADER_DECLARATIONS \
"uniform vec4 bounds; // x, y: top left; w, v: bottom right \n"\
"uniform vec4 corner_centers_1; // x, y: top left; w, v: top right \n"\
"uniform vec4 corner_centers_2; // x, y: bottom right; w, v: bottom left \n"\
"uniform vec2 pixel_step; \n"\
" \n"\
"float \n"\
"ellipsis_dist (vec2 p, vec2 radius) \n"\
"{ \n"\
" if (radius == vec2(0, 0)) \n"\
" return 0.0; \n"\
" \n"\
" vec2 p0 = p / radius; \n"\
" vec2 p1 = (2.0 * p0) / radius; \n"\
" \n"\
" return (dot(p0, p0) - 1.0) / length (p1); \n"\
"} \n"\
" \n"\
"float \n"\
"ellipsis_coverage (vec2 point, vec2 center, vec2 radius) \n"\
"{ \n"\
" float d = ellipsis_dist ((point - center), radius); \n"\
" return clamp (0.5 - d, 0.0, 1.0); \n"\
"} \n"\
" \n"\
"float \n"\
"rounded_rect_coverage (vec4 bounds, \n"\
" vec4 corner_centers_1, \n"\
" vec4 corner_centers_2, \n"\
" vec2 p) \n"\
"{ \n"\
" if (p.x < bounds.x || p.y < bounds.y || \n"\
" p.x >= bounds.z || p.y >= bounds.w) \n"\
" return 0.0; \n"\
" \n"\
" vec2 ref_tl = corner_centers_1.xy; \n"\
" vec2 ref_tr = corner_centers_1.zw; \n"\
" vec2 ref_br = corner_centers_2.xy; \n"\
" vec2 ref_bl = corner_centers_2.zw; \n"\
" \n"\
" if (p.x >= ref_tl.x && p.x >= ref_bl.x && \n"\
" p.x <= ref_tr.x && p.x <= ref_br.x) \n"\
" return 1.0; \n"\
" \n"\
" if (p.y >= ref_tl.y && p.y >= ref_tr.y && \n"\
" p.y <= ref_bl.y && p.y <= ref_br.y) \n"\
" return 1.0; \n"\
" \n"\
" vec2 rad_tl = corner_centers_1.xy - bounds.xy; \n"\
" vec2 rad_tr = corner_centers_1.zw - bounds.zy; \n"\
" vec2 rad_br = corner_centers_2.xy - bounds.zw; \n"\
" vec2 rad_bl = corner_centers_2.zw - bounds.xw; \n"\
" \n"\
" float d_tl = ellipsis_coverage(p, ref_tl, rad_tl); \n"\
" float d_tr = ellipsis_coverage(p, ref_tr, rad_tr); \n"\
" float d_br = ellipsis_coverage(p, ref_br, rad_br); \n"\
" float d_bl = ellipsis_coverage(p, ref_bl, rad_bl); \n"\
" \n"\
" vec4 corner_coverages = 1.0 - vec4(d_tl, d_tr, d_br, d_bl); \n"\
" \n"\
" bvec4 is_out = bvec4(p.x < ref_tl.x && p.y < ref_tl.y, \n"\
" p.x > ref_tr.x && p.y < ref_tr.y, \n"\
" p.x > ref_br.x && p.y > ref_br.y, \n"\
" p.x < ref_bl.x && p.y > ref_bl.y); \n"\
" \n"\
" return 1.0 - dot(vec4(is_out), corner_coverages); \n"\
"} \n"
#define ROUNDED_CLIP_FRAGMENT_SHADER_CODE \
"vec2 texture_coord; \n"\
" \n"\
"texture_coord = cogl_tex_coord0_in.xy / pixel_step; \n"\
" \n"\
"cogl_color_out *= rounded_rect_coverage (bounds, \n"\
" corner_centers_1, \n"\
" corner_centers_2, \n"\
" texture_coord); \n"
typedef struct _MetaBackgroundLayer MetaBackgroundLayer;
typedef enum
{
PIPELINE_VIGNETTE = (1 << 0),
PIPELINE_BLEND = (1 << 1),
PIPELINE_GRADIENT = (1 << 2),
PIPELINE_ROUNDED_CLIP = (1 << 3),
PIPELINE_ALL = (PIPELINE_VIGNETTE |
PIPELINE_BLEND |
PIPELINE_GRADIENT |
PIPELINE_ROUNDED_CLIP)
} PipelineFlags;
struct _MetaBackgroundContent
{
GObject parent;
MetaDisplay *display;
int monitor;
MetaBackground *background;
gboolean gradient;
double gradient_max_darkness;
int gradient_height;
gboolean vignette;
double vignette_brightness;
double vignette_sharpness;
gboolean has_rounded_clip;
float rounded_clip_radius;
gboolean rounded_clip_bounds_set;
graphene_rect_t rounded_clip_bounds;
ChangedFlags changed;
CoglPipeline *pipeline;
PipelineFlags pipeline_flags;
cairo_rectangle_int_t texture_area;
int texture_width, texture_height;
cairo_region_t *clip_region;
cairo_region_t *unobscured_region;
};
static void clutter_content_iface_init (ClutterContentInterface *iface);
G_DEFINE_TYPE_WITH_CODE (MetaBackgroundContent,
meta_background_content,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTENT,
clutter_content_iface_init));
enum
{
PROP_0,
PROP_META_DISPLAY,
PROP_MONITOR,
PROP_BACKGROUND,
PROP_GRADIENT,
PROP_GRADIENT_HEIGHT,
PROP_GRADIENT_MAX_DARKNESS,
PROP_VIGNETTE,
PROP_VIGNETTE_SHARPNESS,
PROP_VIGNETTE_BRIGHTNESS,
PROP_ROUNDED_CLIP_RADIUS,
N_PROPS,
};
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
set_clip_region (MetaBackgroundContent *self,
cairo_region_t *clip_region)
{
g_clear_pointer (&self->clip_region, cairo_region_destroy);
if (clip_region)
{
if (cairo_region_is_empty (clip_region))
self->clip_region = cairo_region_reference (clip_region);
else
self->clip_region = cairo_region_copy (clip_region);
}
}
static void
set_unobscured_region (MetaBackgroundContent *self,
cairo_region_t *unobscured_region)
{
g_clear_pointer (&self->unobscured_region, cairo_region_destroy);
if (unobscured_region)
{
if (cairo_region_is_empty (unobscured_region))
self->unobscured_region = cairo_region_reference (unobscured_region);
else
self->unobscured_region = cairo_region_copy (unobscured_region);
}
}
static void
invalidate_pipeline (MetaBackgroundContent *self,
ChangedFlags changed)
{
self->changed |= changed;
}
static void
on_background_changed (MetaBackground *background,
MetaBackgroundContent *self)
{
invalidate_pipeline (self, CHANGED_BACKGROUND);
clutter_content_invalidate (CLUTTER_CONTENT (self));
}
static CoglPipeline *
make_pipeline (PipelineFlags pipeline_flags)
{
static CoglPipeline *templates[PIPELINE_ALL + 1];
CoglPipeline **templatep;
g_assert (pipeline_flags < G_N_ELEMENTS (templates));
templatep = &templates[pipeline_flags];
if (*templatep == NULL)
{
/* Cogl automatically caches pipelines with no eviction policy,
* so we need to prevent identical pipelines from getting cached
* separately, by reusing the same shader snippets.
*/
*templatep = COGL_PIPELINE (meta_create_texture_pipeline (NULL));
if ((pipeline_flags & PIPELINE_VIGNETTE) != 0)
{
static CoglSnippet *vignette_vertex_snippet;
static CoglSnippet *vignette_fragment_snippet;
if (!vignette_vertex_snippet)
vignette_vertex_snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_VERTEX,
VIGNETTE_VERTEX_SHADER_DECLARATIONS,
VIGNETTE_VERTEX_SHADER_CODE);
cogl_pipeline_add_snippet (*templatep, vignette_vertex_snippet);
if (!vignette_fragment_snippet)
vignette_fragment_snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
VIGNETTE_FRAGMENT_SHADER_DECLARATIONS,
VIGNETTE_FRAGMENT_SHADER_CODE);
cogl_pipeline_add_snippet (*templatep, vignette_fragment_snippet);
}
if ((pipeline_flags & PIPELINE_GRADIENT) != 0)
{
static CoglSnippet *gradient_vertex_snippet;
static CoglSnippet *gradient_fragment_snippet;
if (!gradient_vertex_snippet)
gradient_vertex_snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_VERTEX,
GRADIENT_VERTEX_SHADER_DECLARATIONS,
GRADIENT_VERTEX_SHADER_CODE);
cogl_pipeline_add_snippet (*templatep, gradient_vertex_snippet);
if (!gradient_fragment_snippet)
gradient_fragment_snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
GRADIENT_FRAGMENT_SHADER_DECLARATIONS,
GRADIENT_FRAGMENT_SHADER_CODE);
cogl_pipeline_add_snippet (*templatep, gradient_fragment_snippet);
}
if ((pipeline_flags & PIPELINE_ROUNDED_CLIP) != 0)
{
static CoglSnippet *rounded_clip_fragment_snippet;
if (!rounded_clip_fragment_snippet)
{
rounded_clip_fragment_snippet =
cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT,
ROUNDED_CLIP_FRAGMENT_SHADER_DECLARATIONS,
ROUNDED_CLIP_FRAGMENT_SHADER_CODE);
}
cogl_pipeline_add_snippet (*templatep, rounded_clip_fragment_snippet);
}
if ((pipeline_flags & PIPELINE_BLEND) == 0)
cogl_pipeline_set_blend (*templatep, "RGBA = ADD (SRC_COLOR, 0)", NULL);
}
return cogl_pipeline_copy (*templatep);
}
static void
setup_pipeline (MetaBackgroundContent *self,
ClutterActor *actor,
ClutterPaintContext *paint_context,
cairo_rectangle_int_t *actor_pixel_rect)
{
PipelineFlags pipeline_flags = 0;
guint8 opacity;
float color_component;
CoglFramebuffer *fb;
CoglPipelineFilter min_filter, mag_filter;
opacity = clutter_actor_get_paint_opacity (actor);
if (opacity < 255)
pipeline_flags |= PIPELINE_BLEND;
if (self->vignette && clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
pipeline_flags |= PIPELINE_VIGNETTE;
if (self->gradient && clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
pipeline_flags |= PIPELINE_GRADIENT;
if (self->has_rounded_clip && clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
pipeline_flags |= PIPELINE_ROUNDED_CLIP | PIPELINE_BLEND;
if (pipeline_flags != self->pipeline_flags)
g_clear_pointer (&self->pipeline, cogl_object_unref);
if (self->pipeline == NULL)
{
self->pipeline_flags = pipeline_flags;
self->pipeline = make_pipeline (pipeline_flags);
self->changed = CHANGED_ALL;
}
if (self->changed & CHANGED_BACKGROUND)
{
CoglPipelineWrapMode wrap_mode;
CoglTexture *texture = meta_background_get_texture (self->background,
self->monitor,
&self->texture_area,
&wrap_mode);
if (texture)
{
self->texture_width = cogl_texture_get_width (texture);
self->texture_height = cogl_texture_get_height (texture);
}
else
{
self->texture_width = 0;
self->texture_height = 0;
}
cogl_pipeline_set_layer_texture (self->pipeline, 0, texture);
cogl_pipeline_set_layer_wrap_mode (self->pipeline, 0, wrap_mode);
self->changed &= ~CHANGED_BACKGROUND;
}
if (self->changed & CHANGED_VIGNETTE_PARAMETERS)
{
cogl_pipeline_set_uniform_1f (self->pipeline,
cogl_pipeline_get_uniform_location (self->pipeline,
"vignette_sharpness"),
self->vignette_sharpness);
self->changed &= ~CHANGED_VIGNETTE_PARAMETERS;
}
if (self->changed & CHANGED_GRADIENT_PARAMETERS)
{
MetaRectangle monitor_geometry;
float gradient_height_perc;
meta_display_get_monitor_geometry (self->display,
self->monitor, &monitor_geometry);
gradient_height_perc = MAX (0.0001, self->gradient_height / (float)monitor_geometry.height);
cogl_pipeline_set_uniform_1f (self->pipeline,
cogl_pipeline_get_uniform_location (self->pipeline,
"gradient_height_perc"),
gradient_height_perc);
cogl_pipeline_set_uniform_1f (self->pipeline,
cogl_pipeline_get_uniform_location (self->pipeline,
"gradient_max_darkness"),
self->gradient_max_darkness);
self->changed &= ~CHANGED_GRADIENT_PARAMETERS;
}
if (self->changed & CHANGED_ROUNDED_CLIP_PARAMETERS)
{
float monitor_scale;
float bounds_x1, bounds_x2, bounds_y1, bounds_y2;
float clip_radius;
monitor_scale = meta_is_stage_views_scaled ()
? meta_display_get_monitor_scale (self->display, self->monitor)
: 1.0;
if (self->rounded_clip_bounds_set)
{
bounds_x1 = self->rounded_clip_bounds.origin.x;
bounds_x2 = self->rounded_clip_bounds.origin.x + self->rounded_clip_bounds.size.width;
bounds_y1 = self->rounded_clip_bounds.origin.y;
bounds_y2 = self->rounded_clip_bounds.origin.y + self->rounded_clip_bounds.size.height;
bounds_x1 *= monitor_scale;
bounds_x2 *= monitor_scale;
bounds_y1 *= monitor_scale;
bounds_y2 *= monitor_scale;
}
else
{
bounds_x1 = 0.0;
bounds_x2 = self->texture_width;
bounds_y1 = 0.0;
bounds_y2 = self->texture_height;
}
clip_radius = self->rounded_clip_radius * monitor_scale;
float bounds[] = {
bounds_x1,
bounds_y1,
bounds_x2,
bounds_y2,
};
float corner_centers_1[] = {
bounds_x1 + clip_radius,
bounds_y1 + clip_radius,
bounds_x2 - clip_radius,
bounds_y1 + clip_radius,
};
float corner_centers_2[] = {
bounds_x2 - clip_radius,
bounds_y2 - clip_radius,
bounds_x1 + clip_radius,
bounds_y2 - clip_radius,
};
int bounds_uniform_location;
int corner_centers_1_uniform_location;
int corner_centers_2_uniform_location;
bounds_uniform_location =
cogl_pipeline_get_uniform_location (self->pipeline, "bounds");
corner_centers_1_uniform_location =
cogl_pipeline_get_uniform_location (self->pipeline, "corner_centers_1");
corner_centers_2_uniform_location =
cogl_pipeline_get_uniform_location (self->pipeline, "corner_centers_2");
cogl_pipeline_set_uniform_float (self->pipeline,
bounds_uniform_location,
4, 1,
bounds);
cogl_pipeline_set_uniform_float (self->pipeline,
corner_centers_1_uniform_location,
4, 1,
corner_centers_1);
cogl_pipeline_set_uniform_float (self->pipeline,
corner_centers_2_uniform_location,
4, 1,
corner_centers_2);
self->changed &= ~CHANGED_ROUNDED_CLIP_PARAMETERS;
}
if (self->vignette)
{
color_component = self->vignette_brightness * opacity / 255.;
if (!clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
{
/* Darken everything to match the average brightness that would
* be there if we were drawing the vignette, which is
* (1 - (pi/12.) * vignette_sharpness) [exercise for the reader :]
*/
color_component *= (1 - 0.74 * self->vignette_sharpness);
}
}
else
{
color_component = opacity / 255.;
}
cogl_pipeline_set_color4f (self->pipeline,
color_component,
color_component,
color_component,
opacity / 255.);
fb = clutter_paint_context_get_framebuffer (paint_context);
if (meta_actor_painting_untransformed (fb,
actor_pixel_rect->width,
actor_pixel_rect->height,
self->texture_width,
self->texture_height,
NULL, NULL))
{
min_filter = COGL_PIPELINE_FILTER_NEAREST;
mag_filter = COGL_PIPELINE_FILTER_NEAREST;
}
else
{
min_filter = COGL_PIPELINE_FILTER_LINEAR_MIPMAP_NEAREST;
mag_filter = COGL_PIPELINE_FILTER_LINEAR;
}
cogl_pipeline_set_layer_filters (self->pipeline, 0, min_filter, mag_filter);
}
static void
set_glsl_parameters (MetaBackgroundContent *self,
cairo_rectangle_int_t *actor_pixel_rect)
{
float monitor_scale;
float scale[2];
float offset[2];
int pixel_step_uniform_location;
monitor_scale = meta_is_stage_views_scaled ()
? meta_display_get_monitor_scale (self->display, self->monitor)
: 1.0;
float pixel_step[] = {
1.f / (self->texture_area.width * monitor_scale),
1.f / (self->texture_area.height * monitor_scale),
};
pixel_step_uniform_location =
cogl_pipeline_get_uniform_location (self->pipeline,
"pixel_step");
/* Compute a scale and offset for transforming texture coordinates to the
* coordinate system from [-0.5 to 0.5] across the area of the actor
*/
scale[0] = self->texture_area.width / (float)actor_pixel_rect->width;
scale[1] = self->texture_area.height / (float)actor_pixel_rect->height;
offset[0] = self->texture_area.x / (float)actor_pixel_rect->width - 0.5;
offset[1] = self->texture_area.y / (float)actor_pixel_rect->height - 0.5;
cogl_pipeline_set_uniform_float (self->pipeline,
cogl_pipeline_get_uniform_location (self->pipeline,
"scale"),
2, 1, scale);
cogl_pipeline_set_uniform_float (self->pipeline,
cogl_pipeline_get_uniform_location (self->pipeline,
"offset"),
2, 1, offset);
cogl_pipeline_set_uniform_float (self->pipeline,
pixel_step_uniform_location,
2, 1,
pixel_step);
}
static void
paint_clipped_rectangle (MetaBackgroundContent *self,
ClutterPaintNode *node,
ClutterActorBox *actor_box,
cairo_rectangle_int_t *rect)
{
g_autoptr (ClutterPaintNode) pipeline_node = NULL;
float h_scale, v_scale;
float x1, y1, x2, y2;
float tx1, ty1, tx2, ty2;
h_scale = self->texture_area.width / clutter_actor_box_get_width (actor_box);
v_scale = self->texture_area.height / clutter_actor_box_get_height (actor_box);
x1 = rect->x;
y1 = rect->y;
x2 = rect->x + rect->width;
y2 = rect->y + rect->height;
tx1 = (x1 * h_scale - self->texture_area.x) / (float)self->texture_area.width;
ty1 = (y1 * v_scale - self->texture_area.y) / (float)self->texture_area.height;
tx2 = (x2 * h_scale - self->texture_area.x) / (float)self->texture_area.width;
ty2 = (y2 * v_scale - self->texture_area.y) / (float)self->texture_area.height;
pipeline_node = clutter_pipeline_node_new (self->pipeline);
clutter_paint_node_set_name (pipeline_node, "MetaBackgroundContent (Slice)");
clutter_paint_node_add_texture_rectangle (pipeline_node,
&(ClutterActorBox) {
.x1 = x1,
.y1 = y1,
.x2 = x2,
.y2 = y2,
},
tx1, ty1,
tx2, ty2);
clutter_paint_node_add_child (node, pipeline_node);
}
static void
meta_background_content_paint_content (ClutterContent *content,
ClutterActor *actor,
ClutterPaintNode *node,
ClutterPaintContext *paint_context)
{
MetaBackgroundContent *self = META_BACKGROUND_CONTENT (content);
ClutterActorBox actor_box;
cairo_rectangle_int_t rect_within_actor;
cairo_rectangle_int_t rect_within_stage;
cairo_region_t *region;
int i, n_rects;
float transformed_x, transformed_y, transformed_width, transformed_height;
gboolean untransformed;
if ((self->clip_region && cairo_region_is_empty (self->clip_region)))
return;
clutter_actor_get_content_box (actor, &actor_box);
rect_within_actor.x = actor_box.x1;
rect_within_actor.y = actor_box.y1;
rect_within_actor.width = actor_box.x2 - actor_box.x1;
rect_within_actor.height = actor_box.y2 - actor_box.y1;
if (clutter_actor_is_in_clone_paint (actor))
{
untransformed = FALSE;
}
else
{
clutter_actor_get_transformed_position (actor,
&transformed_x,
&transformed_y);
rect_within_stage.x = floorf (transformed_x);
rect_within_stage.y = floorf (transformed_y);
clutter_actor_get_transformed_size (actor,
&transformed_width,
&transformed_height);
rect_within_stage.width = ceilf (transformed_width);
rect_within_stage.height = ceilf (transformed_height);
untransformed =
rect_within_actor.x == rect_within_stage.x &&
rect_within_actor.y == rect_within_stage.y &&
rect_within_actor.width == rect_within_stage.width &&
rect_within_actor.height == rect_within_stage.height;
}
if (untransformed) /* actor and stage space are the same */
{
if (self->clip_region)
{
region = cairo_region_copy (self->clip_region);
cairo_region_intersect_rectangle (region, &rect_within_stage);
}
else
{
const cairo_region_t *redraw_clip;
redraw_clip = clutter_paint_context_get_redraw_clip (paint_context);
if (redraw_clip)
{
region = cairo_region_copy (redraw_clip);
cairo_region_intersect_rectangle (region, &rect_within_stage);
}
else
{
region = cairo_region_create_rectangle (&rect_within_stage);
}
}
}
else /* actor and stage space are different but we need actor space */
{
if (self->clip_region)
{
region = cairo_region_copy (self->clip_region);
cairo_region_intersect_rectangle (region, &rect_within_actor);
}
else
{
region = cairo_region_create_rectangle (&rect_within_actor);
}
}
if (self->unobscured_region)
cairo_region_intersect (region, self->unobscured_region);
/* region is now in actor space */
if (cairo_region_is_empty (region))
{
cairo_region_destroy (region);
return;
}
setup_pipeline (self, actor, paint_context, &rect_within_actor);
set_glsl_parameters (self, &rect_within_actor);
/* Limit to how many separate rectangles we'll draw; beyond this just
* fall back and draw the whole thing */
#define MAX_RECTS 64
n_rects = cairo_region_num_rectangles (region);
if (n_rects <= MAX_RECTS)
{
for (i = 0; i < n_rects; i++)
{
cairo_rectangle_int_t rect;
cairo_region_get_rectangle (region, i, &rect);
paint_clipped_rectangle (self, node, &actor_box, &rect);
}
}
else
{
cairo_rectangle_int_t rect;
cairo_region_get_extents (region, &rect);
paint_clipped_rectangle (self, node, &actor_box, &rect);
}
cairo_region_destroy (region);
}
static gboolean
meta_background_content_get_preferred_size (ClutterContent *content,
float *width,
float *height)
{
MetaBackgroundContent *background_content = META_BACKGROUND_CONTENT (content);
MetaRectangle monitor_geometry;
meta_display_get_monitor_geometry (background_content->display,
background_content->monitor,
&monitor_geometry);
if (width)
*width = monitor_geometry.width;
if (height)
*height = monitor_geometry.height;
return TRUE;
}
static void
clutter_content_iface_init (ClutterContentInterface *iface)
{
iface->paint_content = meta_background_content_paint_content;
iface->get_preferred_size = meta_background_content_get_preferred_size;
}
static void
set_monitor (MetaBackgroundContent *self,
int monitor)
{
MetaRectangle old_monitor_geometry;
MetaRectangle new_monitor_geometry;
MetaDisplay *display = self->display;
if(self->monitor == monitor)
return;
meta_display_get_monitor_geometry (display, self->monitor, &old_monitor_geometry);
meta_display_get_monitor_geometry (display, monitor, &new_monitor_geometry);
if(old_monitor_geometry.height != new_monitor_geometry.height)
invalidate_pipeline (self, CHANGED_GRADIENT_PARAMETERS);
self->monitor = monitor;
}
static void
meta_background_content_dispose (GObject *object)
{
MetaBackgroundContent *self = META_BACKGROUND_CONTENT (object);
set_clip_region (self, NULL);
set_unobscured_region (self, NULL);
meta_background_content_set_background (self, NULL);
g_clear_pointer (&self->pipeline, cogl_object_unref);
G_OBJECT_CLASS (meta_background_content_parent_class)->dispose (object);
}
static void
meta_background_content_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MetaBackgroundContent *self = META_BACKGROUND_CONTENT (object);
switch (prop_id)
{
case PROP_META_DISPLAY:
self->display = g_value_get_object (value);
break;
case PROP_MONITOR:
set_monitor (self, g_value_get_int (value));
break;
case PROP_BACKGROUND:
meta_background_content_set_background (self, g_value_get_object (value));
break;
case PROP_GRADIENT:
meta_background_content_set_gradient (self,
g_value_get_boolean (value),
self->gradient_height,
self->gradient_max_darkness);
break;
case PROP_GRADIENT_HEIGHT:
meta_background_content_set_gradient (self,
self->gradient,
g_value_get_int (value),
self->gradient_max_darkness);
break;
case PROP_GRADIENT_MAX_DARKNESS:
meta_background_content_set_gradient (self,
self->gradient,
self->gradient_height,
g_value_get_double (value));
break;
case PROP_VIGNETTE:
meta_background_content_set_vignette (self,
g_value_get_boolean (value),
self->vignette_brightness,
self->vignette_sharpness);
break;
case PROP_VIGNETTE_SHARPNESS:
meta_background_content_set_vignette (self,
self->vignette,
self->vignette_brightness,
g_value_get_double (value));
break;
case PROP_VIGNETTE_BRIGHTNESS:
meta_background_content_set_vignette (self,
self->vignette,
g_value_get_double (value),
self->vignette_sharpness);
break;
case PROP_ROUNDED_CLIP_RADIUS:
meta_background_content_set_rounded_clip_radius (self,
g_value_get_float (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
meta_background_content_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MetaBackgroundContent *self = META_BACKGROUND_CONTENT (object);
switch (prop_id)
{
case PROP_META_DISPLAY:
g_value_set_object (value, self->display);
break;
case PROP_MONITOR:
g_value_set_int (value, self->monitor);
break;
case PROP_BACKGROUND:
g_value_set_object (value, self->background);
break;
case PROP_GRADIENT:
g_value_set_boolean (value, self->gradient);
break;
case PROP_GRADIENT_HEIGHT:
g_value_set_int (value, self->gradient_height);
break;
case PROP_GRADIENT_MAX_DARKNESS:
g_value_set_double (value, self->gradient_max_darkness);
break;
case PROP_VIGNETTE:
g_value_set_boolean (value, self->vignette);
break;
case PROP_VIGNETTE_BRIGHTNESS:
g_value_set_double (value, self->vignette_brightness);
break;
case PROP_VIGNETTE_SHARPNESS:
g_value_set_double (value, self->vignette_sharpness);
break;
case PROP_ROUNDED_CLIP_RADIUS:
g_value_set_float (value, self->rounded_clip_radius);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
meta_background_content_class_init (MetaBackgroundContentClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = meta_background_content_dispose;
object_class->set_property = meta_background_content_set_property;
object_class->get_property = meta_background_content_get_property;
properties[PROP_META_DISPLAY] =
g_param_spec_object ("meta-display",
"MetaDisplay",
"MetaDisplay",
META_TYPE_DISPLAY,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
properties[PROP_MONITOR] =
g_param_spec_int ("monitor",
"monitor",
"monitor",
0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
properties[PROP_BACKGROUND] =
g_param_spec_object ("background",
"Background",
"MetaBackground object holding background parameters",
META_TYPE_BACKGROUND,
G_PARAM_READWRITE);
properties[PROP_GRADIENT] =
g_param_spec_boolean ("gradient",
"Gradient",
"Whether gradient effect is enabled",
FALSE,
G_PARAM_READWRITE);
properties[PROP_GRADIENT_HEIGHT] =
g_param_spec_int ("gradient-height",
"Gradient Height",
"Height of gradient effect",
0, G_MAXINT, 0,
G_PARAM_READWRITE);
properties[PROP_GRADIENT_MAX_DARKNESS] =
g_param_spec_double ("gradient-max-darkness",
"Gradient Max Darkness",
"How dark is the gradient initially",
0.0, 1.0, 0.0,
G_PARAM_READWRITE);
properties[PROP_VIGNETTE] =
g_param_spec_boolean ("vignette",
"Vignette",
"Whether vignette effect is enabled",
FALSE,
G_PARAM_READWRITE);
properties[PROP_VIGNETTE_BRIGHTNESS] =
g_param_spec_double ("brightness",
"Vignette Brightness",
"Brightness of vignette effect",
0.0, 1.0, 1.0,
G_PARAM_READWRITE);
properties[PROP_VIGNETTE_SHARPNESS] =
g_param_spec_double ("vignette-sharpness",
"Vignette Sharpness",
"Sharpness of vignette effect",
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE);
properties[PROP_ROUNDED_CLIP_RADIUS] =
g_param_spec_float ("rounded-clip-radius",
"Rounded clip radius",
"Rounded clip radius",
0.0, G_MAXFLOAT, 0.0,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
meta_background_content_init (MetaBackgroundContent *self)
{
self->gradient = FALSE;
self->gradient_height = 0;
self->gradient_max_darkness = 0.0;
self->vignette = FALSE;
self->vignette_brightness = 1.0;
self->vignette_sharpness = 0.0;
self->has_rounded_clip = FALSE;
self->rounded_clip_radius = 0.0;
}
/**
* meta_background_content_new:
* @monitor: Index of the monitor for which to draw the background
*
* Creates a new actor to draw the background for the given monitor.
*
* Return value: (transfer full): the newly created background actor
*/
ClutterContent *
meta_background_content_new (MetaDisplay *display,
int monitor)
{
return g_object_new (META_TYPE_BACKGROUND_CONTENT,
"meta-display", display,
"monitor", monitor,
NULL);
}
void
meta_background_content_set_background (MetaBackgroundContent *self,
MetaBackground *background)
{
g_return_if_fail (META_IS_BACKGROUND_CONTENT (self));
g_return_if_fail (background == NULL || META_IS_BACKGROUND (background));
if (background == self->background)
return;
if (self->background)
{
g_signal_handlers_disconnect_by_func (self->background,
(gpointer)on_background_changed,
self);
g_clear_object (&self->background);
}
if (background)
{
self->background = g_object_ref (background);
g_signal_connect (self->background, "changed",
G_CALLBACK (on_background_changed), self);
}
invalidate_pipeline (self, CHANGED_BACKGROUND);
clutter_content_invalidate (CLUTTER_CONTENT (self));
}
void
meta_background_content_set_gradient (MetaBackgroundContent *self,
gboolean enabled,
int height,
double max_darkness)
{
gboolean changed = FALSE;
g_return_if_fail (META_IS_BACKGROUND_CONTENT (self));
g_return_if_fail (height >= 0);
g_return_if_fail (max_darkness >= 0. && max_darkness <= 1.);
enabled = enabled != FALSE && height != 0;
if (enabled != self->gradient)
{
self->gradient = enabled;
invalidate_pipeline (self, CHANGED_EFFECTS);
changed = TRUE;
}
if (height != self->gradient_height || max_darkness != self->gradient_max_darkness)
{
self->gradient_height = height;
self->gradient_max_darkness = max_darkness;
invalidate_pipeline (self, CHANGED_GRADIENT_PARAMETERS);
changed = TRUE;
}
if (changed)
clutter_content_invalidate (CLUTTER_CONTENT (self));
}
void
meta_background_content_set_vignette (MetaBackgroundContent *self,
gboolean enabled,
double brightness,
double sharpness)
{
gboolean changed = FALSE;
g_return_if_fail (META_IS_BACKGROUND_CONTENT (self));
g_return_if_fail (brightness >= 0. && brightness <= 1.);
g_return_if_fail (sharpness >= 0.);
enabled = enabled != FALSE;
if (enabled != self->vignette)
{
self->vignette = enabled;
invalidate_pipeline (self, CHANGED_EFFECTS);
changed = TRUE;
}
if (brightness != self->vignette_brightness || sharpness != self->vignette_sharpness)
{
self->vignette_brightness = brightness;
self->vignette_sharpness = sharpness;
invalidate_pipeline (self, CHANGED_VIGNETTE_PARAMETERS);
changed = TRUE;
}
if (changed)
clutter_content_invalidate (CLUTTER_CONTENT (self));
}
void
meta_background_content_set_rounded_clip_radius (MetaBackgroundContent *self,
float radius)
{
gboolean enabled;
gboolean changed = FALSE;
g_return_if_fail (META_IS_BACKGROUND_CONTENT (self));
g_return_if_fail (radius >= 0.0);
enabled = radius > 0.0;
if (enabled != self->has_rounded_clip)
{
self->has_rounded_clip = enabled;
invalidate_pipeline (self, CHANGED_EFFECTS);
changed = TRUE;
}
if (!G_APPROX_VALUE (radius, self->rounded_clip_radius, FLT_EPSILON))
{
self->rounded_clip_radius = radius;
invalidate_pipeline (self, CHANGED_ROUNDED_CLIP_PARAMETERS);
changed = TRUE;
}
if (changed)
{
clutter_content_invalidate (CLUTTER_CONTENT (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ROUNDED_CLIP_RADIUS]);
}
}
/**
* meta_background_content_set_rounded_clip_bounds:
* @self: The #MetaBackgroundContent
* @bounds: (allow-none): The new bounding clip rectangle, or %NULL
*
* Sets the bounding clip rectangle of the #MetaBackgroundContent that's used
* when a rounded clip set via meta_background_content_set_rounded_clip_radius()
* is in effect, set it to %NULL to use no bounding clip, rounding the edges
* of the full texture.
*/
void
meta_background_content_set_rounded_clip_bounds (MetaBackgroundContent *self,
const graphene_rect_t *bounds)
{
g_return_if_fail (META_IS_BACKGROUND_CONTENT (self));
if (bounds == NULL)
{
if (!self->rounded_clip_bounds_set)
return;
self->rounded_clip_bounds_set = FALSE;
}
else
{
if (self->rounded_clip_bounds_set &&
graphene_rect_equal (&self->rounded_clip_bounds, bounds))
return;
self->rounded_clip_bounds_set = TRUE;
graphene_rect_init_from_rect (&self->rounded_clip_bounds, bounds);
}
invalidate_pipeline (self, CHANGED_ROUNDED_CLIP_PARAMETERS);
clutter_content_invalidate (CLUTTER_CONTENT (self));
}
cairo_region_t *
meta_background_content_get_clip_region (MetaBackgroundContent *self)
{
return self->clip_region;
}
void
meta_background_content_cull_out (MetaBackgroundContent *self,
cairo_region_t *unobscured_region,
cairo_region_t *clip_region)
{
set_unobscured_region (self, unobscured_region);
set_clip_region (self, clip_region);
}
void
meta_background_content_reset_culling (MetaBackgroundContent *self)
{
set_unobscured_region (self, NULL);
set_clip_region (self, NULL);
}