MetaBackgroundActor: import background rendering using libgnome-desktop

Instead of relying on gnome-settings-daemon to set up _XROOTPMAP_ID
properly, do rendering in process and stuff the background into a normal
texture.
This will allow to do more fancy transitions in the future, using
Clutter to drive them.

https://bugzilla.gnome.org/show_bug.cgi?id=682427
This commit is contained in:
Giovanni Campagna 2012-11-10 20:49:23 +01:00
parent 579bf2105e
commit dbc9303efd
5 changed files with 246 additions and 109 deletions

View File

@ -75,6 +75,7 @@ MUTTER_PC_MODULES="
xcomposite >= 0.2 xfixes xrender xdamage xi >= 1.6.0
$CLUTTER_PACKAGE >= 1.13.5
cogl-1.0 >= 1.13.3
gnome-desktop-3.0
"
GLIB_GSETTINGS

View File

@ -48,6 +48,7 @@ libmutter_la_SOURCES = \
compositor/compositor-private.h \
compositor/meta-background-actor.c \
compositor/meta-background-actor-private.h \
compositor/meta-background.c \
compositor/meta-module.c \
compositor/meta-module.h \
compositor/meta-plugin.c \

View File

@ -3,6 +3,9 @@
#ifndef META_BACKGROUND_ACTOR_PRIVATE_H
#define META_BACKGROUND_ACTOR_PRIVATE_H
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnome-desktop/gnome-bg.h>
#include <meta/screen.h>
#include <meta/meta-background-actor.h>
@ -12,4 +15,14 @@ void meta_background_actor_set_visible_region (MetaBackgroundActor *self,
void meta_background_actor_update (MetaScreen *screen);
void meta_background_actor_screen_size_changed (MetaScreen *screen);
GTask *meta_background_draw_async (MetaScreen *screen,
GnomeBG *bg,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
CoglHandle meta_background_draw_finish (MetaScreen *screen,
GAsyncResult *result,
GError **error);
#endif /* META_BACKGROUND_ACTOR_PRIVATE_H */

View File

@ -29,8 +29,6 @@
#include <clutter/clutter.h>
#include <X11/Xatom.h>
#include "cogl-utils.h"
#include "compositor-private.h"
#include <meta/errors.h>
@ -51,11 +49,16 @@ struct _MetaScreenBackground
MetaScreen *screen;
GSList *actors;
GSettings *settings;
GnomeBG *bg;
GCancellable *cancellable;
float texture_width;
float texture_height;
CoglTexture *texture;
CoglMaterialWrapMode wrap_mode;
guint have_pixmap : 1;
GTask *rendering_task;
};
struct _MetaBackgroundActorPrivate
@ -82,30 +85,26 @@ G_DEFINE_TYPE (MetaBackgroundActor, meta_background_actor, CLUTTER_TYPE_ACTOR);
static void set_texture (MetaScreenBackground *background,
CoglHandle texture);
static void set_texture_to_stage_color (MetaScreenBackground *background);
static void
on_notify_stage_color (GObject *stage,
GParamSpec *pspec,
MetaScreenBackground *background)
{
if (!background->have_pixmap)
set_texture_to_stage_color (background);
}
static void
free_screen_background (MetaScreenBackground *background)
{
set_texture (background, COGL_INVALID_HANDLE);
if (background->screen != NULL)
{
ClutterActor *stage = meta_get_stage_for_screen (background->screen);
g_signal_handlers_disconnect_by_func (stage,
(gpointer) on_notify_stage_color,
background);
background->screen = NULL;
}
g_cancellable_cancel (background->cancellable);
g_object_unref (background->cancellable);
g_object_unref (background->bg);
g_object_unref (background->settings);
}
static void
on_settings_changed (GSettings *settings,
const char *key,
MetaScreenBackground *background)
{
gnome_bg_load_from_preferences (background->bg,
background->settings);
}
static MetaScreenBackground *
@ -116,18 +115,34 @@ meta_screen_background_get (MetaScreen *screen)
background = g_object_get_data (G_OBJECT (screen), "meta-screen-background");
if (background == NULL)
{
ClutterActor *stage;
background = g_new0 (MetaScreenBackground, 1);
background->screen = screen;
g_object_set_data_full (G_OBJECT (screen), "meta-screen-background",
background, (GDestroyNotify) free_screen_background);
stage = meta_get_stage_for_screen (screen);
g_signal_connect (stage, "notify::color",
G_CALLBACK (on_notify_stage_color), background);
background->settings = g_settings_new ("org.gnome.desktop.background");
g_signal_connect (background->settings, "changed",
G_CALLBACK (on_settings_changed), background);
background->bg = gnome_bg_new ();
g_signal_connect_object (background->bg, "transitioned",
G_CALLBACK (meta_background_actor_update),
screen, G_CONNECT_SWAPPED);
g_signal_connect_object (background->bg, "changed",
G_CALLBACK (meta_background_actor_update),
screen, G_CONNECT_SWAPPED);
background->texture_width = -1;
background->texture_height = -1;
background->wrap_mode = COGL_MATERIAL_WRAP_MODE_REPEAT;
on_settings_changed (background->settings, NULL, background);
/* GnomeBG has queued a changed event, but we need to start rendering now,
or it will be too late when we paint the first frame.
*/
g_object_set_data (G_OBJECT (background->bg), "ignore-pending-change", GINT_TO_POINTER (TRUE));
meta_background_actor_update (screen);
}
@ -209,30 +224,14 @@ set_texture (MetaScreenBackground *background,
update_wrap_mode (background);
}
/* Sets our pipeline to paint with a 1x1 texture of the stage's background
* color; doing this when we have no pixmap allows the application to turn
* off painting the stage. There might be a performance benefit to
* painting in this case with a solid color, but the normal solid color
* case is a 1x1 root pixmap, so we'd have to reverse-engineer that to
* actually pick up the (small?) performance win. This is just a fallback.
*/
static void
set_texture_to_stage_color (MetaScreenBackground *background)
static inline void
meta_background_ensure_rendered (MetaScreenBackground *background)
{
ClutterActor *stage = meta_get_stage_for_screen (background->screen);
ClutterColor color;
CoglHandle texture;
if (G_LIKELY (background->rendering_task == NULL ||
background->texture != COGL_INVALID_HANDLE))
return;
clutter_stage_get_color (CLUTTER_STAGE (stage), &color);
/* Slicing will prevent COGL from using hardware texturing for
* the tiled 1x1 pixmap, and will cause it to draw the window
* background in millions of separate 1x1 rectangles */
texture = meta_create_color_texture_4ub (color.red, color.green,
color.blue, 0xff,
COGL_TEXTURE_NO_SLICING);
set_texture (background, texture);
cogl_handle_unref (texture);
g_task_wait_sync (background->rendering_task);
}
static void
@ -300,6 +299,8 @@ meta_background_actor_paint (ClutterActor *actor)
guint8 color_component;
int width, height;
meta_background_ensure_rendered (priv->background);
meta_screen_get_size (priv->background->screen, &width, &height);
color_component = (int)(0.5 + opacity * priv->dim_factor);
@ -487,82 +488,72 @@ meta_background_actor_new_for_screen (MetaScreen *screen)
return CLUTTER_ACTOR (self);
}
static void
on_background_drawn (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
MetaScreen *screen = META_SCREEN (object);
MetaScreenBackground *background;
CoglHandle texture;
GError *error;
background = meta_screen_background_get (screen);
g_clear_object (&background->rendering_task);
g_clear_object (&background->cancellable);
error = NULL;
texture = meta_background_draw_finish (screen, result, &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_error_free (error);
return;
}
if (texture != COGL_INVALID_HANDLE)
{
set_texture (background, texture);
cogl_handle_unref (texture);
return;
}
else
{
g_warning ("Failed to create background texture from pixmap: %s",
error->message);
g_error_free (error);
}
}
/**
* meta_background_actor_update:
* @screen: a #MetaScreen
*
* Refetches the _XROOTPMAP_ID property for the root window and updates
* the contents of the background actor based on that. There's no attempt
* to optimize out pixmap values that don't change (since a root pixmap
* could be replaced by with another pixmap with the same ID under some
* circumstances), so this should only be called when we actually receive
* a PropertyNotify event for the property.
* Forces a redraw of the background. The redraw happens asynchronously in
* a thread, and the actual on screen change is therefore delayed until
* the redraw is finished.
*/
void
meta_background_actor_update (MetaScreen *screen)
{
MetaScreenBackground *background;
MetaDisplay *display;
MetaCompositor *compositor;
Atom type;
int format;
gulong nitems;
gulong bytes_after;
guchar *data;
Pixmap root_pixmap_id;
background = meta_screen_background_get (screen);
display = meta_screen_get_display (screen);
compositor = meta_display_get_compositor (display);
root_pixmap_id = None;
if (!XGetWindowProperty (meta_display_get_xdisplay (display),
meta_screen_get_xroot (screen),
compositor->atom_x_root_pixmap,
0, LONG_MAX,
False,
AnyPropertyType,
&type, &format, &nitems, &bytes_after, &data) &&
type != None)
{
/* Got a property. */
if (type == XA_PIXMAP && format == 32 && nitems == 1)
{
/* Was what we expected. */
root_pixmap_id = *(Pixmap *)data;
}
XFree(data);
}
if (root_pixmap_id != None)
if (background->cancellable)
{
CoglHandle texture;
CoglContext *ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
GError *error = NULL;
meta_error_trap_push (display);
texture = cogl_texture_pixmap_x11_new (ctx, root_pixmap_id, FALSE, &error);
meta_error_trap_pop (display);
if (texture != COGL_INVALID_HANDLE)
{
set_texture (background, texture);
cogl_handle_unref (texture);
background->have_pixmap = True;
return;
}
else
{
g_warning ("Failed to create background texture from pixmap: %s",
error->message);
g_error_free (error);
}
g_cancellable_cancel (background->cancellable);
g_object_unref (background->cancellable);
}
background->have_pixmap = False;
set_texture_to_stage_color (background);
g_clear_object (&background->rendering_task);
background->cancellable = g_cancellable_new ();
background->rendering_task = meta_background_draw_async (screen,
background->bg,
background->cancellable,
on_background_drawn, NULL);
}
/**

View File

@ -0,0 +1,131 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* meta-background.c: Utilities for drawing the background
*
* Copyright 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
*/
#include "config.h"
#include <gio/gio.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "meta-background-actor-private.h"
#include <core/screen-private.h>
typedef struct {
GnomeBG *bg;
GdkPixbuf *pixbuf;
GdkRectangle *monitors;
int num_monitors;
} TaskData;
static void
task_data_free (gpointer task_data)
{
TaskData *td = task_data;
g_object_unref (td->bg);
g_object_unref (td->pixbuf);
g_free (td->monitors);
g_slice_free (TaskData, td);
}
static void
meta_background_draw_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
TaskData *td = task_data;
gnome_bg_draw_areas (td->bg,
td->pixbuf,
TRUE,
td->monitors,
td->num_monitors);
g_task_return_pointer (task, g_object_ref (td->pixbuf), g_object_unref);
}
GTask *
meta_background_draw_async (MetaScreen *screen,
GnomeBG *bg,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
TaskData *td;
int i;
g_return_val_if_fail (META_IS_SCREEN (screen), NULL);
g_return_val_if_fail (GNOME_IS_BG (bg), NULL);
task = g_task_new (screen, cancellable, callback, user_data);
g_task_set_source_tag (task, meta_background_draw_async);
g_task_set_return_on_cancel (task, TRUE);
g_task_set_check_cancellable (task, TRUE);
td = g_slice_new (TaskData);
td->bg = g_object_ref (bg);
td->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
FALSE, /* has alpha */
8, /* bits per sample */
screen->rect.width,
screen->rect.height);
td->num_monitors = meta_screen_get_n_monitors (screen);
td->monitors = g_new (GdkRectangle, td->num_monitors);
for (i = 0; i < td->num_monitors; i++)
meta_screen_get_monitor_geometry (screen, i, (MetaRectangle*)(td->monitors + i));
g_task_set_task_data (task, td, task_data_free);
g_task_run_in_thread (task, meta_background_draw_thread);
return task;
}
CoglHandle
meta_background_draw_finish (MetaScreen *screen,
GAsyncResult *result,
GError **error)
{
GdkPixbuf *pixbuf;
CoglHandle handle;
pixbuf = g_task_propagate_pointer (G_TASK (result), error);
if (pixbuf == NULL)
return COGL_INVALID_HANDLE;
handle = cogl_texture_new_from_data (gdk_pixbuf_get_width (pixbuf),
gdk_pixbuf_get_height (pixbuf),
COGL_TEXTURE_NO_ATLAS | COGL_TEXTURE_NO_SLICING,
COGL_PIXEL_FORMAT_RGB_888,
COGL_PIXEL_FORMAT_RGB_888,
gdk_pixbuf_get_rowstride (pixbuf),
gdk_pixbuf_get_pixels (pixbuf));
g_object_unref (pixbuf);
return handle;
}