Reintroduce background crossfading

... but do it right, using Clutter animation framework for vblank syncing
and Cogl to generate GLSL shaders that do the hard work of blending the
old and the new background in HW.

https://bugzilla.gnome.org/show_bug.cgi?id=682427
This commit is contained in:
Giovanni Campagna
2012-11-11 00:16:48 +01:00
parent dbc9303efd
commit 3e723caabd
4 changed files with 251 additions and 134 deletions

View File

@@ -23,47 +23,8 @@
#include "cogl-utils.h" #include "cogl-utils.h"
/** #define CLUTTER_ENABLE_EXPERIMENTAL_API
* meta_create_color_texture_4ub: #include <clutter/clutter.h>
* @red:
* @green:
* @blue:
* @alpha:
* @flags: Optional flags for the texture, or %COGL_TEXTURE_NONE;
* %COGL_TEXTURE_NO_SLICING is useful if the texture will be
* repeated to create a constant color fill, since hardware
* repeat can't be used for a sliced texture.
*
* Creates a texture that is a single pixel with the specified
* unpremultiplied color components.
*
* Return value: (transfer full): a newly created Cogl texture
*/
CoglHandle
meta_create_color_texture_4ub (guint8 red,
guint8 green,
guint8 blue,
guint8 alpha,
CoglTextureFlags flags)
{
CoglColor color;
guint8 pixel[4];
cogl_color_set_from_4ub (&color, red, green, blue, alpha);
cogl_color_premultiply (&color);
pixel[0] = cogl_color_get_red_byte (&color);
pixel[1] = cogl_color_get_green_byte (&color);
pixel[2] = cogl_color_get_blue_byte (&color);
pixel[3] = cogl_color_get_alpha_byte (&color);
return cogl_texture_new_from_data (1, 1,
flags,
COGL_PIXEL_FORMAT_RGBA_8888_PRE,
COGL_PIXEL_FORMAT_ANY,
4, pixel);
}
/* Based on gnome-shell/src/st/st-private.c:_st_create_texture_material.c */ /* Based on gnome-shell/src/st/st-private.c:_st_create_texture_material.c */
@@ -79,32 +40,69 @@ meta_create_color_texture_4ub (guint8 red,
* *
* Return value: (transfer full): a newly created Cogl material * Return value: (transfer full): a newly created Cogl material
*/ */
CoglHandle CoglPipeline *
meta_create_texture_material (CoglHandle src_texture) meta_create_texture_material (CoglHandle src_texture)
{ {
static CoglHandle texture_material_template = COGL_INVALID_HANDLE; static CoglPipeline *texture_material_template = NULL;
CoglHandle material; CoglPipeline *material;
/* We use a material that has a dummy texture as a base for all if (G_UNLIKELY (texture_material_template == NULL))
texture materials. The idea is that only the Cogl texture object
would be different in the children so it is likely that Cogl will
be able to share GL programs between all the textures. */
if (G_UNLIKELY (texture_material_template == COGL_INVALID_HANDLE))
{ {
CoglHandle dummy_texture; ClutterBackend *backend = clutter_get_default_backend ();
CoglContext *context = clutter_backend_get_cogl_context (backend);
dummy_texture = meta_create_color_texture_4ub (0xff, 0xff, 0xff, 0xff, texture_material_template = cogl_pipeline_new (context);
COGL_TEXTURE_NONE); cogl_pipeline_set_layer_null_texture (texture_material_template,
0, COGL_TEXTURE_TYPE_2D);
texture_material_template = cogl_material_new ();
cogl_material_set_layer (texture_material_template, 0, dummy_texture);
cogl_handle_unref (dummy_texture);
} }
material = cogl_material_copy (texture_material_template); material = cogl_pipeline_copy (texture_material_template);
if (src_texture != COGL_INVALID_HANDLE) if (src_texture != COGL_INVALID_HANDLE)
cogl_material_set_layer (material, 0, src_texture); cogl_pipeline_set_layer_texture (material, 0, src_texture);
return material;
}
/**
* meta_create_crossfade_material:
* @src_texture_0: (allow-none): the texture to crossfade from
* @src_texture_1: (allow-none): the texture to crossfade to
*
* Creates a material with two layers, using a combine constant to
* crossfade between them.
*
* Return value: (transfer full): a newly created Cogl material
*/
CoglPipeline *
meta_create_crossfade_material (CoglHandle src_texture_0,
CoglHandle src_texture_1)
{
static CoglPipeline *texture_material_template = NULL;
CoglPipeline *material;
if (G_UNLIKELY (texture_material_template == NULL))
{
ClutterBackend *backend = clutter_get_default_backend ();
CoglContext *context = clutter_backend_get_cogl_context (backend);
texture_material_template = cogl_pipeline_new (context);
cogl_pipeline_set_layer_null_texture (texture_material_template,
0, COGL_TEXTURE_TYPE_2D);
cogl_pipeline_set_layer_null_texture (texture_material_template,
1, COGL_TEXTURE_TYPE_2D);
cogl_pipeline_set_layer_combine (texture_material_template,
1, "RGBA = INTERPOLATE (TEXTURE, PREVIOUS, CONSTANT[A])",
NULL);
}
material = cogl_pipeline_copy (texture_material_template);
if (src_texture_0 != COGL_INVALID_HANDLE)
cogl_pipeline_set_layer_texture (material, 0, src_texture_0);
if (src_texture_1 != COGL_INVALID_HANDLE)
cogl_pipeline_set_layer_texture (material, 1, src_texture_1);
return material; return material;
} }

View File

@@ -23,13 +23,11 @@
#ifndef __META_COGL_UTILS_H__ #ifndef __META_COGL_UTILS_H__
#define __META_COGL_UTILS_H__ #define __META_COGL_UTILS_H__
#define COGL_ENABLE_EXPERIMENTAL_API
#include <cogl/cogl.h> #include <cogl/cogl.h>
CoglHandle meta_create_color_texture_4ub (guint8 red, CoglPipeline *meta_create_texture_material (CoglHandle src_texture);
guint8 green, CoglPipeline *meta_create_crossfade_material (CoglHandle src_texture_0,
guint8 blue, CoglHandle src_texture_1);
guint8 alpha,
CoglTextureFlags flags);
CoglHandle meta_create_texture_material (CoglHandle src_texture);
#endif /* __META_COGL_UTILS_H__ */ #endif /* __META_COGL_UTILS_H__ */

View File

@@ -117,21 +117,6 @@ process_property_notify (MetaCompositor *compositor,
{ {
MetaWindowActor *window_actor; MetaWindowActor *window_actor;
if (event->atom == compositor->atom_x_root_pixmap)
{
GSList *l;
for (l = meta_display_get_screens (compositor->display); l; l = l->next)
{
MetaScreen *screen = l->data;
if (event->window == meta_screen_get_xroot (screen))
{
meta_background_actor_update (screen);
return;
}
}
}
if (window == NULL) if (window == NULL)
return; return;

View File

@@ -34,6 +34,8 @@
#include <meta/errors.h> #include <meta/errors.h>
#include "meta-background-actor-private.h" #include "meta-background-actor-private.h"
#define CROSSFADE_DURATION 1000
/* We allow creating multiple MetaBackgroundActors for the same MetaScreen to /* We allow creating multiple MetaBackgroundActors for the same MetaScreen to
* allow different rendering options to be set for different copies. * allow different rendering options to be set for different copies.
* But we want to share the same underlying CoglTexture for efficiency and * But we want to share the same underlying CoglTexture for efficiency and
@@ -55,6 +57,7 @@ struct _MetaScreenBackground
float texture_width; float texture_width;
float texture_height; float texture_height;
CoglTexture *old_texture;
CoglTexture *texture; CoglTexture *texture;
CoglMaterialWrapMode wrap_mode; CoglMaterialWrapMode wrap_mode;
@@ -64,10 +67,14 @@ struct _MetaScreenBackground
struct _MetaBackgroundActorPrivate struct _MetaBackgroundActorPrivate
{ {
MetaScreenBackground *background; MetaScreenBackground *background;
CoglPipeline *single_pipeline;
CoglPipeline *crossfade_pipeline;
CoglPipeline *pipeline; CoglPipeline *pipeline;
cairo_region_t *visible_region; cairo_region_t *visible_region;
float dim_factor; float dim_factor;
float crossfade_progress;
guint is_crossfading : 1;
}; };
enum enum
@@ -75,6 +82,7 @@ enum
PROP_0, PROP_0,
PROP_DIM_FACTOR, PROP_DIM_FACTOR,
PROP_CROSSFADE_PROGRESS,
PROP_LAST PROP_LAST
}; };
@@ -83,6 +91,7 @@ static GParamSpec *obj_props[PROP_LAST];
G_DEFINE_TYPE (MetaBackgroundActor, meta_background_actor, CLUTTER_TYPE_ACTOR); G_DEFINE_TYPE (MetaBackgroundActor, meta_background_actor, CLUTTER_TYPE_ACTOR);
static void clear_old_texture (MetaScreenBackground *background);
static void set_texture (MetaScreenBackground *background, static void set_texture (MetaScreenBackground *background,
CoglHandle texture); CoglHandle texture);
@@ -150,19 +159,86 @@ meta_screen_background_get (MetaScreen *screen)
} }
static void static void
update_wrap_mode_of_actor (MetaBackgroundActor *self) update_actor_pipeline (MetaBackgroundActor *self,
gboolean crossfade)
{ {
MetaBackgroundActorPrivate *priv = self->priv; MetaBackgroundActorPrivate *priv = self->priv;
cogl_pipeline_set_layer_wrap_mode (priv->pipeline, 0, priv->background->wrap_mode); if (crossfade)
{
priv->pipeline = priv->crossfade_pipeline;
priv->is_crossfading = TRUE;
cogl_pipeline_set_layer_texture (priv->pipeline, 0, priv->background->old_texture);
cogl_pipeline_set_layer_wrap_mode (priv->pipeline, 0, priv->background->wrap_mode);
cogl_pipeline_set_layer_texture (priv->pipeline, 1, priv->background->texture);
cogl_pipeline_set_layer_wrap_mode (priv->pipeline, 1, priv->background->wrap_mode);
}
else
{
priv->pipeline = priv->single_pipeline;
priv->is_crossfading = FALSE;
cogl_pipeline_set_layer_texture (priv->pipeline, 0, priv->background->texture);
cogl_pipeline_set_layer_wrap_mode (priv->pipeline, 0, priv->background->wrap_mode);
}
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
} }
static void static void
update_wrap_mode (MetaScreenBackground *background) crossfade_completed (ClutterTimeline *timeline,
MetaBackgroundActor *actor)
{
clear_old_texture (actor->priv->background);
update_actor_pipeline (actor, FALSE);
}
static void
clear_old_texture (MetaScreenBackground *background)
{
if (background->old_texture != COGL_INVALID_HANDLE)
{
cogl_handle_unref (background->old_texture);
background->old_texture = COGL_INVALID_HANDLE;
}
}
static void
set_texture (MetaScreenBackground *background,
CoglHandle texture)
{ {
GSList *l; GSList *l;
gboolean crossfade;
int width, height; int width, height;
if (background->old_texture != COGL_INVALID_HANDLE)
{
cogl_handle_unref (background->old_texture);
background->old_texture = COGL_INVALID_HANDLE;
}
if (texture != COGL_INVALID_HANDLE)
{
background->old_texture = background->texture;
background->texture = cogl_handle_ref (texture);
}
else if (background->texture != COGL_INVALID_HANDLE)
{
cogl_handle_unref (background->texture);
background->texture = COGL_INVALID_HANDLE;
}
if (texture != COGL_INVALID_HANDLE &&
background->old_texture != COGL_INVALID_HANDLE)
crossfade = TRUE;
else
crossfade = FALSE;
background->texture_width = cogl_texture_get_width (background->texture);
background->texture_height = cogl_texture_get_height (background->texture);
meta_screen_get_size (background->screen, &width, &height); meta_screen_get_size (background->screen, &width, &height);
/* We turn off repeating when we have a full-screen pixmap to keep from /* We turn off repeating when we have a full-screen pixmap to keep from
@@ -175,53 +251,55 @@ update_wrap_mode (MetaScreenBackground *background)
background->wrap_mode = COGL_MATERIAL_WRAP_MODE_REPEAT; background->wrap_mode = COGL_MATERIAL_WRAP_MODE_REPEAT;
for (l = background->actors; l; l = l->next) for (l = background->actors; l; l = l->next)
update_wrap_mode_of_actor (l->data);
}
static void
set_texture_on_actor (MetaBackgroundActor *self)
{
MetaBackgroundActorPrivate *priv = self->priv;
MetaDisplay *display = meta_screen_get_display (priv->background->screen);
/* This may trigger destruction of an old texture pixmap, which, if
* the underlying X pixmap is already gone has the tendency to trigger
* X errors inside DRI. For safety, trap errors */
meta_error_trap_push (display);
cogl_pipeline_set_layer_texture (priv->pipeline, 0, priv->background->texture);
meta_error_trap_pop (display);
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
}
static void
set_texture (MetaScreenBackground *background,
CoglHandle texture)
{
MetaDisplay *display = meta_screen_get_display (background->screen);
GSList *l;
/* This may trigger destruction of an old texture pixmap, which, if
* the underlying X pixmap is already gone has the tendency to trigger
* X errors inside DRI. For safety, trap errors */
meta_error_trap_push (display);
if (background->texture != COGL_INVALID_HANDLE)
{ {
cogl_handle_unref (background->texture); MetaBackgroundActor *actor = l->data;
background->texture = COGL_INVALID_HANDLE;
update_actor_pipeline (actor, crossfade);
if (crossfade)
{
ClutterTransition *transition;
ClutterInterval *interval;
interval = clutter_interval_new (G_TYPE_FLOAT, 0.0, 1.0);
transition = g_object_new (CLUTTER_TYPE_PROPERTY_TRANSITION,
"animatable", actor,
"property-name", "crossfade-progress",
"interval", interval,
"remove-on-complete", TRUE,
"duration", CROSSFADE_DURATION,
"progress-mode", CLUTTER_EASE_OUT_QUAD,
NULL);
g_signal_connect_object (transition, "completed",
G_CALLBACK (crossfade_completed), actor, 0);
clutter_actor_remove_transition (CLUTTER_ACTOR (actor), "crossfade");
clutter_actor_add_transition (CLUTTER_ACTOR (actor), "crossfade",
transition);
}
} }
meta_error_trap_pop (display); }
if (texture != COGL_INVALID_HANDLE) static void
background->texture = cogl_handle_ref (texture); update_wrap_mode (MetaScreenBackground *background)
{
GSList *l;
int width, height;
background->texture_width = cogl_texture_get_width (background->texture); meta_screen_get_size (background->screen, &width, &height);
background->texture_height = cogl_texture_get_height (background->texture);
for (l = background->actors; l; l = l->next) if (width == background->texture_width && height == background->texture_height)
set_texture_on_actor (l->data); background->wrap_mode = COGL_MATERIAL_WRAP_MODE_CLAMP_TO_EDGE;
else
background->wrap_mode = COGL_MATERIAL_WRAP_MODE_REPEAT;
update_wrap_mode (background); for (l = background->actors; l != NULL; l++)
{
MetaBackgroundActor *actor = l->data;
update_actor_pipeline (actor, actor->priv->is_crossfading);
}
} }
static inline void static inline void
@@ -248,7 +326,8 @@ meta_background_actor_dispose (GObject *object)
priv->background = NULL; priv->background = NULL;
} }
g_clear_pointer(&priv->pipeline, cogl_object_unref); g_clear_pointer(&priv->single_pipeline, cogl_object_unref);
g_clear_pointer(&priv->crossfade_pipeline, cogl_object_unref);
G_OBJECT_CLASS (meta_background_actor_parent_class)->dispose (object); G_OBJECT_CLASS (meta_background_actor_parent_class)->dispose (object);
} }
@@ -298,6 +377,7 @@ meta_background_actor_paint (ClutterActor *actor)
guint8 opacity = clutter_actor_get_paint_opacity (actor); guint8 opacity = clutter_actor_get_paint_opacity (actor);
guint8 color_component; guint8 color_component;
int width, height; int width, height;
CoglColor crossfade_color;
meta_background_ensure_rendered (priv->background); meta_background_ensure_rendered (priv->background);
@@ -311,6 +391,17 @@ meta_background_actor_paint (ClutterActor *actor)
color_component, color_component,
opacity); opacity);
if (priv->is_crossfading)
{
cogl_color_init_from_4f (&crossfade_color,
priv->crossfade_progress,
priv->crossfade_progress,
priv->crossfade_progress,
priv->crossfade_progress);
cogl_pipeline_set_layer_combine_constant (priv->pipeline,
1, &crossfade_color);
}
cogl_set_source (priv->pipeline); cogl_set_source (priv->pipeline);
if (priv->visible_region) if (priv->visible_region)
@@ -357,6 +448,22 @@ meta_background_actor_get_paint_volume (ClutterActor *actor,
return TRUE; return TRUE;
} }
static void
meta_background_actor_set_crossfade_progress (MetaBackgroundActor *self,
gfloat crossfade_progress)
{
MetaBackgroundActorPrivate *priv = self->priv;
if (priv->crossfade_progress == crossfade_progress)
return;
priv->crossfade_progress = crossfade_progress;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CROSSFADE_PROGRESS]);
}
static void static void
meta_background_actor_set_dim_factor (MetaBackgroundActor *self, meta_background_actor_set_dim_factor (MetaBackgroundActor *self,
gfloat dim_factor) gfloat dim_factor)
@@ -387,6 +494,8 @@ meta_background_actor_get_property(GObject *object,
case PROP_DIM_FACTOR: case PROP_DIM_FACTOR:
g_value_set_float (value, priv->dim_factor); g_value_set_float (value, priv->dim_factor);
break; break;
case PROP_CROSSFADE_PROGRESS:
g_value_set_float (value, priv->crossfade_progress);
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@@ -406,6 +515,9 @@ meta_background_actor_set_property(GObject *object,
case PROP_DIM_FACTOR: case PROP_DIM_FACTOR:
meta_background_actor_set_dim_factor (self, g_value_get_float (value)); meta_background_actor_set_dim_factor (self, g_value_get_float (value));
break; break;
case PROP_CROSSFADE_PROGRESS:
meta_background_actor_set_crossfade_progress (self, g_value_get_float (value));
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@@ -443,7 +555,18 @@ meta_background_actor_class_init (MetaBackgroundActorClass *klass)
1.0, 1.0,
G_PARAM_READWRITE); G_PARAM_READWRITE);
obj_props[PROP_DIM_FACTOR] = pspec; obj_props[PROP_DIM_FACTOR] = pspec;
g_object_class_install_property (object_class, PROP_DIM_FACTOR, pspec);
/**
* MetaBackgroundActor:crossfade-progress: (skip)
*/
pspec = g_param_spec_float ("crossfade-progress",
"", "",
0.0, 1.0,
1.0,
G_PARAM_READWRITE);
obj_props[PROP_CROSSFADE_PROGRESS] = pspec;
g_object_class_install_properties (object_class, PROP_LAST, obj_props);
} }
static void static void
@@ -479,11 +602,12 @@ meta_background_actor_new_for_screen (MetaScreen *screen)
priv->background = meta_screen_background_get (screen); priv->background = meta_screen_background_get (screen);
priv->background->actors = g_slist_prepend (priv->background->actors, self); priv->background->actors = g_slist_prepend (priv->background->actors, self);
/* A CoglMaterial and a CoglPipeline are the same thing */ priv->single_pipeline = meta_create_texture_material (priv->background->texture);
priv->pipeline = (CoglPipeline*) meta_create_texture_material (NULL); priv->crossfade_pipeline = meta_create_crossfade_material (priv->background->old_texture,
priv->background->texture);
set_texture_on_actor (self); if (priv->background->texture != COGL_INVALID_HANDLE)
update_wrap_mode_of_actor (self); update_actor_pipeline (self, FALSE);
return CLUTTER_ACTOR (self); return CLUTTER_ACTOR (self);
} }
@@ -520,7 +644,7 @@ on_background_drawn (GObject *object,
} }
else else
{ {
g_warning ("Failed to create background texture from pixmap: %s", g_warning ("Failed to render background: %s",
error->message); error->message);
g_error_free (error); g_error_free (error);
} }
@@ -650,9 +774,17 @@ meta_background_actor_add_glsl_snippet (MetaBackgroundActor *actor,
if (hook == META_SNIPPET_HOOK_VERTEX || if (hook == META_SNIPPET_HOOK_VERTEX ||
hook == META_SNIPPET_HOOK_FRAGMENT) hook == META_SNIPPET_HOOK_FRAGMENT)
cogl_pipeline_add_snippet (priv->pipeline, snippet); {
cogl_pipeline_add_snippet (priv->single_pipeline, snippet);
cogl_pipeline_add_snippet (priv->crossfade_pipeline, snippet);
}
else else
cogl_pipeline_add_layer_snippet (priv->pipeline, 0, snippet); {
/* In case of crossfading, apply the snippet only to the new texture.
We can't apply it to both because declarations would be doubled. */
cogl_pipeline_add_layer_snippet (priv->single_pipeline, 0, snippet);
cogl_pipeline_add_layer_snippet (priv->crossfade_pipeline, 1, snippet);
}
cogl_object_unref (snippet); cogl_object_unref (snippet);
} }
@@ -686,8 +818,12 @@ meta_background_actor_set_uniform_float (MetaBackgroundActor *actor,
priv = actor->priv; priv = actor->priv;
cogl_pipeline_set_uniform_float (priv->pipeline, cogl_pipeline_set_uniform_float (priv->single_pipeline,
cogl_pipeline_get_uniform_location (priv->pipeline, cogl_pipeline_get_uniform_location (priv->single_pipeline,
uniform_name),
n_components, count, uniform);
cogl_pipeline_set_uniform_float (priv->crossfade_pipeline,
cogl_pipeline_get_uniform_location (priv->crossfade_pipeline,
uniform_name), uniform_name),
n_components, count, uniform); n_components, count, uniform);
} }