diff --git a/SOURCES/0001-Add-support-for-quad-buffer-stereo.patch b/SOURCES/0001-Add-support-for-quad-buffer-stereo.patch
index 7d150d2..1130e5d 100644
--- a/SOURCES/0001-Add-support-for-quad-buffer-stereo.patch
+++ b/SOURCES/0001-Add-support-for-quad-buffer-stereo.patch
@@ -1,4 +1,4 @@
-From 6108e0175932f74733c46ebe13db06998f26d4ba Mon Sep 17 00:00:00 2001
+From fd67e75df470b50510b68ccf0f52b0b98d05c63f Mon Sep 17 00:00:00 2001
 From: "Owen W. Taylor" <otaylor@fishsoup.net>
 Date: Thu, 8 May 2014 18:44:15 -0400
 Subject: [PATCH] Add support for quad-buffer stereo
diff --git a/SOURCES/0001-clutter-Backport-of-touch-mode.patch b/SOURCES/0001-clutter-Backport-of-touch-mode.patch
new file mode 100644
index 0000000..dff32fa
--- /dev/null
+++ b/SOURCES/0001-clutter-Backport-of-touch-mode.patch
@@ -0,0 +1,357 @@
+From 2a2e870c139e2130b00d582546616269bca27458 Mon Sep 17 00:00:00 2001
+From: Carlos Garnacho <carlosg@gnome.org>
+Date: Fri, 4 Sep 2020 17:11:36 +0200
+Subject: [PATCH] clutter: Backport of ::touch-mode
+
+In upstream/master this is a ClutterSeat readonly property. Add it to
+ClutterDeviceManager here, the mechanism and triggering is the same
+though.
+---
+ clutter/clutter/clutter-device-manager.c      |  24 +++
+ clutter/clutter/clutter-device-manager.h      |   2 +
+ .../evdev/clutter-device-manager-evdev.c      | 179 ++++++++++++++++++
+ 3 files changed, 205 insertions(+)
+
+diff --git a/clutter/clutter/clutter-device-manager.c b/clutter/clutter/clutter-device-manager.c
+index c676384..e1cc455 100644
+--- a/clutter/clutter/clutter-device-manager.c
++++ b/clutter/clutter/clutter-device-manager.c
+@@ -62,6 +62,7 @@ enum
+   PROP_0,
+ 
+   PROP_BACKEND,
++  PROP_TOUCH_MODE,
+ 
+   PROP_LAST
+ };
+@@ -108,6 +109,7 @@ clutter_device_manager_set_property (GObject      *gobject,
+       priv->backend = g_value_get_object (value);
+       break;
+ 
++    case PROP_TOUCH_MODE:
+     default:
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+     }
+@@ -127,6 +129,10 @@ clutter_device_manager_get_property (GObject    *gobject,
+       g_value_set_object (value, priv->backend);
+       break;
+ 
++    case PROP_TOUCH_MODE:
++      g_value_set_boolean (value, FALSE);
++      break;
++
+     default:
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+     }
+@@ -143,6 +149,12 @@ clutter_device_manager_class_init (ClutterDeviceManagerClass *klass)
+                          P_("The ClutterBackend of the device manager"),
+                          CLUTTER_TYPE_BACKEND,
+                          CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
++  obj_props[PROP_TOUCH_MODE] =
++    g_param_spec_boolean ("touch-mode",
++			  P_("Touch mode"),
++			  P_("Touch mode"),
++			  FALSE,
++			  CLUTTER_PARAM_READABLE);
+ 
+   gobject_class->set_property = clutter_device_manager_set_property;
+   gobject_class->get_property = clutter_device_manager_get_property;
+@@ -579,3 +591,15 @@ clutter_device_manager_get_kbd_a11y_settings (ClutterDeviceManager   *device_man
+ 
+   *settings = device_manager->priv->kbd_a11y_settings;
+ }
++
++gboolean
++clutter_device_manager_get_touch_mode (ClutterDeviceManager *device_manager)
++{
++  gboolean touch_mode;
++
++  g_return_val_if_fail (CLUTTER_IS_DEVICE_MANAGER (device_manager), FALSE);
++
++  g_object_get (G_OBJECT (device_manager), "touch-mode", &touch_mode, NULL);
++
++  return touch_mode;
++}
+diff --git a/clutter/clutter/clutter-device-manager.h b/clutter/clutter/clutter-device-manager.h
+index 1cbf030..a4a6271 100644
+--- a/clutter/clutter/clutter-device-manager.h
++++ b/clutter/clutter/clutter-device-manager.h
+@@ -155,6 +155,8 @@ void clutter_device_manager_set_kbd_a11y_settings (ClutterDeviceManager   *devic
+ CLUTTER_EXPORT
+ void clutter_device_manager_get_kbd_a11y_settings (ClutterDeviceManager   *device_manager,
+                                                    ClutterKbdA11ySettings *settings);
++CLUTTER_EXPORT
++gboolean clutter_device_manager_get_touch_mode (ClutterDeviceManager *device_manager);
+ 
+ G_END_DECLS
+ 
+diff --git a/clutter/clutter/evdev/clutter-device-manager-evdev.c b/clutter/clutter/evdev/clutter-device-manager-evdev.c
+index 84b0aad..78b5b64 100644
+--- a/clutter/clutter/evdev/clutter-device-manager-evdev.c
++++ b/clutter/clutter/evdev/clutter-device-manager-evdev.c
+@@ -108,6 +108,19 @@ struct _ClutterDeviceManagerEvdevPrivate
+ 
+   gint device_id_next;
+   GList *free_device_ids;
++
++  guint tablet_mode_switch_state : 1;
++  guint has_touchscreen          : 1;
++  guint has_tablet_switch        : 1;
++  guint has_pointer              : 1;
++  guint touch_mode               : 1;
++};
++
++enum
++{
++  PROP_0,
++  PROP_TOUCH_MODE,
++  N_PROPS
+ };
+ 
+ static void clutter_device_manager_evdev_event_extender_init (ClutterEventExtenderInterface *iface);
+@@ -765,6 +778,34 @@ clutter_event_source_free (ClutterEventSource *source)
+   g_source_unref (g_source);
+ }
+ 
++static void
++update_touch_mode (ClutterDeviceManagerEvdev *manager_evdev)
++{
++  ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv;
++  gboolean touch_mode;
++
++  /* No touch mode if we don't have a touchscreen, easy */
++  if (!priv->has_touchscreen)
++    touch_mode = FALSE;
++  /* If we have a tablet mode switch, honor it being unset */
++  else if (priv->has_tablet_switch && !priv->tablet_mode_switch_state)
++    touch_mode = FALSE;
++  /* If tablet mode is enabled, go for it */
++  else if (priv->has_tablet_switch && priv->tablet_mode_switch_state)
++    touch_mode = TRUE;
++  /* If there is no tablet mode switch (eg. kiosk machines),
++   * assume touch-mode is mutually exclusive with pointers.
++   */
++  else
++    touch_mode = !priv->has_pointer;
++
++  if (priv->touch_mode != touch_mode)
++    {
++      priv->touch_mode = touch_mode;
++      g_object_notify (G_OBJECT (manager_evdev), "touch-mode");
++    }
++}
++
+ static void
+ evdev_add_device (ClutterDeviceManagerEvdev *manager_evdev,
+                   struct libinput_device    *libinput_device)
+@@ -942,19 +983,81 @@ flush_event_queue (void)
+     }
+ }
+ 
++static gboolean
++has_touchscreen (ClutterDeviceManagerEvdev *manager_evdev)
++{
++  ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv;
++  GSList *l;
++
++  for (l = priv->devices; l; l = l->next)
++    {
++      ClutterInputDeviceType device_type;
++
++      device_type = clutter_input_device_get_device_type (l->data);
++
++      if (device_type == CLUTTER_TOUCHSCREEN_DEVICE)
++        return TRUE;
++    }
++
++  return FALSE;
++}
++
++static gboolean
++device_type_is_pointer (ClutterInputDeviceType device_type)
++{
++  return (device_type == CLUTTER_POINTER_DEVICE ||
++          device_type == CLUTTER_TOUCHPAD_DEVICE);
++}
++
++static gboolean
++has_pointer (ClutterDeviceManagerEvdev *manager_evdev)
++{
++  ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv;
++  GSList *l;
++
++  for (l = priv->devices; l; l = l->next)
++    {
++      ClutterInputDeviceType device_type;
++
++      device_type = clutter_input_device_get_device_type (l->data);
++
++      if (device_type_is_pointer (device_type))
++        return TRUE;
++    }
++
++  return FALSE;
++}
++
+ static gboolean
+ process_base_event (ClutterDeviceManagerEvdev *manager_evdev,
+                     struct libinput_event *event)
+ {
++  ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv;
+   ClutterInputDevice *device;
+   struct libinput_device *libinput_device;
+   gboolean handled = TRUE;
++  gboolean check_touch_mode;
+ 
+   switch (libinput_event_get_type (event))
+     {
+     case LIBINPUT_EVENT_DEVICE_ADDED:
+       libinput_device = libinput_event_get_device (event);
+ 
++      priv->has_touchscreen |=
++        libinput_device_has_capability (libinput_device, LIBINPUT_DEVICE_CAP_TOUCH);
++      priv->has_pointer |=
++        libinput_device_has_capability (libinput_device, LIBINPUT_DEVICE_CAP_POINTER);
++      check_touch_mode = priv->has_touchscreen | priv->has_pointer;
++
++      if (libinput_device_has_capability (libinput_device,
++                                          LIBINPUT_DEVICE_CAP_SWITCH) &&
++          libinput_device_switch_has_switch (libinput_device,
++                                             LIBINPUT_SWITCH_TABLET_MODE))
++        {
++          priv->has_tablet_switch = TRUE;
++          check_touch_mode = TRUE;
++        }
++
+       evdev_add_device (manager_evdev, libinput_device);
+       break;
+ 
+@@ -966,7 +1069,17 @@ process_base_event (ClutterDeviceManagerEvdev *manager_evdev,
+ 
+       libinput_device = libinput_event_get_device (event);
+ 
++      check_touch_mode =
++        libinput_device_has_capability (libinput_device, LIBINPUT_DEVICE_CAP_TOUCH);
+       device = libinput_device_get_user_data (libinput_device);
++      if (check_touch_mode)
++        priv->has_touchscreen = has_touchscreen (manager_evdev);
++      if (device_type_is_pointer (clutter_input_device_get_device_type (device)))
++        {
++          priv->has_pointer = has_pointer (manager_evdev);
++          check_touch_mode = TRUE;
++        }
++
+       evdev_remove_device (manager_evdev,
+                            CLUTTER_INPUT_DEVICE_EVDEV (device));
+       break;
+@@ -975,6 +1088,9 @@ process_base_event (ClutterDeviceManagerEvdev *manager_evdev,
+       handled = FALSE;
+     }
+ 
++  if (check_touch_mode)
++    update_touch_mode (manager_evdev);
++
+   return handled;
+ }
+ 
+@@ -1752,6 +1868,23 @@ process_device_event (ClutterDeviceManagerEvdev *manager_evdev,
+         notify_pad_ring (device, time, number, source, group, mode, angle);
+         break;
+       }
++    case LIBINPUT_EVENT_SWITCH_TOGGLE:
++      {
++        ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv;
++        struct libinput_event_switch *switch_event =
++          libinput_event_get_switch_event (event);
++        enum libinput_switch sw =
++          libinput_event_switch_get_switch (switch_event);
++        enum libinput_switch_state state =
++          libinput_event_switch_get_switch_state (switch_event);
++
++        if (sw == LIBINPUT_SWITCH_TABLET_MODE)
++          {
++            priv->tablet_mode_switch_state = (state == LIBINPUT_SWITCH_STATE_ON);
++            update_touch_mode (manager_evdev);
++          }
++        break;
++      }
+     default:
+       handled = FALSE;
+     }
+@@ -1967,6 +2100,10 @@ clutter_device_manager_evdev_constructed (GObject *gobject)
+ 
+   source = clutter_event_source_new (manager_evdev);
+   priv->event_source = source;
++
++  priv->has_touchscreen = has_touchscreen (manager_evdev);
++  priv->has_pointer = has_pointer (manager_evdev);
++  update_touch_mode (manager_evdev);
+ }
+ 
+ static void
+@@ -2001,6 +2138,43 @@ clutter_device_manager_evdev_dispose (GObject *object)
+   G_OBJECT_CLASS (clutter_device_manager_evdev_parent_class)->dispose (object);
+ }
+ 
++static void
++clutter_device_manager_evdev_set_property (GObject      *object,
++                                           guint         prop_id,
++                                           const GValue *value,
++                                           GParamSpec   *pspec)
++{
++  switch (prop_id)
++    {
++    case PROP_TOUCH_MODE:
++    default:
++      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++    }
++}
++
++static void
++clutter_device_manager_evdev_get_property (GObject    *object,
++                                           guint       prop_id,
++                                           GValue     *value,
++                                           GParamSpec *pspec)
++{
++  ClutterDeviceManagerEvdev *manager_evdev;
++  ClutterDeviceManagerEvdevPrivate *priv;
++
++  manager_evdev = CLUTTER_DEVICE_MANAGER_EVDEV (object);
++  priv = manager_evdev->priv;
++
++  switch (prop_id)
++    {
++    case PROP_TOUCH_MODE:
++      g_value_set_boolean (value, priv->touch_mode);
++      break;
++    default:
++      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++    }
++}
++
++
+ static void
+ clutter_device_manager_evdev_finalize (GObject *object)
+ {
+@@ -2036,6 +2210,8 @@ clutter_device_manager_evdev_class_init (ClutterDeviceManagerEvdevClass *klass)
+   gobject_class->constructed = clutter_device_manager_evdev_constructed;
+   gobject_class->finalize = clutter_device_manager_evdev_finalize;
+   gobject_class->dispose = clutter_device_manager_evdev_dispose;
++  gobject_class->set_property = clutter_device_manager_evdev_set_property;
++  gobject_class->get_property = clutter_device_manager_evdev_get_property;
+ 
+   manager_class = CLUTTER_DEVICE_MANAGER_CLASS (klass);
+   manager_class->add_device = clutter_device_manager_evdev_add_device;
+@@ -2047,6 +2223,9 @@ clutter_device_manager_evdev_class_init (ClutterDeviceManagerEvdevClass *klass)
+   manager_class->get_supported_virtual_device_types = clutter_device_manager_evdev_get_supported_virtual_device_types;
+   manager_class->compress_motion = clutter_device_manager_evdev_compress_motion;
+   manager_class->apply_kbd_a11y_settings = clutter_device_manager_evdev_apply_kbd_a11y_settings;
++
++  g_object_class_override_property (gobject_class, PROP_TOUCH_MODE,
++                                    "touch-mode");
+ }
+ 
+ static void
+-- 
+2.29.2
+
diff --git a/SOURCES/0001-clutter-stage-view-Hide-double-buffered-shadowfb-beh.patch b/SOURCES/0001-clutter-stage-view-Hide-double-buffered-shadowfb-beh.patch
new file mode 100644
index 0000000..6d9cffd
--- /dev/null
+++ b/SOURCES/0001-clutter-stage-view-Hide-double-buffered-shadowfb-beh.patch
@@ -0,0 +1,46 @@
+From 7bcc274dbc6cb75814cce3e5c2e7f45cf25b0538 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 9 Feb 2021 17:59:08 +0100
+Subject: [PATCH 1/2] clutter/stage-view: Hide double buffered shadowfb behind
+ envvar
+
+It still results in worse performance than a single FBO based shadowfb,
+so don't use it. It will need a new EGL extension for zero copy CPU
+memory based FBO to be feasable.
+---
+ clutter/clutter/clutter-stage-view.c | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 5e5966d06..ec18db7b8 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -282,6 +282,14 @@ init_dma_buf_shadowfbs (ClutterStageView  *view,
+   CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context);
+   CoglFramebuffer *initial_shadowfb;
+ 
++  if (g_strcmp0 (g_getenv ("MUTTER_DEBUG_ENABLE_DOUBLE_BUFFER_SHADOWFB"),
++                 "1") != 0)
++    {
++      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
++                   "Double buffered shadowfb not enabled");
++      return FALSE;
++    }
++
+   if (!cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE))
+     {
+       g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+@@ -390,8 +398,8 @@ init_shadowfb (ClutterStageView *view)
+       return;
+     }
+ 
+-  g_warning ("Failed to initialize double buffered shadow fb for %s: %s",
+-             priv->name, error->message);
++  g_debug ("Failed to initialize double buffered shadow fb for %s: %s",
++           priv->name, error->message);
+   g_clear_error (&error);
+ 
+   if (!init_fallback_shadowfb (view, cogl_context, width, height, &error))
+-- 
+2.29.2
+
diff --git a/SOURCES/0001-display-Make-check-alive-timeout-configureable.patch b/SOURCES/0001-display-Make-check-alive-timeout-configureable.patch
new file mode 100644
index 0000000..e0dfabe
--- /dev/null
+++ b/SOURCES/0001-display-Make-check-alive-timeout-configureable.patch
@@ -0,0 +1,248 @@
+From 7f6f326a1bb96aad0b7aea9c4d7e257bf53c026c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 21 Feb 2020 21:03:16 +0100
+Subject: [PATCH] display: Make check-alive timeout configureable
+
+The check-alive feature is there for the user to be able to terminate
+frozen applications more easily. However, sometimes applications are
+implemented in a way where they fail to be reply to ping requests in a
+timely manner, resulting in that, to the compositor, they are
+indistinguishable from clients that have frozen indefinitely.
+
+When using an application that has these issues, the GUI showed in
+response to the failure to respond to ping requests can become annoying,
+as it disrupts the visual presentation of the application.
+
+To allow users to work-around these issues, add a setting allowing them
+to configure the timeout waited until an application is considered
+frozen, or disabling the check completely.
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/1080
+---
+ data/org.gnome.mutter.gschema.xml.in | 10 ++++
+ src/core/display.c                   | 18 ++++----
+ src/core/prefs.c                     | 68 ++++++++++++++++++++++++++++
+ src/meta/prefs.h                     |  3 ++
+ 4 files changed, 90 insertions(+), 9 deletions(-)
+
+diff --git a/data/org.gnome.mutter.gschema.xml.in b/data/org.gnome.mutter.gschema.xml.in
+index 6cbd9c1b5..4d37b1488 100644
+--- a/data/org.gnome.mutter.gschema.xml.in
++++ b/data/org.gnome.mutter.gschema.xml.in
+@@ -123,6 +123,16 @@
+       </description>
+     </key>
+ 
++    <key name="check-alive-timeout" type="u">
++      <default>5000</default>
++      <summary>Timeout for check-alive ping</summary>
++      <description>
++        Number of milliseconds a client has to respond to a ping request in
++        order to not be detected as frozen. Using 0 will disable the alive check
++        completely.
++      </description>
++    </key>
++
+     <child name="keybindings" schema="org.gnome.mutter.keybindings"/>
+ 
+   </schema>
+diff --git a/src/core/display.c b/src/core/display.c
+index eb7dc43b6..c30a03385 100644
+--- a/src/core/display.c
++++ b/src/core/display.c
+@@ -1923,12 +1923,6 @@ meta_set_syncing (gboolean setting)
+     }
+ }
+ 
+-/*
+- * How long, in milliseconds, we should wait after pinging a window
+- * before deciding it's not going to get back to us.
+- */
+-#define PING_TIMEOUT_DELAY 5000
+-
+ /**
+  * meta_display_ping_timeout:
+  * @data: All the information about this ping. It is a #MetaPingData
+@@ -1986,6 +1980,11 @@ meta_display_ping_window (MetaWindow *window,
+ {
+   MetaDisplay *display = window->display;
+   MetaPingData *ping_data;
++  unsigned int check_alive_timeout;
++
++  check_alive_timeout = meta_prefs_get_check_alive_timeout ();
++  if (check_alive_timeout == 0)
++    return;
+ 
+   if (serial == 0)
+     {
+@@ -1999,9 +1998,10 @@ meta_display_ping_window (MetaWindow *window,
+   ping_data = g_new (MetaPingData, 1);
+   ping_data->window = window;
+   ping_data->serial = serial;
+-  ping_data->ping_timeout_id = g_timeout_add (PING_TIMEOUT_DELAY,
+-					      meta_display_ping_timeout,
+-					      ping_data);
++  ping_data->ping_timeout_id =
++    g_timeout_add (check_alive_timeout,
++                   meta_display_ping_timeout,
++                   ping_data);
+   g_source_set_name_by_id (ping_data->ping_timeout_id, "[mutter] meta_display_ping_timeout");
+ 
+   display->pending_pings = g_slist_prepend (display->pending_pings, ping_data);
+diff --git a/src/core/prefs.c b/src/core/prefs.c
+index 3f0db8afc..4892406ce 100644
+--- a/src/core/prefs.c
++++ b/src/core/prefs.c
+@@ -99,6 +99,7 @@ static gboolean bell_is_visible = FALSE;
+ static gboolean bell_is_audible = TRUE;
+ static gboolean gnome_accessibility = FALSE;
+ static gboolean gnome_animations = TRUE;
++static unsigned int check_alive_timeout = 5000;
+ static char *cursor_theme = NULL;
+ /* cursor_size will, when running as an X11 compositing window manager, be the
+  * actual cursor size, multiplied with the global window scaling factor. On
+@@ -213,6 +214,12 @@ typedef struct
+   gint *target;
+ } MetaIntPreference;
+ 
++typedef struct
++{
++  MetaBasePreference base;
++  unsigned int *target;
++} MetaUintPreference;
++
+ 
+ /* All preferences that are not keybindings must be listed here,
+  * plus in the GSettings schemas and the MetaPreference enum.
+@@ -491,6 +498,18 @@ static MetaIntPreference preferences_int[] =
+     { { NULL, 0, 0 }, NULL },
+   };
+ 
++static MetaUintPreference preferences_uint[] =
++  {
++    {
++      { "check-alive-timeout",
++        SCHEMA_MUTTER,
++        META_PREF_CHECK_ALIVE_TIMEOUT,
++      },
++      &check_alive_timeout,
++    },
++    { { NULL, 0, 0 }, NULL },
++  };
++
+ static void
+ handle_preference_init_enum (void)
+ {
+@@ -613,6 +632,21 @@ handle_preference_init_int (void)
+     }
+ }
+ 
++static void
++handle_preference_init_uint (void)
++{
++  MetaUintPreference *cursor = preferences_uint;
++
++  while (cursor->base.key != NULL)
++    {
++      if (cursor->target)
++        *cursor->target = g_settings_get_uint (SETTINGS (cursor->base.schema),
++                                               cursor->base.key);
++
++      ++cursor;
++    }
++}
++
+ static void
+ handle_preference_update_enum (GSettings *settings,
+                                gchar *key)
+@@ -788,6 +822,28 @@ handle_preference_update_int (GSettings *settings,
+     }
+ }
+ 
++static void
++handle_preference_update_uint (GSettings *settings,
++                               char *key)
++{
++  MetaUintPreference *cursor = preferences_uint;
++  unsigned int new_value;
++
++  while (cursor->base.key && strcmp (key, cursor->base.key) != 0)
++    ++cursor;
++
++  if (!cursor->base.key || !cursor->target)
++    return;
++
++  new_value = g_settings_get_uint (SETTINGS (cursor->base.schema), key);
++
++  if (*cursor->target != new_value)
++    {
++      *cursor->target = new_value;
++      queue_changed (cursor->base.pref);
++    }
++}
++
+ 
+ /****************************************************************************/
+ /* Listeners.                                                               */
+@@ -964,6 +1020,7 @@ meta_prefs_init (void)
+   handle_preference_init_string ();
+   handle_preference_init_string_array ();
+   handle_preference_init_int ();
++  handle_preference_init_uint ();
+ 
+   init_bindings ();
+ }
+@@ -1017,6 +1074,8 @@ settings_changed (GSettings *settings,
+     handle_preference_update_bool (settings, key);
+   else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32))
+     handle_preference_update_int (settings, key);
++  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32))
++    handle_preference_update_uint (settings, key);
+   else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING_ARRAY))
+     handle_preference_update_string_array (settings, key);
+   else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
+@@ -1640,6 +1699,9 @@ meta_preference_to_string (MetaPreference pref)
+ 
+     case META_PREF_AUTO_MAXIMIZE:
+       return "AUTO_MAXIMIZE";
++
++    case META_PREF_CHECK_ALIVE_TIMEOUT:
++      return "CHECK_ALIVE_TIMEOUT";
+     }
+ 
+   return "(unknown)";
+@@ -1966,6 +2028,12 @@ meta_prefs_get_overlay_binding (MetaKeyCombo *combo)
+   *combo = overlay_key_combo;
+ }
+ 
++unsigned int
++meta_prefs_get_check_alive_timeout (void)
++{
++  return check_alive_timeout;
++}
++
+ const char *
+ meta_prefs_get_iso_next_group_option (void)
+ {
+diff --git a/src/meta/prefs.h b/src/meta/prefs.h
+index 9664b5c07..f42d1c63c 100644
+--- a/src/meta/prefs.h
++++ b/src/meta/prefs.h
+@@ -103,6 +103,7 @@ typedef enum
+   META_PREF_AUTO_MAXIMIZE,
+   META_PREF_CENTER_NEW_WINDOWS,
+   META_PREF_DRAG_THRESHOLD,
++  META_PREF_CHECK_ALIVE_TIMEOUT,
+ } MetaPreference;
+ 
+ typedef void (* MetaPrefsChangedFunc) (MetaPreference pref,
+@@ -475,4 +476,6 @@ gboolean           meta_prefs_bell_is_audible      (void);
+ META_EXPORT
+ GDesktopVisualBellType meta_prefs_get_visual_bell_type (void);
+ 
++unsigned int meta_prefs_get_check_alive_timeout (void);
++
+ #endif
+-- 
+2.28.0
+
diff --git a/SOURCES/0001-monitor-config-manager-Handle-multiple-builtin-panel.patch b/SOURCES/0001-monitor-config-manager-Handle-multiple-builtin-panel.patch
new file mode 100644
index 0000000..73fcb7b
--- /dev/null
+++ b/SOURCES/0001-monitor-config-manager-Handle-multiple-builtin-panel.patch
@@ -0,0 +1,211 @@
+From 19024a5b2eff02b22cdb3fc90142f522dd361996 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 27 Nov 2020 09:03:38 +0100
+Subject: [PATCH] monitor-config-manager: Handle multiple builtin panels
+ gracefully
+
+While multiple built-in panels isn't actually supported in any
+meaningful manner, if we would ever end up with such a situation, e.g.
+due to kernel bugs[0], we shouldn't crash when trying to set an
+'external only' without any external monitors.
+
+While we could handle this with more degraded functionality (e.g. don't
+support the 'switch' method of monitor configuration at all), handle it
+by simply not trying to switch to external-only when there are no,
+according to the kernel, external monitors available. This would e.g.
+still allow betwene 'mirror-all', and 'linear' switches.
+
+The crash itself was disguised as an arbitrary X11 BadValue error, due
+to mutter trying to resize the root window to 0x0, as the monitor
+configuration that was applied consisted of zero logical monitors, thus
+was effectively empty.
+
+[0] https://bugzilla.redhat.com/show_bug.cgi?id=1896904
+
+Related: https://bugzilla.redhat.com/show_bug.cgi?id=1899260
+Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1607>
+---
+ src/backends/meta-monitor-config-manager.c |   3 +
+ src/tests/monitor-unit-tests.c             | 145 +++++++++++++++++++++
+ 2 files changed, 148 insertions(+)
+
+diff --git a/src/backends/meta-monitor-config-manager.c b/src/backends/meta-monitor-config-manager.c
+index bc1a39db8..d62bad52d 100644
+--- a/src/backends/meta-monitor-config-manager.c
++++ b/src/backends/meta-monitor-config-manager.c
+@@ -1157,6 +1157,9 @@ create_for_switch_config_external (MetaMonitorConfigManager *config_manager)
+       x += logical_monitor_config->layout.width;
+     }
+ 
++  if (!logical_monitor_configs)
++    return NULL;
++
+   return meta_monitors_config_new (monitor_manager,
+                                    logical_monitor_configs,
+                                    layout_mode,
+diff --git a/src/tests/monitor-unit-tests.c b/src/tests/monitor-unit-tests.c
+index f47544b03..725f84173 100644
+--- a/src/tests/monitor-unit-tests.c
++++ b/src/tests/monitor-unit-tests.c
+@@ -3175,6 +3175,149 @@ meta_test_monitor_non_upright_panel (void)
+   check_monitor_configuration (&test_case);
+ }
+ 
++static void
++meta_test_monitor_switch_external_without_external (void)
++{
++  MonitorTestCase test_case = {
++    .setup = {
++      .modes = {
++        {
++          .width = 1024,
++          .height = 768,
++          .refresh_rate = 60.0
++        }
++      },
++      .n_modes = 1,
++      .outputs = {
++        {
++          .crtc = 0,
++          .modes = { 0 },
++          .n_modes = 1,
++          .preferred_mode = 0,
++          .possible_crtcs = { 0 },
++          .n_possible_crtcs = 1,
++          .width_mm = 222,
++          .height_mm = 125,
++          .is_laptop_panel = TRUE
++        },
++        {
++          .crtc = 1,
++          .modes = { 0 },
++          .n_modes = 1,
++          .preferred_mode = 0,
++          .possible_crtcs = { 1 },
++          .n_possible_crtcs = 1,
++          .width_mm = 222,
++          .height_mm = 125,
++          .is_laptop_panel = TRUE
++        }
++      },
++      .n_outputs = 2,
++      .crtcs = {
++        {
++          .current_mode = 0
++        },
++        {
++          .current_mode = 0
++        }
++      },
++      .n_crtcs = 2
++    },
++
++    .expect = {
++      .monitors = {
++        {
++          .outputs = { 0 },
++          .n_outputs = 1,
++          .modes = {
++            {
++              .width = 1024,
++              .height = 768,
++              .refresh_rate = 60.0,
++              .crtc_modes = {
++                {
++                  .output = 0,
++                  .crtc_mode = 0
++                }
++              }
++            }
++          },
++          .n_modes = 1,
++          .current_mode = 0,
++          .width_mm = 222,
++          .height_mm = 125
++        },
++        {
++          .outputs = { 1 },
++          .n_outputs = 1,
++          .modes = {
++            {
++              .width = 1024,
++              .height = 768,
++              .refresh_rate = 60.0,
++              .crtc_modes = {
++                {
++                  .output = 1,
++                  .crtc_mode = 0
++                }
++              }
++            }
++          },
++          .n_modes = 1,
++          .current_mode = 0,
++          .width_mm = 222,
++          .height_mm = 125
++        }
++      },
++      .n_monitors = 2,
++      .logical_monitors = {
++        {
++          .monitors = { 0 },
++          .n_monitors = 1,
++          .layout = { .x = 0, .y = 0, .width = 1024, .height = 768 },
++          .scale = 1
++        },
++        {
++          .monitors = { 1 },
++          .n_monitors = 1,
++          .layout = { .x = 1024, .y = 0, .width = 1024, .height = 768 },
++          .scale = 1
++        }
++      },
++      .n_logical_monitors = 2,
++      .primary_logical_monitor = 0,
++      .n_outputs = 2,
++      .crtcs = {
++        {
++          .current_mode = 0,
++        },
++        {
++          .current_mode = 0,
++        },
++      },
++      .n_crtcs = 2,
++      .n_tiled_monitors = 0,
++      .screen_width = 2048,
++      .screen_height = 768
++    }
++  };
++  MetaMonitorTestSetup *test_setup;
++  MetaBackend *backend = meta_get_backend ();
++  MetaMonitorManager *monitor_manager =
++    meta_backend_get_monitor_manager (backend);
++
++  test_setup = create_monitor_test_setup (&test_case.setup,
++                                          MONITOR_TEST_FLAG_NO_STORED);
++  emulate_hotplug (test_setup);
++  check_monitor_configuration (&test_case);
++
++  meta_monitor_manager_switch_config (monitor_manager,
++                                      META_MONITOR_SWITCH_CONFIG_EXTERNAL);
++  check_monitor_configuration (&test_case);
++
++  check_monitor_test_clients_state ();
++}
++
+ static void
+ meta_test_monitor_custom_vertical_config (void)
+ {
+@@ -5969,6 +6112,8 @@ init_monitor_tests (void)
+                     meta_test_monitor_preferred_non_first_mode);
+   add_monitor_test ("/backends/monitor/non-upright-panel",
+                     meta_test_monitor_non_upright_panel);
++  add_monitor_test ("/backends/monitor/switch-external-without-external",
++                    meta_test_monitor_switch_external_without_external);
+ 
+   add_monitor_test ("/backends/monitor/custom/vertical-config",
+                     meta_test_monitor_custom_vertical_config);
+-- 
+2.29.2
+
diff --git a/SOURCES/0001-xwayland-Don-t-spew-warnings-when-looking-for-X11-di.patch b/SOURCES/0001-xwayland-Don-t-spew-warnings-when-looking-for-X11-di.patch
new file mode 100644
index 0000000..b1bcbbe
--- /dev/null
+++ b/SOURCES/0001-xwayland-Don-t-spew-warnings-when-looking-for-X11-di.patch
@@ -0,0 +1,304 @@
+From d366b2bc4e89ed5807f0221afc25e66cb3d289ed Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 9 Dec 2020 11:23:37 +0100
+Subject: [PATCH 1/2] xwayland: Don't spew warnings when looking for X11
+ displays
+
+It's not important, so only show it when doing MUTTER_DEBUG=wayland.
+Instead report what display numbers were eventually found.
+---
+ src/wayland/meta-xwayland.c | 123 +++++++++++++++++++++++++++---------
+ 1 file changed, 92 insertions(+), 31 deletions(-)
+
+diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c
+index 15c85df69..699d5561c 100644
+--- a/src/wayland/meta-xwayland.c
++++ b/src/wayland/meta-xwayland.c
+@@ -146,9 +146,10 @@ meta_xwayland_is_xwayland_surface (MetaWaylandSurface *surface)
+ }
+ 
+ static gboolean
+-try_display (int    display,
+-             char **filename_out,
+-             int   *fd_out)
++try_display (int      display,
++             char   **filename_out,
++             int     *fd_out,
++             GError **error)
+ {
+   gboolean ret = FALSE;
+   char *filename;
+@@ -164,11 +165,32 @@ try_display (int    display,
+       char pid[11];
+       char *end;
+       pid_t other;
++      int read_bytes;
+ 
+       fd = open (filename, O_CLOEXEC, O_RDONLY);
+-      if (fd < 0 || read (fd, pid, 11) != 11)
++      if (fd < 0)
+         {
+-          g_warning ("can't read lock file %s: %m", filename);
++          g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                       "Failed to open lock file %s: %s",
++                       filename, g_strerror (errno));
++          goto out;
++        }
++
++      read_bytes = read (fd, pid, 11);
++      if (read_bytes != 11)
++        {
++          if (read_bytes < 0)
++            {
++              g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                           "Failed to read lock file %s: %s",
++                           filename, g_strerror (errno));
++            }
++          else
++            {
++              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
++                           "Failed to read lock file %s",
++                           filename);
++            }
+           goto out;
+         }
+       close (fd);
+@@ -178,7 +200,8 @@ try_display (int    display,
+       other = strtol (pid, &end, 0);
+       if (end != pid + 10)
+         {
+-          g_warning ("can't parse lock file %s", filename);
++          g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
++                       "Can't parse lock file %s", filename);
+           goto out;
+         }
+ 
+@@ -187,18 +210,23 @@ try_display (int    display,
+           /* Process is dead. Try unlinking the lock file and trying again. */
+           if (unlink (filename) < 0)
+             {
+-              g_warning ("failed to unlink stale lock file %s: %m", filename);
++              g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                           "Failed to unlink stale lock file %s: %m", filename);
+               goto out;
+             }
+ 
+           goto again;
+         }
+ 
++      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
++                   "Lock file %s already occupied", filename);
+       goto out;
+     }
+   else if (fd < 0)
+     {
+-      g_warning ("failed to create lock file %s: %m", filename);
++      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
++                   "Failed to create lock file %s: %s",
++                   filename, g_strerror (errno));
+       goto out;
+     }
+ 
+@@ -223,24 +251,34 @@ try_display (int    display,
+ }
+ 
+ static char *
+-create_lock_file (int display, int *display_out)
++create_lock_file (int      display,
++                  int     *display_out,
++                  GError **error)
+ {
++  g_autoptr (GError) local_error = NULL;
+   char *filename;
+   int fd;
+-
+   char pid[12];
+   int size;
+   int number_of_tries = 0;
+ 
+-  while (!try_display (display, &filename, &fd))
++  while (!try_display (display, &filename, &fd, &local_error))
+     {
++      meta_verbose ("Failed to open display %d: %s\n",
++                    display, local_error->message);
++      g_clear_error (&local_error);
++
+       display++;
+       number_of_tries++;
+ 
+       /* If we can't get a display after 50 times, then something's wrong. Just
+        * abort in this case. */
+       if (number_of_tries >= 50)
+-        return NULL;
++        {
++          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
++                       "Tried to bind 50 display numbers, giving up");
++          return NULL;
++        }
+     }
+ 
+   /* Subtle detail: we use the pid of the wayland compositor, not the xserver
+@@ -248,11 +286,22 @@ create_lock_file (int display, int *display_out)
+    * it _would've_ written without either the NUL or the size clamping, hence
+    * the disparity in size. */
+   size = snprintf (pid, 12, "%10d\n", getpid ());
++  errno = 0;
+   if (size != 11 || write (fd, pid, 11) != 11)
+     {
++      if (errno != 0)
++        {
++          g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                       "Failed to write pid to lock file: %s",
++                       g_strerror (errno));
++        }
++      else
++        {
++          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
++                       "Failed to write pid to lock file");
++        }
+       unlink (filename);
+       close (fd);
+-      g_warning ("failed to write pid to lock file %s", filename);
+       g_free (filename);
+       return NULL;
+     }
+@@ -264,8 +313,8 @@ create_lock_file (int display, int *display_out)
+ }
+ 
+ static int
+-bind_to_abstract_socket (int       display,
+-                         gboolean *fatal)
++bind_to_abstract_socket (int        display,
++                         GError   **error)
+ {
+   struct sockaddr_un addr;
+   socklen_t size, name_size;
+@@ -274,8 +323,8 @@ bind_to_abstract_socket (int       display,
+   fd = socket (PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
+   if (fd < 0)
+     {
+-      *fatal = TRUE;
+-      g_warning ("Failed to create socket: %m");
++      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                   "Failed to create socket: %s", g_strerror (errno));
+       return -1;
+     }
+ 
+@@ -285,17 +334,18 @@ bind_to_abstract_socket (int       display,
+   size = offsetof (struct sockaddr_un, sun_path) + name_size;
+   if (bind (fd, (struct sockaddr *) &addr, size) < 0)
+     {
+-      *fatal = errno != EADDRINUSE;
+-      g_warning ("failed to bind to @%s: %m", addr.sun_path + 1);
++      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                   "Failed to bind to @%s: %s",
++                   addr.sun_path + 1, g_strerror (errno));
+       close (fd);
+       return -1;
+     }
+ 
+   if (listen (fd, 1) < 0)
+     {
+-      *fatal = errno != EADDRINUSE;
+-      g_warning ("Failed to listen on abstract socket @%s: %m",
+-                 addr.sun_path + 1);
++      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                   "Failed to listen on abstract socket @%s: %s",
++                   addr.sun_path + 1, g_strerror (errno));
+       close (fd);
+       return -1;
+     }
+@@ -304,7 +354,8 @@ bind_to_abstract_socket (int       display,
+ }
+ 
+ static int
+-bind_to_unix_socket (int display)
++bind_to_unix_socket (int      display,
++                     GError **error)
+ {
+   struct sockaddr_un addr;
+   socklen_t size, name_size;
+@@ -321,13 +372,18 @@ bind_to_unix_socket (int display)
+   unlink (addr.sun_path);
+   if (bind (fd, (struct sockaddr *) &addr, size) < 0)
+     {
+-      g_warning ("failed to bind to %s: %m\n", addr.sun_path);
++      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                   "Failed to bind to %s: %s",
++                   addr.sun_path, g_strerror (errno));
+       close (fd);
+       return -1;
+     }
+ 
+   if (listen (fd, 1) < 0)
+     {
++      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                   "Failed to listen on %s: %s",
++                   addr.sun_path, g_strerror (errno));
+       unlink (addr.sun_path);
+       close (fd);
+       return -1;
+@@ -385,7 +441,6 @@ choose_xdisplay (MetaXWaylandManager *manager)
+ {
+   int display = 0;
+   char *lock_file = NULL;
+-  gboolean fatal = FALSE;
+ 
+   if (display_number_override != -1)
+     display = display_number_override;
+@@ -394,33 +449,37 @@ choose_xdisplay (MetaXWaylandManager *manager)
+ 
+   do
+     {
+-      lock_file = create_lock_file (display, &display);
++      g_autoptr (GError) error = NULL;
++
++      lock_file = create_lock_file (display, &display, &error);
+       if (!lock_file)
+         {
+-          g_warning ("Failed to create an X lock file");
++          g_warning ("Failed to create an X lock file: %s", error->message);
+           return FALSE;
+         }
+ 
+-      manager->abstract_fd = bind_to_abstract_socket (display, &fatal);
++      manager->abstract_fd = bind_to_abstract_socket (display, &error);
+       if (manager->abstract_fd < 0)
+         {
+           unlink (lock_file);
+ 
+-          if (!fatal)
++          if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE))
+             {
++              meta_verbose ("Failed to bind abstract socket: %s\n", error->message);
+               display++;
+               continue;
+             }
+           else
+             {
+-              g_warning ("Failed to bind abstract socket");
++              g_warning ("Failed to bind abstract socket: %s", error->message);
+               return FALSE;
+             }
+         }
+ 
+-      manager->unix_fd = bind_to_unix_socket (display);
++      manager->unix_fd = bind_to_unix_socket (display, &error);
+       if (manager->unix_fd < 0)
+         {
++          meta_verbose ("Failed to bind unix socket: %s\n", error->message);
+           unlink (lock_file);
+           close (manager->abstract_fd);
+           display++;
+@@ -435,6 +494,8 @@ choose_xdisplay (MetaXWaylandManager *manager)
+   manager->display_name = g_strdup_printf (":%d", manager->display_index);
+   manager->lock_file = lock_file;
+ 
++  g_message ("Using X11 display %s for Xwayland", manager->display_name);
++
+   return TRUE;
+ }
+ 
+-- 
+2.29.2
+
diff --git a/SOURCES/0002-cogl-gpu-info-Fix-software-acceleration-detection.patch b/SOURCES/0002-cogl-gpu-info-Fix-software-acceleration-detection.patch
new file mode 100644
index 0000000..106ebaa
--- /dev/null
+++ b/SOURCES/0002-cogl-gpu-info-Fix-software-acceleration-detection.patch
@@ -0,0 +1,27 @@
+From 03c30b76bae4c2e3f51a6689ebb7c0c60bd7b29a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 9 Feb 2021 18:00:26 +0100
+Subject: [PATCH 2/2] cogl/gpu-info: Fix software acceleration detection
+
+The string used to match mesa changed; update to fix software rendering
+detection.
+---
+ cogl/cogl/cogl-gpu-info.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/cogl/cogl/cogl-gpu-info.c b/cogl/cogl/cogl-gpu-info.c
+index f44319e96..c1817b3b0 100644
+--- a/cogl/cogl/cogl-gpu-info.c
++++ b/cogl/cogl/cogl-gpu-info.c
+@@ -192,6 +192,8 @@ check_mesa_vendor (const CoglGpuInfoStrings *strings)
+     return TRUE;
+   else if (strcmp (strings->vendor_string, "Mesa Project") == 0)
+     return TRUE;
++  else if (strcmp (strings->vendor_string, "Mesa/X.org") == 0)
++    return TRUE;
+ 
+   return FALSE;
+ }
+-- 
+2.29.2
+
diff --git a/SOURCES/0002-xwayland-Make-sure-tmp-.X11-unix-exists.patch b/SOURCES/0002-xwayland-Make-sure-tmp-.X11-unix-exists.patch
new file mode 100644
index 0000000..15d829c
--- /dev/null
+++ b/SOURCES/0002-xwayland-Make-sure-tmp-.X11-unix-exists.patch
@@ -0,0 +1,90 @@
+From 56c2e4efdcef14531dcf752e89117d22a21ec8ad Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 9 Dec 2020 15:18:29 +0100
+Subject: [PATCH 2/2] xwayland: Make sure /tmp/.X11-unix/ exists
+
+When we're running under a polyinstantiated SELinux environment, we'll
+likely start with an isolated and empty /tmp, meannig no /tmp/.X11-unix
+directory to add things to. To make it possible to still function in
+this kind of setup, make sure said directory exists.
+---
+ src/wayland/meta-xwayland.c | 30 ++++++++++++++++++++++++++++--
+ 1 file changed, 28 insertions(+), 2 deletions(-)
+
+diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c
+index 699d5561c..f3df9766e 100644
+--- a/src/wayland/meta-xwayland.c
++++ b/src/wayland/meta-xwayland.c
+@@ -30,6 +30,7 @@
+ #include <glib-unix.h>
+ #include <glib.h>
+ #include <sys/socket.h>
++#include <sys/stat.h>
+ #include <sys/un.h>
+ 
+ #include "compositor/meta-surface-actor-wayland.h"
+@@ -436,9 +437,27 @@ meta_xwayland_override_display_number (int number)
+   display_number_override = number;
+ }
+ 
++static gboolean
++ensure_x11_unix_dir (GError **error)
++{
++  if (mkdir ("/tmp/.X11-unix", 01777) != 0)
++    {
++      if (errno == EEXIST)
++        return TRUE;
++
++      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                   "Failed to create directory \"/tmp/.X11-unix\": %s",
++                   g_strerror (errno));
++      return FALSE;
++    }
++
++  return TRUE;
++}
++
+ static gboolean
+ choose_xdisplay (MetaXWaylandManager *manager)
+ {
++  g_autoptr (GError) error = NULL;
+   int display = 0;
+   char *lock_file = NULL;
+ 
+@@ -447,10 +466,15 @@ choose_xdisplay (MetaXWaylandManager *manager)
+   else if (g_getenv ("RUNNING_UNDER_GDM"))
+     display = 1024;
+ 
+-  do
++  if (!ensure_x11_unix_dir (&error))
+     {
+-      g_autoptr (GError) error = NULL;
++      g_warning ("Failed to ensure X11 socket directory: %s",
++                 error->message);
++      return FALSE;
++    }
+ 
++  do
++    {
+       lock_file = create_lock_file (display, &display, &error);
+       if (!lock_file)
+         {
+@@ -466,6 +490,7 @@ choose_xdisplay (MetaXWaylandManager *manager)
+           if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE))
+             {
+               meta_verbose ("Failed to bind abstract socket: %s\n", error->message);
++              g_clear_error (&error);
+               display++;
+               continue;
+             }
+@@ -480,6 +505,7 @@ choose_xdisplay (MetaXWaylandManager *manager)
+       if (manager->unix_fd < 0)
+         {
+           meta_verbose ("Failed to bind unix socket: %s\n", error->message);
++          g_clear_error (&error);
+           unlink (lock_file);
+           close (manager->abstract_fd);
+           display++;
+-- 
+2.29.2
+
diff --git a/SOURCES/geometric-picking.patch b/SOURCES/geometric-picking.patch
new file mode 100644
index 0000000..6892dca
--- /dev/null
+++ b/SOURCES/geometric-picking.patch
@@ -0,0 +1,2091 @@
+From ac946bf95ce3e4dc900f72dcb4189dd49bdb3155 Mon Sep 17 00:00:00 2001
+From: Daniel van Vugt <daniel.van.vugt@canonical.com>
+Date: Thu, 18 Jul 2019 16:56:41 +0800
+Subject: [PATCH 1/4] clutter/point: Add ClutterPoint quarilateral testing API
+
+Add a function to check whether a point is inside a quadrilateral
+by checking the cross product of vectors with the quadrilateral
+points, and the point being checked.
+
+If the passed quadrilateral is zero-sized, no point is ever reported
+to be inside it.
+
+This will be used by the next commit when comparing the transformed
+actor vertices.
+
+[feaneron: add a commit message and remove unecessary code]
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/189
+---
+ clutter/clutter/clutter-base-types.c | 62 ++++++++++++++++++++++++++++
+ clutter/clutter/clutter-types.h      |  3 ++
+ 2 files changed, 65 insertions(+)
+
+diff --git a/clutter/clutter/clutter-base-types.c b/clutter/clutter/clutter-base-types.c
+index aeb25c90ef..c84f9aa64b 100644
+--- a/clutter/clutter/clutter-base-types.c
++++ b/clutter/clutter/clutter-base-types.c
+@@ -570,6 +570,68 @@ G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterPoint, clutter_point,
+                                clutter_point_free,
+                                CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_point_progress))
+ 
++static int
++clutter_point_compare_line (const ClutterPoint *p,
++                            const ClutterPoint *a,
++                            const ClutterPoint *b)
++{
++  float x1 = b->x - a->x;
++  float y1 = b->y - a->y;
++  float x2 = p->x - a->x;
++  float y2 = p->y - a->y;
++  float cross_z = x1 * y2 - y1 * x2;
++
++  if (cross_z > 0.f)
++    return 1;
++  else if (cross_z < 0.f)
++    return -1;
++  else
++    return 0;
++}
++
++/**
++ * clutter_point_inside_quadrilateral:
++ * @point: a #ClutterPoint to test
++ * @vertices: array of vertices of the quadrilateral, in clockwise order,
++ *            from top-left to bottom-left
++ *
++ * Determines whether a point is inside the convex quadrilateral provided,
++ * and not on any of its edges or vertices.
++ *
++ * Returns: %TRUE if @point is inside the quadrilateral
++ */
++gboolean
++clutter_point_inside_quadrilateral (const ClutterPoint *point,
++                                    const ClutterPoint *vertices)
++{
++  unsigned int i;
++  int first_side;
++
++  first_side = 0;
++
++  for (i = 0; i < 4; i++)
++    {
++      int side;
++
++      side = clutter_point_compare_line (point,
++                                         &vertices[i],
++                                         &vertices[(i + 1) % 4]);
++
++      if (side)
++        {
++          if (first_side == 0)
++            first_side = side;
++          else if (side != first_side)
++            return FALSE;
++        }
++    }
++
++  if (first_side == 0)
++    return FALSE;
++
++  return TRUE;
++}
++
+ 
+ 
+ /*
+diff --git a/clutter/clutter/clutter-types.h b/clutter/clutter/clutter-types.h
+index 0f0fb1c2ac..0508028273 100644
+--- a/clutter/clutter/clutter-types.h
++++ b/clutter/clutter/clutter-types.h
+@@ -200,6 +200,9 @@ float                   clutter_point_distance  (const ClutterPoint *a,
+                                                  const ClutterPoint *b,
+                                                  float              *x_distance,
+                                                  float              *y_distance);
++CLUTTER_EXPORT
++gboolean clutter_point_inside_quadrilateral     (const ClutterPoint *point,
++                                                 const ClutterPoint *vertices);
+ 
+ /**
+  * ClutterSize:
+-- 
+2.29.2
+
+
+From 8abac81711cfef8317bb675349d6b5b0a79eb05d Mon Sep 17 00:00:00 2001
+From: Daniel van Vugt <daniel.van.vugt@canonical.com>
+Date: Thu, 2 Aug 2018 19:03:30 +0800
+Subject: [PATCH 2/4] clutter: Introduce geometric picking
+
+Currently, Clutter does picking by drawing with Cogl and reading
+the pixel that's beneath the given point. Since Cogl has a journal
+that records drawing operations, and has optimizations to read a
+single pixel from a list of rectangle, it would be expected that
+we would hit this fast path and not flush the journal while picking.
+
+However, that's not the case: dithering, clipping with scissors, etc,
+can all flush the journal, issuing commands to the GPU and making
+picking slow. On NVidia-based systems, this glReadPixels() call is
+extremely costly.
+
+Introduce geometric picking, and avoid using the Cogl journal entirely.
+Do this by introducing a stack of actors in ClutterStage. This stack
+is cached, but for now, don't use the cache as much as possible.
+
+The picking routines are still tied to painting.
+
+When projecting the actor vertexes, do it manually and take the modelview
+matrix of the framebuffer into account as well.
+
+CPU usage on an Intel i7-7700, tested with two different GPUs/drivers:
+
+  |         |     Intel | Nvidia |
+  | ------: | --------: | -----: |
+  | Moving the mouse:            |
+  | Before  |       10% |    10% |
+  | After   |        6% |     6% |
+  | Moving a window:             |
+  | Before  |       23% |    81% |
+  | After   |       19% |    40% |
+
+Closes: https://gitlab.gnome.org/GNOME/mutter/issues/154,
+        https://gitlab.gnome.org/GNOME/mutter/issues/691
+
+Helps significantly with: https://gitlab.gnome.org/GNOME/mutter/issues/283,
+                          https://gitlab.gnome.org/GNOME/mutter/issues/590,
+                          https://gitlab.gnome.org/GNOME/mutter/issues/700
+
+v2: Fix code style issues
+    Simplify quadrilateral checks
+    Remove the 0.5f hack
+    Differentiate axis-aligned rectangles
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/189
+---
+ clutter/clutter/clutter-actor-private.h      |   2 -
+ clutter/clutter/clutter-actor.c              | 232 ++++++----
+ clutter/clutter/clutter-actor.h              |   4 +
+ clutter/clutter/clutter-debug.h              |   1 -
+ clutter/clutter/clutter-main.c               | 120 -----
+ clutter/clutter/clutter-private.h            |   5 -
+ clutter/clutter/clutter-stage-private.h      |  16 +-
+ clutter/clutter/clutter-stage-window.c       |  18 -
+ clutter/clutter/clutter-stage-window.h       |   8 -
+ clutter/clutter/clutter-stage.c              | 459 +++++++++++--------
+ clutter/clutter/cogl/clutter-stage-cogl.c    |  52 ---
+ clutter/clutter/deprecated/clutter-texture.c |  93 +---
+ clutter/tests/conform/actor-pick.c           |  99 +---
+ clutter/tests/conform/meson.build            |   1 -
+ clutter/tests/conform/texture.c              |  84 ----
+ src/compositor/meta-surface-actor.c          |  27 +-
+ 16 files changed, 439 insertions(+), 782 deletions(-)
+ delete mode 100644 clutter/tests/conform/texture.c
+
+diff --git a/clutter/clutter/clutter-actor-private.h b/clutter/clutter/clutter-actor-private.h
+index c44f6342fd..9bf1a30493 100644
+--- a/clutter/clutter/clutter-actor-private.h
++++ b/clutter/clutter/clutter-actor-private.h
+@@ -297,8 +297,6 @@ const gchar *                   _clutter_actor_get_debug_name
+ void                            _clutter_actor_push_clone_paint                         (void);
+ void                            _clutter_actor_pop_clone_paint                          (void);
+ 
+-guint32                         _clutter_actor_get_pick_id                              (ClutterActor *self);
+-
+ void                            _clutter_actor_shader_pre_paint                         (ClutterActor *actor,
+                                                                                          gboolean      repeat);
+ void                            _clutter_actor_shader_post_paint                        (ClutterActor *actor);
+diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c
+index 43093fe79d..01ffa51caa 100644
+--- a/clutter/clutter/clutter-actor.c
++++ b/clutter/clutter/clutter-actor.c
+@@ -730,8 +730,6 @@ struct _ClutterActorPrivate
+ 
+   gchar *name; /* a non-unique name, used for debugging */
+ 
+-  gint32 pick_id; /* per-stage unique id, used for picking */
+-
+   /* a back-pointer to the Pango context that we can use
+    * to create pre-configured PangoLayout
+    */
+@@ -1281,6 +1279,105 @@ clutter_actor_verify_map_state (ClutterActor *self)
+ 
+ #endif /* CLUTTER_ENABLE_DEBUG */
+ 
++static gboolean
++_clutter_actor_transform_local_box_to_stage (ClutterActor          *self,
++                                             ClutterStage          *stage,
++                                             const ClutterActorBox *box,
++                                             ClutterPoint           vertices[4])
++{
++  CoglFramebuffer *fb = cogl_get_draw_framebuffer ();
++  CoglMatrix stage_transform, inv_stage_transform;
++  CoglMatrix modelview, transform_to_stage;
++  int v;
++
++  clutter_actor_get_transform (CLUTTER_ACTOR (stage), &stage_transform);
++  if (!cogl_matrix_get_inverse (&stage_transform, &inv_stage_transform))
++    return FALSE;
++  cogl_framebuffer_get_modelview_matrix (fb, &modelview);
++  cogl_matrix_multiply (&transform_to_stage, &inv_stage_transform, &modelview);
++
++  vertices[0].x = box->x1;
++  vertices[0].y = box->y1;
++
++  vertices[1].x = box->x2;
++  vertices[1].y = box->y1;
++
++  vertices[2].x = box->x2;
++  vertices[2].y = box->y2;
++
++  vertices[3].x = box->x1;
++  vertices[3].y = box->y2;
++
++  for (v = 0; v < 4; v++)
++    {
++      float z = 0.f;
++      float w = 1.f;
++
++      cogl_matrix_transform_point (&transform_to_stage,
++                                   &vertices[v].x,
++                                   &vertices[v].y,
++                                   &z,
++                                   &w);
++    }
++
++  return TRUE;
++}
++
++/**
++ * clutter_actor_pick_box:
++ * @self: The #ClutterActor being "pick" painted.
++ * @box: A rectangle in the actor's own local coordinates.
++ *
++ * Logs (does a virtual paint of) a rectangle for picking. Note that @box is
++ * in the actor's own local coordinates, so is usually {0,0,width,height}
++ * to include the whole actor. That is unless the actor has a shaped input
++ * region in which case you may wish to log the (multiple) smaller rectangles
++ * that make up the input region.
++ */
++void
++clutter_actor_pick_box (ClutterActor          *self,
++                        const ClutterActorBox *box)
++{
++  ClutterStage *stage;
++  ClutterPoint vertices[4];
++
++  g_return_if_fail (CLUTTER_IS_ACTOR (self));
++  g_return_if_fail (box != NULL);
++
++  if (box->x1 >= box->x2 || box->y1 >= box->y2)
++    return;
++
++  stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self));
++
++  if (_clutter_actor_transform_local_box_to_stage (self, stage, box, vertices))
++    clutter_stage_log_pick (stage, vertices, self);
++}
++
++static gboolean
++_clutter_actor_push_pick_clip (ClutterActor          *self,
++                               const ClutterActorBox *clip)
++{
++  ClutterStage *stage;
++  ClutterPoint vertices[4];
++
++  stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self));
++
++  if (!_clutter_actor_transform_local_box_to_stage (self, stage, clip, vertices))
++    return FALSE;
++
++  clutter_stage_push_pick_clip (stage, vertices);
++  return TRUE;
++}
++
++static void
++_clutter_actor_pop_pick_clip (ClutterActor *self)
++{
++  ClutterActor *stage;
++
++  stage = _clutter_actor_get_stage_internal (self);
++  clutter_stage_pop_pick_clip (CLUTTER_STAGE (stage));
++}
++
+ static void
+ clutter_actor_set_mapped (ClutterActor *self,
+                           gboolean      mapped)
+@@ -1509,8 +1606,7 @@ clutter_actor_update_map_state (ClutterActor  *self,
+ static void
+ clutter_actor_real_map (ClutterActor *self)
+ {
+-  ClutterActorPrivate *priv = self->priv;
+-  ClutterActor *stage, *iter;
++  ClutterActor *iter;
+ 
+   g_assert (!CLUTTER_ACTOR_IS_MAPPED (self));
+ 
+@@ -1521,13 +1617,6 @@ clutter_actor_real_map (ClutterActor *self)
+ 
+   self->priv->needs_paint_volume_update = TRUE;
+ 
+-  stage = _clutter_actor_get_stage_internal (self);
+-  priv->pick_id = _clutter_stage_acquire_pick_id (CLUTTER_STAGE (stage), self);
+-
+-  CLUTTER_NOTE (ACTOR, "Pick id '%d' for actor '%s'",
+-                priv->pick_id,
+-                _clutter_actor_get_debug_name (self));
+-
+   clutter_actor_ensure_resource_scale (self);
+ 
+   /* notify on parent mapped before potentially mapping
+@@ -1632,11 +1721,6 @@ clutter_actor_real_unmap (ClutterActor *self)
+ 
+       stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self));
+ 
+-      if (stage != NULL)
+-        _clutter_stage_release_pick_id (stage, priv->pick_id);
+-
+-      priv->pick_id = -1;
+-
+       if (stage != NULL &&
+           clutter_stage_get_key_focus (stage) == self)
+         {
+@@ -2255,46 +2339,16 @@ static void
+ clutter_actor_real_pick (ClutterActor       *self,
+ 			 const ClutterColor *color)
+ {
+-  CoglFramebuffer *framebuffer = cogl_get_draw_framebuffer ();
+-
+-  /* the default implementation is just to paint a rectangle
+-   * with the same size of the actor using the passed color
+-   */
+   if (clutter_actor_should_pick_paint (self))
+     {
+-      static CoglPipeline *default_pick_pipeline = NULL;
+-      ClutterActorBox box = { 0, };
+-      CoglPipeline *pick_pipeline;
+-      float width, height;
+-
+-      if (G_UNLIKELY (default_pick_pipeline == NULL))
+-        {
+-          CoglContext *ctx =
+-            clutter_backend_get_cogl_context (clutter_get_default_backend ());
+-
+-          default_pick_pipeline = cogl_pipeline_new (ctx);
+-        }
+-
+-      g_assert (default_pick_pipeline != NULL);
+-      pick_pipeline = cogl_pipeline_copy (default_pick_pipeline);
++      ClutterActorBox box = {
++        .x1 = 0,
++        .y1 = 0,
++        .x2 = clutter_actor_get_width (self),
++        .y2 = clutter_actor_get_height (self),
++      };
+ 
+-      clutter_actor_get_allocation_box (self, &box);
+-
+-      width = box.x2 - box.x1;
+-      height = box.y2 - box.y1;
+-
+-      cogl_pipeline_set_color4ub (pick_pipeline,
+-                                  color->red,
+-                                  color->green,
+-                                  color->blue,
+-                                  color->alpha);
+-
+-      cogl_framebuffer_draw_rectangle (framebuffer,
+-                                       pick_pipeline,
+-                                       0, 0,
+-                                       width, height);
+-
+-      cogl_object_unref (pick_pipeline);
++      clutter_actor_pick_box (self, &box);
+     }
+ 
+   /* XXX - this thoroughly sucks, but we need to maintain compatibility
+@@ -3585,15 +3639,6 @@ _clutter_actor_update_last_paint_volume (ClutterActor *self)
+   priv->last_paint_volume_valid = TRUE;
+ }
+ 
+-guint32
+-_clutter_actor_get_pick_id (ClutterActor *self)
+-{
+-  if (self->priv->pick_id < 0)
+-    return 0;
+-
+-  return self->priv->pick_id;
+-}
+-
+ /* This is the same as clutter_actor_add_effect except that it doesn't
+    queue a redraw and it doesn't notify on the effect property */
+ static void
+@@ -3826,6 +3871,7 @@ clutter_actor_paint (ClutterActor *self)
+   ClutterActorPrivate *priv;
+   ClutterPickMode pick_mode;
+   gboolean culling_inhibited;
++  ClutterActorBox clip;
+   gboolean clip_set = FALSE;
+   ClutterStage *stage;
+ 
+@@ -3919,24 +3965,38 @@ clutter_actor_paint (ClutterActor *self)
+ 
+   if (priv->has_clip)
+     {
+-      CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage);
+-      cogl_framebuffer_push_rectangle_clip (fb,
+-                                            priv->clip.origin.x,
+-                                            priv->clip.origin.y,
+-                                            priv->clip.origin.x + priv->clip.size.width,
+-                                            priv->clip.origin.y + priv->clip.size.height);
++      clip.x1 = priv->clip.origin.x;
++      clip.y1 = priv->clip.origin.y;
++      clip.x2 = priv->clip.origin.x + priv->clip.size.width;
++      clip.y2 = priv->clip.origin.y + priv->clip.size.height;
+       clip_set = TRUE;
+     }
+   else if (priv->clip_to_allocation)
+     {
+-      CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage);
+-      gfloat width, height;
++      clip.x1 = 0.f;
++      clip.y1 = 0.f;
++      clip.x2 = priv->allocation.x2 - priv->allocation.x1;
++      clip.y2 = priv->allocation.y2 - priv->allocation.y1;
++      clip_set = TRUE;
++    }
+ 
+-      width  = priv->allocation.x2 - priv->allocation.x1;
+-      height = priv->allocation.y2 - priv->allocation.y1;
++  if (clip_set)
++    {
++      if (pick_mode == CLUTTER_PICK_NONE)
++        {
++          CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage);
+ 
+-      cogl_framebuffer_push_rectangle_clip (fb, 0, 0, width, height);
+-      clip_set = TRUE;
++          cogl_framebuffer_push_rectangle_clip (fb,
++                                                clip.x1,
++                                                clip.y1,
++                                                clip.x2,
++                                                clip.y2);
++        }
++      else
++        {
++          if (!_clutter_actor_push_pick_clip (self, &clip))
++            clip_set = FALSE;
++        }
+     }
+ 
+   if (pick_mode == CLUTTER_PICK_NONE)
+@@ -4020,9 +4080,16 @@ clutter_actor_paint (ClutterActor *self)
+ done:
+   if (clip_set)
+     {
+-      CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage);
++      if (pick_mode == CLUTTER_PICK_NONE)
++        {
++          CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage);
+ 
+-      cogl_framebuffer_pop_clip (fb);
++          cogl_framebuffer_pop_clip (fb);
++        }
++      else
++        {
++          _clutter_actor_pop_pick_clip (self);
++        }
+     }
+ 
+   cogl_pop_matrix ();
+@@ -4093,11 +4160,12 @@ clutter_actor_continue_paint (ClutterActor *self)
+         {
+           ClutterColor col = { 0, };
+ 
+-          _clutter_id_to_color (_clutter_actor_get_pick_id (self), &col);
+-
+-          /* Actor will then paint silhouette of itself in supplied
+-           * color.  See clutter_stage_get_actor_at_pos() for where
+-           * picking is enabled.
++          /* The actor will log a silhouette of itself to the stage pick log.
++           * Note that the picking color is no longer used as the "log" instead
++           * keeps a weak pointer to the actor itself. But we keep the color
++           * parameter for now so as to maintain ABI compatibility. The color
++           * parameter can be removed when someone feels like breaking the ABI
++           * along with gnome-shell.
+            *
+            * XXX:2.0 - Call the pick() virtual directly
+            */
+@@ -8603,8 +8671,6 @@ clutter_actor_init (ClutterActor *self)
+ 
+   self->priv = priv = clutter_actor_get_instance_private (self);
+ 
+-  priv->pick_id = -1;
+-
+   priv->opacity = 0xff;
+   priv->show_on_set_parent = TRUE;
+   priv->resource_scale = -1.0f;
+diff --git a/clutter/clutter/clutter-actor.h b/clutter/clutter/clutter-actor.h
+index 3e7a59ac0c..16b438ba64 100644
+--- a/clutter/clutter/clutter-actor.h
++++ b/clutter/clutter/clutter-actor.h
+@@ -910,6 +910,10 @@ void                            clutter_actor_bind_model_with_properties
+                                                                                  const char                 *first_model_property,
+                                                                                  ...);
+ 
++CLUTTER_EXPORT
++void clutter_actor_pick_box (ClutterActor          *self,
++                             const ClutterActorBox *box);
++
+ G_END_DECLS
+ 
+ #endif /* __CLUTTER_ACTOR_H__ */
+diff --git a/clutter/clutter/clutter-debug.h b/clutter/clutter/clutter-debug.h
+index 2462385f65..7d170d2d54 100644
+--- a/clutter/clutter/clutter-debug.h
++++ b/clutter/clutter/clutter-debug.h
+@@ -30,7 +30,6 @@ typedef enum
+ typedef enum
+ {
+   CLUTTER_DEBUG_NOP_PICKING         = 1 << 0,
+-  CLUTTER_DEBUG_DUMP_PICK_BUFFERS   = 1 << 1
+ } ClutterPickDebugFlag;
+ 
+ typedef enum
+diff --git a/clutter/clutter/clutter-main.c b/clutter/clutter/clutter-main.c
+index 645c8bceb6..11c221a65b 100644
+--- a/clutter/clutter/clutter-main.c
++++ b/clutter/clutter/clutter-main.c
+@@ -131,7 +131,6 @@ static const GDebugKey clutter_debug_keys[] = {
+ 
+ static const GDebugKey clutter_pick_debug_keys[] = {
+   { "nop-picking", CLUTTER_DEBUG_NOP_PICKING },
+-  { "dump-pick-buffers", CLUTTER_DEBUG_DUMP_PICK_BUFFERS },
+ };
+ 
+ static const GDebugKey clutter_paint_debug_keys[] = {
+@@ -533,125 +532,6 @@ clutter_get_motion_events_enabled (void)
+   return _clutter_context_get_motion_events_enabled ();
+ }
+ 
+-void
+-_clutter_id_to_color (guint         id_,
+-                      ClutterColor *col)
+-{
+-  ClutterMainContext *ctx;
+-  gint red, green, blue;
+-
+-  ctx = _clutter_context_get_default ();
+-
+-  if (ctx->fb_g_mask == 0)
+-    {
+-      /* Figure out framebuffer masks used for pick */
+-      cogl_get_bitmasks (&ctx->fb_r_mask,
+-			 &ctx->fb_g_mask,
+-			 &ctx->fb_b_mask, NULL);
+-
+-      ctx->fb_r_mask_used = ctx->fb_r_mask;
+-      ctx->fb_g_mask_used = ctx->fb_g_mask;
+-      ctx->fb_b_mask_used = ctx->fb_b_mask;
+-
+-      /* XXX - describe what "fuzzy picking" is */
+-      if (clutter_use_fuzzy_picking)
+-	{
+-	  ctx->fb_r_mask_used--;
+-	  ctx->fb_g_mask_used--;
+-	  ctx->fb_b_mask_used--;
+-	}
+-    }
+-
+-  /* compute the numbers we'll store in the components */
+-  red   = (id_ >> (ctx->fb_g_mask_used+ctx->fb_b_mask_used))
+-        & (0xff >> (8-ctx->fb_r_mask_used));
+-  green = (id_ >> ctx->fb_b_mask_used)
+-        & (0xff >> (8-ctx->fb_g_mask_used));
+-  blue  = (id_)
+-        & (0xff >> (8-ctx->fb_b_mask_used));
+-
+-  /* shift left bits a bit and add one, this circumvents
+-   * at least some potential rounding errors in GL/GLES
+-   * driver / hw implementation.
+-   */
+-  if (ctx->fb_r_mask_used != ctx->fb_r_mask)
+-    red = red * 2;
+-  if (ctx->fb_g_mask_used != ctx->fb_g_mask)
+-    green = green * 2;
+-  if (ctx->fb_b_mask_used != ctx->fb_b_mask)
+-    blue  = blue  * 2;
+-
+-  /* shift up to be full 8bit values */
+-  red   = (red   << (8 - ctx->fb_r_mask)) | (0x7f >> (ctx->fb_r_mask_used));
+-  green = (green << (8 - ctx->fb_g_mask)) | (0x7f >> (ctx->fb_g_mask_used));
+-  blue  = (blue  << (8 - ctx->fb_b_mask)) | (0x7f >> (ctx->fb_b_mask_used));
+-
+-  col->red   = red;
+-  col->green = green;
+-  col->blue  = blue;
+-  col->alpha = 0xff;
+-
+-  /* XXX: We rotate the nibbles of the colors here so that there is a
+-   * visible variation between colors of sequential actor identifiers;
+-   * otherwise pick buffers dumped to an image will pretty much just look
+-   * black.
+-   */
+-  if (G_UNLIKELY (clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS))
+-    {
+-      col->red   = (col->red << 4)   | (col->red >> 4);
+-      col->green = (col->green << 4) | (col->green >> 4);
+-      col->blue  = (col->blue << 4)  | (col->blue >> 4);
+-    }
+-}
+-
+-guint
+-_clutter_pixel_to_id (guchar pixel[4])
+-{
+-  ClutterMainContext *ctx;
+-  gint red, green, blue;
+-  guint retval;
+-
+-  ctx = _clutter_context_get_default ();
+-
+-  /* reduce the pixel components to the number of bits actually used of the
+-   * 8bits.
+-   */
+-  if (G_UNLIKELY (clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS))
+-    {
+-      guchar tmp;
+-
+-      /* XXX: In _clutter_id_to_color we rotated the nibbles of the colors so
+-       * that there is a visible variation between colors of sequential actor
+-       * identifiers (otherwise pick buffers dumped to an image will pretty
+-       * much just look black.) Here we reverse that rotation.
+-       */
+-      tmp = ((pixel[0] << 4) | (pixel[0] >> 4));
+-      red = tmp >> (8 - ctx->fb_r_mask);
+-      tmp = ((pixel[1] << 4) | (pixel[1] >> 4));
+-      green = tmp >> (8 - ctx->fb_g_mask);
+-      tmp = ((pixel[2] << 4) | (pixel[2] >> 4));
+-      blue = tmp >> (8 - ctx->fb_b_mask);
+-    }
+-  else
+-    {
+-      red   = pixel[0] >> (8 - ctx->fb_r_mask);
+-      green = pixel[1] >> (8 - ctx->fb_g_mask);
+-      blue  = pixel[2] >> (8 - ctx->fb_b_mask);
+-    }
+-
+-  /* divide potentially by two if 'fuzzy' */
+-  red   = red   >> (ctx->fb_r_mask - ctx->fb_r_mask_used);
+-  green = green >> (ctx->fb_g_mask - ctx->fb_g_mask_used);
+-  blue  = blue  >> (ctx->fb_b_mask - ctx->fb_b_mask_used);
+-
+-  /* combine the correct per component values into the final id */
+-  retval = blue
+-         + (green <<  ctx->fb_b_mask_used)
+-         + (red << (ctx->fb_b_mask_used + ctx->fb_g_mask_used));
+-
+-  return retval;
+-}
+-
+ static CoglPangoFontMap *
+ clutter_context_get_pango_fontmap (void)
+ {
+diff --git a/clutter/clutter/clutter-private.h b/clutter/clutter/clutter-private.h
+index 5a0fed85c9..f2f870b014 100644
+--- a/clutter/clutter/clutter-private.h
++++ b/clutter/clutter/clutter-private.h
+@@ -210,11 +210,6 @@ gboolean      _clutter_feature_init (GError **error);
+ gboolean        _clutter_diagnostic_enabled     (void);
+ void            _clutter_diagnostic_message     (const char *fmt, ...) G_GNUC_PRINTF (1, 2);
+ 
+-/* Picking code */
+-guint           _clutter_pixel_to_id            (guchar        pixel[4]);
+-void            _clutter_id_to_color            (guint         id,
+-                                                 ClutterColor *col);
+-
+ void            _clutter_set_sync_to_vblank     (gboolean      sync_to_vblank);
+ 
+ /* use this function as the accumulator if you have a signal with
+diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h
+index 42474687ad..51ae47af1d 100644
+--- a/clutter/clutter/clutter-stage-private.h
++++ b/clutter/clutter/clutter-stage-private.h
+@@ -75,6 +75,15 @@ gint64    _clutter_stage_get_update_time                  (ClutterStage *stage);
+ void     _clutter_stage_clear_update_time                 (ClutterStage *stage);
+ gboolean _clutter_stage_has_full_redraw_queued            (ClutterStage *stage);
+ 
++void clutter_stage_log_pick (ClutterStage       *stage,
++                             const ClutterPoint *vertices,
++                             ClutterActor       *actor);
++
++void clutter_stage_push_pick_clip (ClutterStage       *stage,
++                                   const ClutterPoint *vertices);
++
++void clutter_stage_pop_pick_clip (ClutterStage *stage);
++
+ ClutterActor *_clutter_stage_do_pick (ClutterStage    *stage,
+                                       gint             x,
+                                       gint             y,
+@@ -93,13 +102,6 @@ void                          _clutter_stage_queue_redraw_entry_invalidate (Clut
+ 
+ CoglFramebuffer *_clutter_stage_get_active_framebuffer (ClutterStage *stage);
+ 
+-gint32          _clutter_stage_acquire_pick_id          (ClutterStage *stage,
+-                                                         ClutterActor *actor);
+-void            _clutter_stage_release_pick_id          (ClutterStage *stage,
+-                                                         gint32        pick_id);
+-ClutterActor *  _clutter_stage_get_actor_by_pick_id     (ClutterStage *stage,
+-                                                         gint32        pick_id);
+-
+ void            _clutter_stage_add_pointer_drag_actor    (ClutterStage       *stage,
+                                                           ClutterInputDevice *device,
+                                                           ClutterActor       *actor);
+diff --git a/clutter/clutter/clutter-stage-window.c b/clutter/clutter/clutter-stage-window.c
+index e8fa976a7d..4c4ef9d643 100644
+--- a/clutter/clutter/clutter-stage-window.c
++++ b/clutter/clutter/clutter-stage-window.c
+@@ -293,24 +293,6 @@ _clutter_stage_window_redraw (ClutterStageWindow *window)
+     iface->redraw (window);
+ }
+ 
+-
+-void
+-_clutter_stage_window_get_dirty_pixel (ClutterStageWindow *window,
+-                                       ClutterStageView   *view,
+-                                       int *x, int *y)
+-{
+-  ClutterStageWindowInterface *iface;
+-
+-  *x = 0;
+-  *y = 0;
+-
+-  g_return_if_fail (CLUTTER_IS_STAGE_WINDOW (window));
+-
+-  iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window);
+-  if (iface->get_dirty_pixel)
+-    iface->get_dirty_pixel (window, view, x, y);
+-}
+-
+ gboolean
+ _clutter_stage_window_can_clip_redraws (ClutterStageWindow *window)
+ {
+diff --git a/clutter/clutter/clutter-stage-window.h b/clutter/clutter/clutter-stage-window.h
+index 6c3601745f..aa0c5f71cc 100644
+--- a/clutter/clutter/clutter-stage-window.h
++++ b/clutter/clutter/clutter-stage-window.h
+@@ -68,10 +68,6 @@ struct _ClutterStageWindowInterface
+ 
+   void              (* redraw)                  (ClutterStageWindow *stage_window);
+ 
+-  void              (* get_dirty_pixel)         (ClutterStageWindow *stage_window,
+-                                                 ClutterStageView   *view,
+-                                                 int *x, int *y);
+-
+   gboolean          (* can_clip_redraws)        (ClutterStageWindow *stage_window);
+ 
+   GList            *(* get_views)               (ClutterStageWindow *stage_window);
+@@ -119,10 +115,6 @@ void              _clutter_stage_window_set_accept_focus        (ClutterStageWin
+ 
+ void              _clutter_stage_window_redraw                  (ClutterStageWindow *window);
+ 
+-void              _clutter_stage_window_get_dirty_pixel         (ClutterStageWindow *window,
+-                                                                 ClutterStageView   *view,
+-                                                                 int *x, int *y);
+-
+ gboolean          _clutter_stage_window_can_clip_redraws        (ClutterStageWindow *window);
+ 
+ GList *           _clutter_stage_window_get_views               (ClutterStageWindow *window);
+diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
+index aaa77d9ede..7d88d5752f 100644
+--- a/clutter/clutter/clutter-stage.c
++++ b/clutter/clutter/clutter-stage.c
+@@ -97,6 +97,11 @@ typedef enum /*< prefix=CLUTTER_STAGE >*/
+ 
+ #define STAGE_NO_CLEAR_ON_PAINT(s)      ((((ClutterStage *) (s))->priv->stage_hints & CLUTTER_STAGE_NO_CLEAR_ON_PAINT) != 0)
+ 
++#ifndef G_APPROX_VALUE
++#define G_APPROX_VALUE(a, b, epsilon) \
++  (((a) > (b) ? (a) - (b) : (b) - (a)) < (epsilon))
++#endif
++
+ struct _ClutterStageQueueRedrawEntry
+ {
+   ClutterActor *actor;
+@@ -104,6 +109,19 @@ struct _ClutterStageQueueRedrawEntry
+   ClutterPaintVolume clip;
+ };
+ 
++typedef struct _PickRecord
++{
++  ClutterPoint vertex[4];
++  ClutterActor *actor;
++  int clip_stack_top;
++} PickRecord;
++
++typedef struct _PickClipRecord
++{
++  int prev;
++  ClutterPoint vertex[4];
++} PickClipRecord;
++
+ struct _ClutterStagePrivate
+ {
+   /* the stage implementation */
+@@ -137,7 +155,11 @@ struct _ClutterStagePrivate
+   GTimer *fps_timer;
+   gint32 timer_n_frames;
+ 
+-  ClutterIDPool *pick_id_pool;
++  GArray *pick_stack;
++  GArray *pick_clip_stack;
++  int pick_clip_stack_top;
++  gboolean pick_stack_frozen;
++  ClutterPickMode cached_pick_mode;
+ 
+ #ifdef CLUTTER_ENABLE_DEBUG
+   gulong redraw_count;
+@@ -326,6 +348,211 @@ clutter_stage_get_preferred_height (ClutterActor *self,
+     *natural_height_p = geom.height;
+ }
+ 
++static void
++add_pick_stack_weak_refs (ClutterStage *stage)
++{
++  ClutterStagePrivate *priv = stage->priv;
++  int i;
++
++  if (priv->pick_stack_frozen)
++    return;
++
++  for (i = 0; i < priv->pick_stack->len; i++)
++    {
++      PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i);
++
++      if (rec->actor)
++        g_object_add_weak_pointer (G_OBJECT (rec->actor),
++                                   (gpointer) &rec->actor);
++    }
++
++  priv->pick_stack_frozen = TRUE;
++}
++
++static void
++remove_pick_stack_weak_refs (ClutterStage *stage)
++{
++  ClutterStagePrivate *priv = stage->priv;
++  int i;
++
++  if (!priv->pick_stack_frozen)
++    return;
++
++  for (i = 0; i < priv->pick_stack->len; i++)
++    {
++      PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i);
++
++      if (rec->actor)
++        g_object_remove_weak_pointer (G_OBJECT (rec->actor),
++                                      (gpointer) &rec->actor);
++    }
++
++  priv->pick_stack_frozen = FALSE;
++}
++
++static void
++_clutter_stage_clear_pick_stack (ClutterStage *stage)
++{
++  ClutterStagePrivate *priv = stage->priv;
++
++  remove_pick_stack_weak_refs (stage);
++  g_array_set_size (priv->pick_stack, 0);
++  g_array_set_size (priv->pick_clip_stack, 0);
++  priv->pick_clip_stack_top = -1;
++  priv->cached_pick_mode = CLUTTER_PICK_NONE;
++}
++
++void
++clutter_stage_log_pick (ClutterStage       *stage,
++                        const ClutterPoint *vertices,
++                        ClutterActor       *actor)
++{
++  ClutterStagePrivate *priv;
++  PickRecord rec;
++
++  g_return_if_fail (CLUTTER_IS_STAGE (stage));
++  g_return_if_fail (actor != NULL);
++
++  priv = stage->priv;
++
++  g_assert (!priv->pick_stack_frozen);
++
++  memcpy (rec.vertex, vertices, 4 * sizeof (ClutterPoint));
++  rec.actor = actor;
++  rec.clip_stack_top = priv->pick_clip_stack_top;
++
++  g_array_append_val (priv->pick_stack, rec);
++}
++
++void
++clutter_stage_push_pick_clip (ClutterStage       *stage,
++                              const ClutterPoint *vertices)
++{
++  ClutterStagePrivate *priv;
++  PickClipRecord clip;
++
++  g_return_if_fail (CLUTTER_IS_STAGE (stage));
++
++  priv = stage->priv;
++
++  g_assert (!priv->pick_stack_frozen);
++
++  clip.prev = priv->pick_clip_stack_top;
++  memcpy (clip.vertex, vertices, 4 * sizeof (ClutterPoint));
++
++  g_array_append_val (priv->pick_clip_stack, clip);
++  priv->pick_clip_stack_top = priv->pick_clip_stack->len - 1;
++}
++
++void
++clutter_stage_pop_pick_clip (ClutterStage *stage)
++{
++  ClutterStagePrivate *priv;
++  const PickClipRecord *top;
++
++  g_return_if_fail (CLUTTER_IS_STAGE (stage));
++
++  priv = stage->priv;
++
++  g_assert (!priv->pick_stack_frozen);
++  g_assert (priv->pick_clip_stack_top >= 0);
++
++  /* Individual elements of pick_clip_stack are not freed. This is so they
++   * can be shared as part of a tree of different stacks used by different
++   * actors in the pick_stack. The whole pick_clip_stack does however get
++   * freed later in _clutter_stage_clear_pick_stack.
++   */
++
++  top = &g_array_index (priv->pick_clip_stack,
++                        PickClipRecord,
++                        priv->pick_clip_stack_top);
++
++  priv->pick_clip_stack_top = top->prev;
++}
++
++static gboolean
++is_quadrilateral_axis_aligned_rectangle (const ClutterPoint *vertices)
++{
++  int i;
++
++  for (i = 0; i < 4; i++)
++    {
++      if (!G_APPROX_VALUE (vertices[i].x,
++                           vertices[(i + 1) % 4].x,
++                           FLT_EPSILON) &&
++          !G_APPROX_VALUE (vertices[i].y,
++                           vertices[(i + 1) % 4].y,
++                           FLT_EPSILON))
++        return FALSE;
++    }
++  return TRUE;
++}
++
++static gboolean
++is_inside_axis_aligned_rectangle (const ClutterPoint *point,
++                                  const ClutterPoint *vertices)
++{
++  float min_x = FLT_MAX;
++  float max_x = FLT_MIN;
++  float min_y = FLT_MAX;
++  float max_y = FLT_MIN;
++  int i;
++
++  for (i = 0; i < 3; i++)
++    {
++      min_x = MIN (min_x, vertices[i].x);
++      min_y = MIN (min_y, vertices[i].y);
++      max_x = MAX (max_x, vertices[i].x);
++      max_y = MAX (max_y, vertices[i].y);
++    }
++
++  return (point->x >= min_x &&
++          point->y >= min_y &&
++          point->x < max_x &&
++          point->y < max_y);
++}
++
++static gboolean
++is_inside_input_region (const ClutterPoint *point,
++                        const ClutterPoint *vertices)
++{
++
++  if (is_quadrilateral_axis_aligned_rectangle (vertices))
++    return is_inside_axis_aligned_rectangle (point, vertices);
++  else
++    return clutter_point_inside_quadrilateral (point, vertices);
++}
++
++static gboolean
++pick_record_contains_pixel (ClutterStage     *stage,
++                            const PickRecord *rec,
++                            int               x,
++                            int               y)
++{
++  const ClutterPoint point = CLUTTER_POINT_INIT (x, y);
++  ClutterStagePrivate *priv;
++  int clip_index;
++
++  if (!is_inside_input_region (&point, rec->vertex))
++      return FALSE;
++
++  priv = stage->priv;
++  clip_index = rec->clip_stack_top;
++  while (clip_index >= 0)
++    {
++      const PickClipRecord *clip = &g_array_index (priv->pick_clip_stack,
++                                                   PickClipRecord,
++                                                   clip_index);
++
++      if (!is_inside_input_region (&point, clip->vertex))
++        return FALSE;
++
++      clip_index = clip->prev;
++    }
++
++  return TRUE;
++}
++
+ static inline void
+ queue_full_redraw (ClutterStage *stage)
+ {
+@@ -636,6 +863,12 @@ clutter_stage_do_paint_view (ClutterStage                *stage,
+   float viewport[4];
+   cairo_rectangle_int_t geom;
+ 
++  /* Any mode of painting/picking invalidates the pick cache, unless we're
++   * in the middle of building it. So we reset the cached flag but don't
++   * completely clear the pick stack.
++   */
++  priv->cached_pick_mode = CLUTTER_PICK_NONE;
++
+   _clutter_stage_window_get_geometry (priv->impl, &geom);
+ 
+   viewport[0] = priv->viewport[0];
+@@ -1414,40 +1647,6 @@ clutter_stage_get_redraw_clip_bounds (ClutterStage          *stage,
+     }
+ }
+ 
+-static void
+-read_pixels_to_file (CoglFramebuffer *fb,
+-                     char            *filename_stem,
+-                     int              x,
+-                     int              y,
+-                     int              width,
+-                     int              height)
+-{
+-  guint8 *data;
+-  cairo_surface_t *surface;
+-  static int read_count = 0;
+-  char *filename = g_strdup_printf ("%s-%05d.png",
+-                                    filename_stem,
+-                                    read_count);
+-
+-  data = g_malloc (4 * width * height);
+-  cogl_framebuffer_read_pixels (fb,
+-                                x, y, width, height,
+-                                CLUTTER_CAIRO_FORMAT_ARGB32,
+-                                data);
+-
+-  surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_RGB24,
+-                                                 width, height,
+-                                                 width * 4);
+-
+-  cairo_surface_write_to_png (surface, filename);
+-  cairo_surface_destroy (surface);
+-
+-  g_free (data);
+-  g_free (filename);
+-
+-  read_count++;
+-}
+-
+ static ClutterActor *
+ _clutter_stage_do_pick_on_view (ClutterStage     *stage,
+                                 gint              x,
+@@ -1455,140 +1654,42 @@ _clutter_stage_do_pick_on_view (ClutterStage     *stage,
+                                 ClutterPickMode   mode,
+                                 ClutterStageView *view)
+ {
+-  ClutterActor *actor = CLUTTER_ACTOR (stage);
++  ClutterMainContext *context = _clutter_context_get_default ();
+   ClutterStagePrivate *priv = stage->priv;
+   CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view);
+-  cairo_rectangle_int_t view_layout;
+-  ClutterMainContext *context;
+-  guchar pixel[4] = { 0xff, 0xff, 0xff, 0xff };
+-  CoglColor stage_pick_id;
+-  gboolean dither_enabled_save;
+-  ClutterActor *retval;
+-  gint dirty_x;
+-  gint dirty_y;
+-  gint read_x;
+-  gint read_y;
+-  float fb_width, fb_height;
+-  float fb_scale;
+-  float viewport_offset_x;
+-  float viewport_offset_y;
+-
+-  priv = stage->priv;
+-
+-  context = _clutter_context_get_default ();
+-  fb_scale = clutter_stage_view_get_scale (view);
+-  clutter_stage_view_get_layout (view, &view_layout);
+-
+-  fb_width = view_layout.width * fb_scale;
+-  fb_height = view_layout.height * fb_scale;
+-  cogl_push_framebuffer (fb);
+-
+-  /* needed for when a context switch happens */
+-  _clutter_stage_maybe_setup_viewport (stage, view);
+-
+-  /* FIXME: For some reason leaving the cogl clip stack empty causes the
+-   * picking to not work at all, so setting it the whole framebuffer content
+-   * for now. */
+-  cogl_framebuffer_push_scissor_clip (fb, 0, 0,
+-                                      view_layout.width * fb_scale,
+-                                      view_layout.height * fb_scale);
+-
+-  _clutter_stage_window_get_dirty_pixel (priv->impl, view, &dirty_x, &dirty_y);
++  int i;
+ 
+-  if (G_LIKELY (!(clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS)))
+-    {
+-      CLUTTER_NOTE (PICK, "Pushing pick scissor clip x: %d, y: %d, 1x1",
+-                    (int) (dirty_x * fb_scale),
+-                    (int) (dirty_y * fb_scale));
+-      cogl_framebuffer_push_scissor_clip (fb, dirty_x * fb_scale, dirty_y * fb_scale, 1, 1);
+-    }
++  g_assert (context->pick_mode == CLUTTER_PICK_NONE);
+ 
+-  viewport_offset_x = x * fb_scale - dirty_x * fb_scale;
+-  viewport_offset_y = y * fb_scale - dirty_y * fb_scale;
+-  CLUTTER_NOTE (PICK, "Setting viewport to %f, %f, %f, %f",
+-                priv->viewport[0] * fb_scale - viewport_offset_x,
+-                priv->viewport[1] * fb_scale - viewport_offset_y,
+-                priv->viewport[2] * fb_scale,
+-                priv->viewport[3] * fb_scale);
+-  cogl_framebuffer_set_viewport (fb,
+-                                 priv->viewport[0] * fb_scale - viewport_offset_x,
+-                                 priv->viewport[1] * fb_scale - viewport_offset_y,
+-                                 priv->viewport[2] * fb_scale,
+-                                 priv->viewport[3] * fb_scale);
+-
+-  read_x = dirty_x * fb_scale;
+-  read_y = dirty_y * fb_scale;
+-
+-  CLUTTER_NOTE (PICK, "Performing pick at %i,%i on view %dx%d+%d+%d s: %f",
+-                x, y,
+-                view_layout.width, view_layout.height,
+-                view_layout.x, view_layout.y, fb_scale);
+-
+-  cogl_color_init_from_4ub (&stage_pick_id, 255, 255, 255, 255);
+-  cogl_framebuffer_clear (fb, COGL_BUFFER_BIT_COLOR | COGL_BUFFER_BIT_DEPTH, &stage_pick_id);
+-
+-  /* Disable dithering (if any) when doing the painting in pick mode */
+-  dither_enabled_save = cogl_framebuffer_get_dither_enabled (fb);
+-  cogl_framebuffer_set_dither_enabled (fb, FALSE);
+-
+-  /* Render the entire scence in pick mode - just single colored silhouette's
+-   * are drawn offscreen (as we never swap buffers)
+-  */
+-  context->pick_mode = mode;
+-
+-  clutter_stage_do_paint_view (stage, view, NULL);
+-  context->pick_mode = CLUTTER_PICK_NONE;
+-
+-  /* Read the color of the screen co-ords pixel. RGBA_8888_PRE is used
+-     even though we don't care about the alpha component because under
+-     GLES this is the only format that is guaranteed to work so Cogl
+-     will end up having to do a conversion if any other format is
+-     used. The format is requested as pre-multiplied because Cogl
+-     assumes that all pixels in the framebuffer are premultiplied so
+-     it avoids a conversion. */
+-  cogl_framebuffer_read_pixels (fb,
+-                                read_x, read_y, 1, 1,
+-                                COGL_PIXEL_FORMAT_RGBA_8888_PRE,
+-                                pixel);
+-
+-  if (G_UNLIKELY (clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS))
++  if (mode != priv->cached_pick_mode)
+     {
+-      char *file_name =
+-        g_strdup_printf ("pick-buffer-%s-view-x-%d",
+-                         _clutter_actor_get_debug_name (actor),
+-                         view_layout.x);
++      _clutter_stage_clear_pick_stack (stage);
+ 
+-      read_pixels_to_file (fb, file_name, 0, 0, fb_width, fb_height);
++      cogl_push_framebuffer (fb);
+ 
+-      g_free (file_name);
+-    }
+-
+-  /* Restore whether GL_DITHER was enabled */
+-  cogl_framebuffer_set_dither_enabled (fb, dither_enabled_save);
+-
+-  if (G_LIKELY (!(clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS)))
+-    cogl_framebuffer_pop_clip (fb);
++      context->pick_mode = mode;
++      clutter_stage_do_paint_view (stage, view, NULL);
++      context->pick_mode = CLUTTER_PICK_NONE;
++      priv->cached_pick_mode = mode;
+ 
+-  cogl_framebuffer_pop_clip (fb);
++      cogl_pop_framebuffer ();
+ 
+-  _clutter_stage_dirty_viewport (stage);
++      add_pick_stack_weak_refs (stage);
++    }
+ 
+-  if (pixel[0] == 0xff && pixel[1] == 0xff && pixel[2] == 0xff)
+-    retval = actor;
+-  else
++  /* Search all "painted" pickable actors from front to back. A linear search
++   * is required, and also performs fine since there is typically only
++   * on the order of dozens of actors in the list (on screen) at a time.
++   */
++  for (i = priv->pick_stack->len - 1; i >= 0; i--)
+     {
+-      guint32 id_ = _clutter_pixel_to_id (pixel);
++      const PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i);
+ 
+-      retval = _clutter_stage_get_actor_by_pick_id (stage, id_);
+-      CLUTTER_NOTE (PICK, "Picking actor %s with id %u (pixel: 0x%x%x%x%x",
+-                    G_OBJECT_TYPE_NAME (retval),
+-                    id_,
+-                    pixel[0], pixel[1], pixel[2], pixel[3]);
++      if (rec->actor && pick_record_contains_pixel (stage, rec, x, y))
++        return rec->actor;
+     }
+ 
+-  cogl_pop_framebuffer ();
+-
+-  return retval;
++  return CLUTTER_ACTOR (stage);
+ }
+ 
+ static ClutterStageView *
+@@ -1901,7 +2002,9 @@ clutter_stage_finalize (GObject *object)
+ 
+   g_array_free (priv->paint_volume_stack, TRUE);
+ 
+-  _clutter_id_pool_free (priv->pick_id_pool);
++  _clutter_stage_clear_pick_stack (stage);
++  g_array_free (priv->pick_clip_stack, TRUE);
++  g_array_free (priv->pick_stack, TRUE);
+ 
+   if (priv->fps_timer != NULL)
+     g_timer_destroy (priv->fps_timer);
+@@ -2435,7 +2538,10 @@ clutter_stage_init (ClutterStage *self)
+   priv->paint_volume_stack =
+     g_array_new (FALSE, FALSE, sizeof (ClutterPaintVolume));
+ 
+-  priv->pick_id_pool = _clutter_id_pool_new (256);
++  priv->pick_stack = g_array_new (FALSE, FALSE, sizeof (PickRecord));
++  priv->pick_clip_stack = g_array_new (FALSE, FALSE, sizeof (PickClipRecord));
++  priv->pick_clip_stack_top = -1;
++  priv->cached_pick_mode = CLUTTER_PICK_NONE;
+ }
+ 
+ /**
+@@ -4253,6 +4359,12 @@ _clutter_stage_queue_actor_redraw (ClutterStage                 *stage,
+   CLUTTER_NOTE (CLIPPING, "stage_queue_actor_redraw (actor=%s, clip=%p): ",
+                 _clutter_actor_get_debug_name (actor), clip);
+ 
++  /* Queuing a redraw or clip change invalidates the pick cache, unless we're
++   * in the middle of building it. So we reset the cached flag but don't
++   * completely clear the pick stack...
++   */
++  priv->cached_pick_mode = CLUTTER_PICK_NONE;
++
+   if (!priv->redraw_pending)
+     {
+       ClutterMasterClock *master_clock;
+@@ -4513,39 +4625,6 @@ _clutter_stage_get_active_framebuffer (ClutterStage *stage)
+   return stage->priv->active_framebuffer;
+ }
+ 
+-gint32
+-_clutter_stage_acquire_pick_id (ClutterStage *stage,
+-                                ClutterActor *actor)
+-{
+-  ClutterStagePrivate *priv = stage->priv;
+-
+-  g_assert (priv->pick_id_pool != NULL);
+-
+-  return _clutter_id_pool_add (priv->pick_id_pool, actor);
+-}
+-
+-void
+-_clutter_stage_release_pick_id (ClutterStage *stage,
+-                                gint32        pick_id)
+-{
+-  ClutterStagePrivate *priv = stage->priv;
+-
+-  g_assert (priv->pick_id_pool != NULL);
+-
+-  _clutter_id_pool_remove (priv->pick_id_pool, pick_id);
+-}
+-
+-ClutterActor *
+-_clutter_stage_get_actor_by_pick_id (ClutterStage *stage,
+-                                     gint32        pick_id)
+-{
+-  ClutterStagePrivate *priv = stage->priv;
+-
+-  g_assert (priv->pick_id_pool != NULL);
+-
+-  return _clutter_id_pool_lookup (priv->pick_id_pool, pick_id);
+-}
+-
+ void
+ _clutter_stage_add_pointer_drag_actor (ClutterStage       *stage,
+                                        ClutterInputDevice *device,
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index 3f1f609c4e..effed79759 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -926,57 +926,6 @@ clutter_stage_cogl_redraw (ClutterStageWindow *stage_window)
+   stage_cogl->frame_count++;
+ }
+ 
+-static void
+-clutter_stage_cogl_get_dirty_pixel (ClutterStageWindow *stage_window,
+-                                    ClutterStageView   *view,
+-                                    int                *x,
+-                                    int                *y)
+-{
+-  CoglFramebuffer *onscreen = clutter_stage_view_get_onscreen (view);
+-  ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
+-  ClutterStageViewCoglPrivate *view_priv =
+-    clutter_stage_view_cogl_get_instance_private (view_cogl);
+-  gboolean has_buffer_age =
+-    cogl_is_onscreen (onscreen) &&
+-    is_buffer_age_enabled ();
+-  float fb_scale;
+-  gboolean scale_is_fractional;
+-
+-  fb_scale = clutter_stage_view_get_scale (view);
+-  if (fb_scale != floorf (fb_scale))
+-    scale_is_fractional = TRUE;
+-  else
+-    scale_is_fractional = FALSE;
+-
+-  /*
+-   * Buffer damage is tracked in the framebuffer coordinate space
+-   * using the damage history. When fractional scaling is used, a
+-   * coordinate on the stage might not correspond to the exact position of any
+-   * physical pixel, which causes issues when painting using the pick mode.
+-   *
+-   * For now, always use the (0, 0) pixel for picking when using fractional
+-   * framebuffer scaling.
+-   */
+-  if (!has_buffer_age ||
+-      scale_is_fractional ||
+-      !clutter_damage_history_is_age_valid (view_priv->damage_history, 0))
+-    {
+-      *x = 0;
+-      *y = 0;
+-    }
+-  else
+-    {
+-      cairo_rectangle_int_t view_layout;
+-      const cairo_rectangle_int_t *fb_damage;
+-
+-      clutter_stage_view_get_layout (view, &view_layout);
+-
+-      fb_damage = clutter_damage_history_lookup (view_priv->damage_history, 0);
+-      *x = fb_damage->x / fb_scale;
+-      *y = fb_damage->y / fb_scale;
+-    }
+-}
+-
+ static void
+ clutter_stage_window_iface_init (ClutterStageWindowInterface *iface)
+ {
+@@ -994,7 +943,6 @@ clutter_stage_window_iface_init (ClutterStageWindowInterface *iface)
+   iface->ignoring_redraw_clips = clutter_stage_cogl_ignoring_redraw_clips;
+   iface->get_redraw_clip_bounds = clutter_stage_cogl_get_redraw_clip_bounds;
+   iface->redraw = clutter_stage_cogl_redraw;
+-  iface->get_dirty_pixel = clutter_stage_cogl_get_dirty_pixel;
+ }
+ 
+ static void
+diff --git a/clutter/clutter/deprecated/clutter-texture.c b/clutter/clutter/deprecated/clutter-texture.c
+index bea239f454..2c677b8a44 100644
+--- a/clutter/clutter/deprecated/clutter-texture.c
++++ b/clutter/clutter/deprecated/clutter-texture.c
+@@ -572,83 +572,6 @@ gen_texcoords_and_draw_cogl_rectangle (ClutterActor    *self,
+                                             0, 0, t_w, t_h);
+ }
+ 
+-static CoglPipeline *
+-create_pick_pipeline (ClutterActor *self)
+-{
+-  ClutterTexture *texture = CLUTTER_TEXTURE (self);
+-  ClutterTexturePrivate *priv = texture->priv;
+-  CoglPipeline *pick_pipeline = cogl_pipeline_copy (texture_template_pipeline);
+-  GError *error = NULL;
+-
+-  if (!cogl_pipeline_set_layer_combine (pick_pipeline, 0,
+-                                        "RGBA = "
+-                                        "  MODULATE (CONSTANT, TEXTURE[A])",
+-                                        &error))
+-    {
+-      if (!priv->seen_create_pick_pipeline_warning)
+-        g_warning ("Error setting up texture combine for shaped "
+-                   "texture picking: %s", error->message);
+-      priv->seen_create_pick_pipeline_warning = TRUE;
+-      g_error_free (error);
+-      cogl_object_unref (pick_pipeline);
+-      return NULL;
+-    }
+-
+-  cogl_pipeline_set_blend (pick_pipeline,
+-                           "RGBA = ADD (SRC_COLOR[RGBA], 0)",
+-                           NULL);
+-
+-  cogl_pipeline_set_alpha_test_function (pick_pipeline,
+-                                         COGL_PIPELINE_ALPHA_FUNC_EQUAL,
+-                                         1.0);
+-
+-  return pick_pipeline;
+-}
+-
+-static void
+-clutter_texture_pick (ClutterActor       *self,
+-                      const ClutterColor *color)
+-{
+-  ClutterTexture *texture = CLUTTER_TEXTURE (self);
+-  ClutterTexturePrivate *priv = texture->priv;
+-  CoglFramebuffer *framebuffer = cogl_get_draw_framebuffer ();
+-
+-  if (!clutter_actor_should_pick_paint (self))
+-    return;
+-
+-  if (G_LIKELY (priv->pick_with_alpha_supported) && priv->pick_with_alpha)
+-    {
+-      CoglColor pick_color;
+-
+-      if (priv->pick_pipeline == NULL)
+-        priv->pick_pipeline = create_pick_pipeline (self);
+-
+-      if (priv->pick_pipeline == NULL)
+-        {
+-          priv->pick_with_alpha_supported = FALSE;
+-          CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)->pick (self,
+-                                                                    color);
+-          return;
+-        }
+-
+-      if (priv->fbo_handle != NULL)
+-        update_fbo (self);
+-
+-      cogl_color_init_from_4ub (&pick_color,
+-                                color->red,
+-                                color->green,
+-                                color->blue,
+-                                0xff);
+-      cogl_pipeline_set_layer_combine_constant (priv->pick_pipeline,
+-                                                0, &pick_color);
+-      cogl_pipeline_set_layer_texture (priv->pick_pipeline, 0,
+-                                       clutter_texture_get_cogl_texture (texture));
+-      gen_texcoords_and_draw_cogl_rectangle (self, priv->pick_pipeline, framebuffer);
+-    }
+-  else
+-    CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)->pick (self, color);
+-}
+-
+ static void
+ clutter_texture_paint (ClutterActor *self)
+ {
+@@ -767,12 +690,6 @@ clutter_texture_dispose (GObject *object)
+       priv->pipeline = NULL;
+     }
+ 
+-  if (priv->pick_pipeline != NULL)
+-    {
+-      cogl_object_unref (priv->pick_pipeline);
+-      priv->pick_pipeline = NULL;
+-    }
+-
+   G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object);
+ }
+ 
+@@ -944,7 +861,6 @@ clutter_texture_class_init (ClutterTextureClass *klass)
+   GParamSpec *pspec;
+ 
+   actor_class->paint            = clutter_texture_paint;
+-  actor_class->pick             = clutter_texture_pick;
+   actor_class->get_paint_volume = clutter_texture_get_paint_volume;
+   actor_class->realize          = clutter_texture_realize;
+   actor_class->unrealize        = clutter_texture_unrealize;
+@@ -1263,11 +1179,9 @@ clutter_texture_init (ClutterTexture *self)
+   priv->repeat_y          = FALSE;
+   priv->sync_actor_size   = TRUE;
+   priv->fbo_handle        = NULL;
+-  priv->pick_pipeline     = NULL;
+   priv->keep_aspect_ratio = FALSE;
+   priv->pick_with_alpha   = FALSE;
+   priv->pick_with_alpha_supported = TRUE;
+-  priv->seen_create_pick_pipeline_warning = FALSE;
+ 
+   if (G_UNLIKELY (texture_template_pipeline == NULL))
+     {
+@@ -3052,13 +2966,8 @@ clutter_texture_set_pick_with_alpha (ClutterTexture *texture,
+   if (priv->pick_with_alpha == pick_with_alpha)
+     return;
+ 
+-  if (!pick_with_alpha && priv->pick_pipeline != NULL)
+-    {
+-      cogl_object_unref (priv->pick_pipeline);
+-      priv->pick_pipeline = NULL;
+-    }
++  g_assert (!pick_with_alpha);  /* No longer supported */
+ 
+-  /* NB: the pick pipeline is created lazily when we first pick */
+   priv->pick_with_alpha = pick_with_alpha;
+ 
+   /* NB: actors are expected to call clutter_actor_queue_redraw when
+diff --git a/clutter/tests/conform/actor-pick.c b/clutter/tests/conform/actor-pick.c
+index 969b4920ac..2bf5954c73 100644
+--- a/clutter/tests/conform/actor-pick.c
++++ b/clutter/tests/conform/actor-pick.c
+@@ -5,7 +5,6 @@
+ #define STAGE_HEIGHT 480
+ #define ACTORS_X 12
+ #define ACTORS_Y 16
+-#define SHIFT_STEP STAGE_WIDTH / ACTORS_X
+ 
+ typedef struct _State State;
+ 
+@@ -20,84 +19,11 @@ struct _State
+   gboolean pass;
+ };
+ 
+-struct _ShiftEffect
+-{
+-  ClutterShaderEffect parent_instance;
+-};
+-
+-struct _ShiftEffectClass
+-{
+-  ClutterShaderEffectClass parent_class;
+-};
+-
+-typedef struct _ShiftEffect       ShiftEffect;
+-typedef struct _ShiftEffectClass  ShiftEffectClass;
+-
+-#define TYPE_SHIFT_EFFECT        (shift_effect_get_type ())
+-
+-GType shift_effect_get_type (void);
+-
+-G_DEFINE_TYPE (ShiftEffect,
+-               shift_effect,
+-               CLUTTER_TYPE_SHADER_EFFECT);
+-
+-static void
+-shader_paint (ClutterEffect           *effect,
+-              ClutterEffectPaintFlags  flags)
+-{
+-  ClutterShaderEffect *shader = CLUTTER_SHADER_EFFECT (effect);
+-  float tex_width;
+-  ClutterActor *actor =
+-    clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect));
+-
+-  if (g_test_verbose ())
+-    g_debug ("shader_paint");
+-
+-  clutter_shader_effect_set_shader_source (shader,
+-    "uniform sampler2D tex;\n"
+-    "uniform float step;\n"
+-    "void main (void)\n"
+-    "{\n"
+-    "  cogl_color_out = texture2D(tex, vec2 (cogl_tex_coord_in[0].s + step,\n"
+-    "                                        cogl_tex_coord_in[0].t));\n"
+-    "}\n");
+-
+-  tex_width = clutter_actor_get_width (actor);
+-
+-  clutter_shader_effect_set_uniform (shader, "tex", G_TYPE_INT, 1, 0);
+-  clutter_shader_effect_set_uniform (shader, "step", G_TYPE_FLOAT, 1,
+-                                     SHIFT_STEP / tex_width);
+-
+-  CLUTTER_EFFECT_CLASS (shift_effect_parent_class)->paint (effect, flags);
+-}
+-
+-static void
+-shader_pick (ClutterEffect           *effect,
+-             ClutterEffectPaintFlags  flags)
+-{
+-  shader_paint (effect, flags);
+-}
+-
+-static void
+-shift_effect_class_init (ShiftEffectClass *klass)
+-{
+-  ClutterEffectClass *shader_class = CLUTTER_EFFECT_CLASS (klass);
+-
+-  shader_class->paint = shader_paint;
+-  shader_class->pick = shader_pick;
+-}
+-
+-static void
+-shift_effect_init (ShiftEffect *self)
+-{
+-}
+-
+ static const char *test_passes[] = {
+   "No covering actor",
+   "Invisible covering actor",
+   "Clipped covering actor",
+   "Blur effect",
+-  "Shift effect",
+ };
+ 
+ static gboolean
+@@ -165,30 +91,10 @@ on_timeout (gpointer data)
+           if (g_test_verbose ())
+             g_print ("With blur effect:\n");
+         }
+-      else if (test_num == 4)
+-        {
+-          if (!clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
+-            continue;
+-
+-          clutter_actor_hide (over_actor);
+-          clutter_actor_remove_effect_by_name (CLUTTER_ACTOR (state->stage),
+-                                               "blur");
+-
+-          clutter_actor_add_effect_with_name (CLUTTER_ACTOR (state->stage),
+-                                              "shift",
+-                                              g_object_new (TYPE_SHIFT_EFFECT,
+-                                                            NULL));
+-
+-          if (g_test_verbose ())
+-            g_print ("With shift effect:\n");
+-        }
+ 
+       for (y = 0; y < ACTORS_Y; y++)
+         {
+-          if (test_num == 4)
+-            x = 1;
+-          else
+-            x = 0;
++          x = 0;
+ 
+           for (; x < ACTORS_X; x++)
+             {
+@@ -198,9 +104,6 @@ on_timeout (gpointer data)
+ 
+               pick_x = x * state->actor_width + state->actor_width / 2;
+ 
+-              if (test_num == 4)
+-                pick_x -= SHIFT_STEP;
+-
+               actor =
+                 clutter_stage_get_actor_at_pos (CLUTTER_STAGE (state->stage),
+                                                 CLUTTER_PICK_ALL,
+diff --git a/clutter/tests/conform/meson.build b/clutter/tests/conform/meson.build
+index a9f2d7e20c..fffc9014c4 100644
+--- a/clutter/tests/conform/meson.build
++++ b/clutter/tests/conform/meson.build
+@@ -42,7 +42,6 @@ clutter_conform_tests_deprecated_tests = [
+   'behaviours',
+   'group',
+   'rectangle',
+-  'texture',
+ ]
+ 
+ clutter_conform_tests = []
+diff --git a/clutter/tests/conform/texture.c b/clutter/tests/conform/texture.c
+deleted file mode 100644
+index 392fd5c47e..0000000000
+--- a/clutter/tests/conform/texture.c
++++ /dev/null
+@@ -1,84 +0,0 @@
+-#define CLUTTER_DISABLE_DEPRECATION_WARNINGS
+-#include <clutter/clutter.h>
+-#include <string.h>
+-
+-static CoglHandle
+-make_texture (void)
+-{
+-  guint32 *data = g_malloc (100 * 100 * 4);
+-  int x;
+-  int y;
+-
+-  for (y = 0; y < 100; y ++)
+-    for (x = 0; x < 100; x++)
+-      {
+-        if (x < 50 && y < 50)
+-          data[y * 100 + x] = 0xff00ff00;
+-        else
+-          data[y * 100 + x] = 0xff00ffff;
+-      }
+-  return cogl_texture_new_from_data (100,
+-                                     100,
+-                                     COGL_TEXTURE_NONE,
+-                                     COGL_PIXEL_FORMAT_ARGB_8888,
+-                                     COGL_PIXEL_FORMAT_ARGB_8888,
+-                                     400,
+-                                     (guchar *)data);
+-}
+-
+-static void
+-texture_pick_with_alpha (void)
+-{
+-  ClutterTexture *tex = CLUTTER_TEXTURE (clutter_texture_new ());
+-  ClutterStage *stage = CLUTTER_STAGE (clutter_test_get_stage ());
+-  ClutterActor *actor;
+-
+-  clutter_texture_set_cogl_texture (tex, make_texture ());
+-
+-  clutter_actor_add_child (CLUTTER_ACTOR (stage), CLUTTER_ACTOR (tex));
+-
+-  clutter_actor_show (CLUTTER_ACTOR (stage));
+-
+-  if (g_test_verbose ())
+-    {
+-      g_print ("\nstage = %p\n", stage);
+-      g_print ("texture = %p\n\n", tex);
+-    }
+-
+-  clutter_texture_set_pick_with_alpha (tex, TRUE);
+-  if (g_test_verbose ())
+-    g_print ("Testing with pick-with-alpha enabled:\n");
+-
+-  /* This should fall through and hit the stage: */
+-  actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 10);
+-  if (g_test_verbose ())
+-    g_print ("actor @ (10, 10) = %p\n", actor);
+-  g_assert (actor == CLUTTER_ACTOR (stage));
+-
+-  /* The rest should hit the texture */
+-  actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 90, 10);
+-  if (g_test_verbose ())
+-    g_print ("actor @ (90, 10) = %p\n", actor);
+-  g_assert (actor == CLUTTER_ACTOR (tex));
+-  actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 90, 90);
+-  if (g_test_verbose ())
+-    g_print ("actor @ (90, 90) = %p\n", actor);
+-  g_assert (actor == CLUTTER_ACTOR (tex));
+-  actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 90);
+-  if (g_test_verbose ())
+-    g_print ("actor @ (10, 90) = %p\n", actor);
+-  g_assert (actor == CLUTTER_ACTOR (tex));
+-
+-  clutter_texture_set_pick_with_alpha (tex, FALSE);
+-  if (g_test_verbose ())
+-    g_print ("Testing with pick-with-alpha disabled:\n");
+-
+-  actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 10);
+-  if (g_test_verbose ())
+-    g_print ("actor @ (10, 10) = %p\n", actor);
+-  g_assert (actor == CLUTTER_ACTOR (tex));
+-}
+-
+-CLUTTER_TEST_SUITE (
+-  CLUTTER_TEST_UNIT ("/texture/pick-with-alpha", texture_pick_with_alpha)
+-)
+diff --git a/src/compositor/meta-surface-actor.c b/src/compositor/meta-surface-actor.c
+index ca4ca19a99..814199145a 100644
+--- a/src/compositor/meta-surface-actor.c
++++ b/src/compositor/meta-surface-actor.c
+@@ -70,38 +70,23 @@ meta_surface_actor_pick (ClutterActor       *actor,
+   else
+     {
+       int n_rects;
+-      float *rectangles;
+       int i;
+-      CoglPipeline *pipeline;
+-      CoglContext *ctx;
+-      CoglFramebuffer *fb;
+-      CoglColor cogl_color;
+ 
+       n_rects = cairo_region_num_rectangles (priv->input_region);
+-      rectangles = g_alloca (sizeof (float) * 4 * n_rects);
+ 
+       for (i = 0; i < n_rects; i++)
+         {
+           cairo_rectangle_int_t rect;
+-          int pos = i * 4;
++          ClutterActorBox box;
+ 
+           cairo_region_get_rectangle (priv->input_region, i, &rect);
+ 
+-          rectangles[pos + 0] = rect.x;
+-          rectangles[pos + 1] = rect.y;
+-          rectangles[pos + 2] = rect.x + rect.width;
+-          rectangles[pos + 3] = rect.y + rect.height;
++          box.x1 = rect.x;
++          box.y1 = rect.y;
++          box.x2 = rect.x + rect.width;
++          box.y2 = rect.y + rect.height;
++          clutter_actor_pick_box (actor, &box);
+         }
+-
+-      ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());
+-      fb = cogl_get_draw_framebuffer ();
+-
+-      cogl_color_init_from_4ub (&cogl_color, color->red, color->green, color->blue, color->alpha);
+-
+-      pipeline = cogl_pipeline_new (ctx);
+-      cogl_pipeline_set_color (pipeline, &cogl_color);
+-      cogl_framebuffer_draw_rectangles (fb, pipeline, rectangles, n_rects);
+-      cogl_object_unref (pipeline);
+     }
+ 
+   clutter_actor_iter_init (&iter, actor);
+-- 
+2.29.2
+
+
+From 254e93de8d60393ca94fa430c0acc6f6a7b9516e Mon Sep 17 00:00:00 2001
+From: Iain Lane <laney@debian.org>
+Date: Mon, 9 Sep 2019 10:17:22 +0100
+Subject: [PATCH 3/4] build: Compile with `-ffloat-store` on x86 (32 bit)
+
+GCC's manpage says that this flag does the following:
+
+  Do not store floating-point variables in registers, and inhibit other
+  options that might change whether a floating-point value is taken from
+  a register or memory.
+
+  This option prevents undesirable excess precision on machines such as
+  the 68000 where the floating registers (of the 68881) keep more
+  precision than a "double" is supposed to have.  Similarly for the x86
+  architecture.  For most programs, the excess precision does only good,
+  but a few programs rely on the precise definition of IEEE floating
+  point.
+
+We rely on this behaviour in our fork of clutter. When performing
+floating point computations on x86, we are getting the wrong results
+because of this architecture's use of the CPU's extended (x87, non-IEEE
+confirming) precision by default. If we enable `-ffloat-store` here,
+then we'll get the same results everywhere by storing into variables
+instead of registers. This does not remove the need to be correct when
+handling floats, but it does mean we don't need to be more correct than
+the IEEE spec requires.
+
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/785
+---
+ meson.build | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/meson.build b/meson.build
+index 8ef592bc58..e1edb78ba7 100644
+--- a/meson.build
++++ b/meson.build
+@@ -267,6 +267,9 @@ foreach function : required_functions
+   endif
+ endforeach
+ 
++if host_machine.cpu_family() == 'x86'
++  add_project_arguments('-ffloat-store', language: 'c')
++endif
+ add_project_arguments('-D_GNU_SOURCE', language: 'c')
+ 
+ all_warnings = [
+-- 
+2.29.2
+
+
+From 2d42caef14772984344e62ce40957d3b40e1f2b6 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= <mail@3v1n0.net>
+Date: Thu, 19 Sep 2019 11:27:50 +0200
+Subject: [PATCH 4/4] stage: Compute view perspective when parameters changed
+
+Clutter stage used to compute the initial projection using a fixed z
+translation which wasn't matching the one we computed in
+calculate_z_translation().
+This caused to have a wrong initial projection on startup which was then
+correctly recomputed only at the first paint.
+
+However, since this calculation doesn't depend on view, but only on viewport
+size, perspective's fovy and z_near we can safely do this at startup and
+only when any of those parameters change.
+
+Then we can move the computation out _clutter_stage_maybe_setup_viewport()
+since the cogl framebuffer viewport sizes aren't affecting this.
+
+Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1639
+https://gitlab.gnome.org/GNOME/mutter/merge_requests/803
+---
+ clutter/clutter/clutter-stage.c | 104 +++++++++++++++-----------------
+ 1 file changed, 47 insertions(+), 57 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
+index 7d88d5752f..0cfa87486e 100644
+--- a/clutter/clutter/clutter-stage.c
++++ b/clutter/clutter/clutter-stage.c
+@@ -234,6 +234,7 @@ static void capture_view_into (ClutterStage          *stage,
+                                cairo_rectangle_int_t *rect,
+                                uint8_t               *data,
+                                int                    stride);
++static void clutter_stage_update_view_perspective (ClutterStage *stage);
+ 
+ static void clutter_container_iface_init (ClutterContainerIface *iface);
+ 
+@@ -2492,29 +2493,6 @@ clutter_stage_init (ClutterStage *self)
+   clutter_actor_set_background_color (CLUTTER_ACTOR (self),
+                                       &default_stage_color);
+ 
+-  priv->perspective.fovy   = 60.0; /* 60 Degrees */
+-  priv->perspective.aspect = (float) geom.width / (float) geom.height;
+-  priv->perspective.z_near = 0.1;
+-  priv->perspective.z_far  = 100.0;
+-
+-  cogl_matrix_init_identity (&priv->projection);
+-  cogl_matrix_perspective (&priv->projection,
+-                           priv->perspective.fovy,
+-                           priv->perspective.aspect,
+-                           priv->perspective.z_near,
+-                           priv->perspective.z_far);
+-  cogl_matrix_get_inverse (&priv->projection,
+-                           &priv->inverse_projection);
+-  cogl_matrix_init_identity (&priv->view);
+-  cogl_matrix_view_2d_in_perspective (&priv->view,
+-                                      priv->perspective.fovy,
+-                                      priv->perspective.aspect,
+-                                      priv->perspective.z_near,
+-                                      50, /* distance to 2d plane */
+-                                      geom.width,
+-                                      geom.height);
+-
+-
+   /* FIXME - remove for 2.0 */
+   priv->fog.z_near = 1.0;
+   priv->fog.z_far  = 2.0;
+@@ -2682,6 +2660,7 @@ clutter_stage_set_perspective (ClutterStage       *stage,
+   priv->has_custom_perspective = TRUE;
+ 
+   clutter_stage_set_perspective_internal (stage, perspective);
++  clutter_stage_update_view_perspective (stage);
+ }
+ 
+ /**
+@@ -2808,6 +2787,7 @@ _clutter_stage_set_viewport (ClutterStage *stage,
+   priv->viewport[2] = width;
+   priv->viewport[3] = height;
+ 
++  clutter_stage_update_view_perspective (stage);
+   _clutter_stage_dirty_viewport (stage);
+ 
+   queue_full_redraw (stage);
+@@ -3788,6 +3768,50 @@ calculate_z_translation (float z_near)
+        + z_near;
+ }
+ 
++static void
++clutter_stage_update_view_perspective (ClutterStage *stage)
++{
++  ClutterStagePrivate *priv = stage->priv;
++  ClutterPerspective perspective;
++  float z_2d;
++
++  perspective = priv->perspective;
++
++  /* Ideally we want to regenerate the perspective matrix whenever
++   * the size changes but if the user has provided a custom matrix
++   * then we don't want to override it */
++  if (!priv->has_custom_perspective)
++    {
++      perspective.fovy = 60.0; /* 60 Degrees */
++      perspective.z_near = 0.1;
++      perspective.aspect = priv->viewport[2] / priv->viewport[3];
++      z_2d = calculate_z_translation (perspective.z_near);
++
++      /* NB: z_2d is only enough room for 85% of the stage_height between
++       * the stage and the z_near plane. For behind the stage plane we
++       * want a more consistent gap of 10 times the stage_height before
++       * hitting the far plane so we calculate that relative to the final
++       * height of the stage plane at the z_2d_distance we got... */
++      perspective.z_far = z_2d +
++        tanf (_DEG_TO_RAD (perspective.fovy / 2.0f)) * z_2d * 20.0f;
++
++      clutter_stage_set_perspective_internal (stage, &perspective);
++    }
++  else
++    {
++      z_2d = calculate_z_translation (perspective.z_near);
++    }
++
++  cogl_matrix_init_identity (&priv->view);
++  cogl_matrix_view_2d_in_perspective (&priv->view,
++                                      perspective.fovy,
++                                      perspective.aspect,
++                                      perspective.z_near,
++                                      z_2d,
++                                      priv->viewport[2],
++                                      priv->viewport[3]);
++}
++
+ void
+ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+                                      ClutterStageView *view)
+@@ -3797,7 +3821,6 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+   if (clutter_stage_view_is_dirty_viewport (view))
+     {
+       cairo_rectangle_int_t view_layout;
+-      ClutterPerspective perspective;
+       float fb_scale;
+       float viewport_offset_x;
+       float viewport_offset_y;
+@@ -3805,7 +3828,6 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+       float viewport_y;
+       float viewport_width;
+       float viewport_height;
+-      float z_2d;
+ 
+       CLUTTER_NOTE (PAINT,
+                     "Setting up the viewport { w:%f, h:%f }",
+@@ -3825,38 +3847,6 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+       clutter_stage_view_set_viewport (view,
+                                        viewport_x, viewport_y,
+                                        viewport_width, viewport_height);
+-
+-      perspective = priv->perspective;
+-
+-      /* Ideally we want to regenerate the perspective matrix whenever
+-       * the size changes but if the user has provided a custom matrix
+-       * then we don't want to override it */
+-      if (!priv->has_custom_perspective)
+-        {
+-          perspective.aspect = priv->viewport[2] / priv->viewport[3];
+-          z_2d = calculate_z_translation (perspective.z_near);
+-
+-          /* NB: z_2d is only enough room for 85% of the stage_height between
+-           * the stage and the z_near plane. For behind the stage plane we
+-           * want a more consistent gap of 10 times the stage_height before
+-           * hitting the far plane so we calculate that relative to the final
+-           * height of the stage plane at the z_2d_distance we got... */
+-          perspective.z_far = z_2d +
+-            tanf (_DEG_TO_RAD (perspective.fovy / 2.0f)) * z_2d * 20.0f;
+-
+-          clutter_stage_set_perspective_internal (stage, &perspective);
+-        }
+-      else
+-        z_2d = calculate_z_translation (perspective.z_near);
+-
+-      cogl_matrix_init_identity (&priv->view);
+-      cogl_matrix_view_2d_in_perspective (&priv->view,
+-                                          perspective.fovy,
+-                                          perspective.aspect,
+-                                          perspective.z_near,
+-                                          z_2d,
+-                                          priv->viewport[2],
+-                                          priv->viewport[3]);
+     }
+ 
+   if (clutter_stage_view_is_dirty_projection (view))
+-- 
+2.29.2
+
diff --git a/SOURCES/shadow-buffer-tile-damage.patch b/SOURCES/shadow-buffer-tile-damage.patch
new file mode 100644
index 0000000..ad19f8e
--- /dev/null
+++ b/SOURCES/shadow-buffer-tile-damage.patch
@@ -0,0 +1,3248 @@
+From e42c4e83283787062fb446a2aa698f227fe2db5f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 29 Apr 2020 16:26:52 +0200
+Subject: [PATCH 01/20] cogl/dma-buf-handle: Pass more metadata to handle
+ constructor
+
+Could be useful would one want to mmap the dmabuf and deal with its
+content manually in CPU space.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit e656d0caf01d8b012d2b458676e5658c540525dc)
+---
+ cogl/cogl/cogl-dma-buf-handle.c            | 45 +++++++++++++++++++++
+ cogl/cogl/cogl-dma-buf-handle.h            | 46 +++++++++++++++++++++-
+ src/backends/native/meta-renderer-native.c | 14 +++++--
+ 3 files changed, 101 insertions(+), 4 deletions(-)
+
+diff --git a/cogl/cogl/cogl-dma-buf-handle.c b/cogl/cogl/cogl-dma-buf-handle.c
+index 4a8f709f2c..d8b4e57c55 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.c
++++ b/cogl/cogl/cogl-dma-buf-handle.c
+@@ -40,6 +40,11 @@ struct _CoglDmaBufHandle
+ {
+   CoglFramebuffer *framebuffer;
+   int dmabuf_fd;
++  int width;
++  int height;
++  int stride;
++  int offset;
++  int bpp;
+   gpointer user_data;
+   GDestroyNotify destroy_func;
+ };
+@@ -47,6 +52,11 @@ struct _CoglDmaBufHandle
+ CoglDmaBufHandle *
+ cogl_dma_buf_handle_new (CoglFramebuffer *framebuffer,
+                          int              dmabuf_fd,
++                         int              width,
++                         int              height,
++                         int              stride,
++                         int              offset,
++                         int              bpp,
+                          gpointer         user_data,
+                          GDestroyNotify   destroy_func)
+ {
+@@ -61,6 +71,12 @@ cogl_dma_buf_handle_new (CoglFramebuffer *framebuffer,
+   dmabuf_handle->user_data = user_data;
+   dmabuf_handle->destroy_func = destroy_func;
+ 
++  dmabuf_handle->width = width;
++  dmabuf_handle->height = height;
++  dmabuf_handle->stride = stride;
++  dmabuf_handle->offset = offset;
++  dmabuf_handle->bpp = bpp;
++
+   return dmabuf_handle;
+ }
+ 
+@@ -92,3 +108,32 @@ cogl_dma_buf_handle_get_fd (CoglDmaBufHandle *dmabuf_handle)
+   return dmabuf_handle->dmabuf_fd;
+ }
+ 
++int
++cogl_dma_buf_handle_get_width (CoglDmaBufHandle *dmabuf_handle)
++{
++  return dmabuf_handle->width;
++}
++
++int
++cogl_dma_buf_handle_get_height (CoglDmaBufHandle *dmabuf_handle)
++{
++  return dmabuf_handle->height;
++}
++
++int
++cogl_dma_buf_handle_get_stride (CoglDmaBufHandle *dmabuf_handle)
++{
++  return dmabuf_handle->stride;
++}
++
++int
++cogl_dma_buf_handle_get_offset (CoglDmaBufHandle *dmabuf_handle)
++{
++  return dmabuf_handle->offset;
++}
++
++int
++cogl_dma_buf_handle_get_bpp (CoglDmaBufHandle *dmabuf_handle)
++{
++  return dmabuf_handle->bpp;
++}
+diff --git a/cogl/cogl/cogl-dma-buf-handle.h b/cogl/cogl/cogl-dma-buf-handle.h
+index 25b9b0ccb5..f64a20678d 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.h
++++ b/cogl/cogl/cogl-dma-buf-handle.h
+@@ -46,7 +46,12 @@
+ CoglDmaBufHandle *
+ cogl_dma_buf_handle_new (CoglFramebuffer *framebuffer,
+                          int              dmabuf_fd,
+-                         gpointer         data,
++                         int              width,
++                         int              height,
++                         int              stride,
++                         int              offset,
++                         int              bpp,
++                         gpointer         user_data,
+                          GDestroyNotify   destroy_func);
+ 
+ /**
+@@ -79,5 +84,44 @@ cogl_dma_buf_handle_get_framebuffer (CoglDmaBufHandle *dmabuf_handle);
+ int
+ cogl_dma_buf_handle_get_fd (CoglDmaBufHandle *dmabuf_handle);
+ 
++/**
++ * cogl_dmabuf_handle_get_width: (skip)
++ *
++ * Returns: the buffer width
++ */
++int
++cogl_dma_buf_handle_get_width (CoglDmaBufHandle *dmabuf_handle);
++
++/**
++ * cogl_dmabuf_handle_get_height: (skip)
++ *
++ * Returns: the buffer height
++ */
++int
++cogl_dma_buf_handle_get_height (CoglDmaBufHandle *dmabuf_handle);
++
++/**
++ * cogl_dmabuf_handle_get_stride: (skip)
++ *
++ * Returns: the buffer stride
++ */
++int
++cogl_dma_buf_handle_get_stride (CoglDmaBufHandle *dmabuf_handle);
++
++/**
++ * cogl_dmabuf_handle_get_offset: (skip)
++ *
++ * Returns: the buffer offset
++ */
++int
++cogl_dma_buf_handle_get_offset (CoglDmaBufHandle *dmabuf_handle);
++
++/**
++ * cogl_dmabuf_handle_get_bpp: (skip)
++ *
++ * Returns: the number of bytes per pixel
++ */
++int
++cogl_dma_buf_handle_get_bpp (CoglDmaBufHandle *dmabuf_handle);
+ 
+ #endif /* __COGL_DMA_BUF_HANDLE_H__ */
+diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c
+index 25833b6cf6..c14cb5acda 100644
+--- a/src/backends/native/meta-renderer-native.c
++++ b/src/backends/native/meta-renderer-native.c
+@@ -2641,6 +2641,9 @@ meta_renderer_native_create_dma_buf (CoglRenderer  *cogl_renderer,
+         CoglFramebuffer *dmabuf_fb;
+         CoglDmaBufHandle *dmabuf_handle;
+         struct gbm_bo *new_bo;
++        int stride;
++        int offset;
++        int bpp;
+         int dmabuf_fd = -1;
+ 
+         new_bo = gbm_bo_create (renderer_gpu_data->gbm.device,
+@@ -2664,11 +2667,14 @@ meta_renderer_native_create_dma_buf (CoglRenderer  *cogl_renderer,
+             return NULL;
+           }
+ 
++        stride = gbm_bo_get_stride (new_bo);
++        offset = gbm_bo_get_offset (new_bo, 0);
++        bpp = 4;
+         dmabuf_fb = create_dma_buf_framebuffer (renderer_native,
+                                                 dmabuf_fd,
+                                                 width, height,
+-                                                gbm_bo_get_stride (new_bo),
+-                                                gbm_bo_get_offset (new_bo, 0),
++                                                stride,
++                                                offset,
+                                                 DRM_FORMAT_MOD_LINEAR,
+                                                 DRM_FORMAT_XRGB8888,
+                                                 error);
+@@ -2677,7 +2683,9 @@ meta_renderer_native_create_dma_buf (CoglRenderer  *cogl_renderer,
+           return NULL;
+ 
+         dmabuf_handle =
+-          cogl_dma_buf_handle_new (dmabuf_fb, dmabuf_fd, new_bo,
++          cogl_dma_buf_handle_new (dmabuf_fb, dmabuf_fd,
++                                   width, height, stride, offset, bpp,
++                                   new_bo,
+                                    (GDestroyNotify) gbm_bo_destroy);
+         cogl_object_unref (dmabuf_fb);
+         return dmabuf_handle;
+-- 
+2.28.0
+
+
+From 2270f6dcf7b1e70386f5b4242f92bf5735bb88ba Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 30 Apr 2020 10:42:30 +0200
+Subject: [PATCH 02/20] clutter/stage-view: Add name property
+
+Will be used for logging to identify what view a log entry concerns. For
+the native and nested backend this is the name of the output the CRTC is
+assigned to drive; for X11 it's just "X11 screen", and for the legacy
+"X11 screen" emulation mode of the nested backend it's called "legacy
+nested".
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit c86367199febdee10ecd7ba24c69b6dda52cb896)
+---
+ clutter/clutter/clutter-stage-view.c           | 18 ++++++++++++++++++
+ src/backends/native/meta-renderer-native.c     |  3 +++
+ .../x11/nested/meta-renderer-x11-nested.c      |  4 ++++
+ 3 files changed, 25 insertions(+)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 0fad6fc446..6b543b5d51 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -27,6 +27,7 @@ enum
+ {
+   PROP_0,
+ 
++  PROP_NAME,
+   PROP_LAYOUT,
+   PROP_FRAMEBUFFER,
+   PROP_OFFSCREEN,
+@@ -40,6 +41,8 @@ static GParamSpec *obj_props[PROP_LAST];
+ 
+ typedef struct _ClutterStageViewPrivate
+ {
++  char *name;
++
+   cairo_rectangle_int_t layout;
+   float scale;
+   CoglFramebuffer *framebuffer;
+@@ -339,6 +342,9 @@ clutter_stage_view_get_property (GObject    *object,
+ 
+   switch (prop_id)
+     {
++    case PROP_NAME:
++      g_value_set_string (value, priv->name);
++      break;
+     case PROP_LAYOUT:
+       g_value_set_boxed (value, &priv->layout);
+       break;
+@@ -372,6 +378,9 @@ clutter_stage_view_set_property (GObject      *object,
+ 
+   switch (prop_id)
+     {
++    case PROP_NAME:
++      priv->name = g_value_dup_string (value);
++      break;
+     case PROP_LAYOUT:
+       layout = g_value_get_boxed (value);
+       priv->layout = *layout;
+@@ -414,6 +423,7 @@ clutter_stage_view_dispose (GObject *object)
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+ 
++  g_clear_pointer (&priv->name, g_free);
+   g_clear_pointer (&priv->framebuffer, cogl_object_unref);
+   g_clear_pointer (&priv->shadowfb, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen, cogl_object_unref);
+@@ -446,6 +456,14 @@ clutter_stage_view_class_init (ClutterStageViewClass *klass)
+   object_class->set_property = clutter_stage_view_set_property;
+   object_class->dispose = clutter_stage_view_dispose;
+ 
++  obj_props[PROP_NAME] =
++    g_param_spec_string ("name",
++                         "Name",
++                         "Name of view",
++                         NULL,
++                         G_PARAM_READWRITE |
++                         G_PARAM_CONSTRUCT_ONLY |
++                         G_PARAM_STATIC_STRINGS);
+   obj_props[PROP_LAYOUT] =
+     g_param_spec_boxed ("layout",
+                         "View layout",
+diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c
+index c14cb5acda..d3fb5b3c55 100644
+--- a/src/backends/native/meta-renderer-native.c
++++ b/src/backends/native/meta-renderer-native.c
+@@ -3679,6 +3679,7 @@ meta_renderer_native_create_view (MetaRenderer       *renderer,
+   CoglContext *cogl_context =
+     cogl_context_from_renderer_native (renderer_native);
+   CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
++  MetaMonitor *monitor;
+   CoglDisplayEGL *cogl_display_egl;
+   CoglOnscreenEGL *onscreen_egl;
+   MetaMonitorTransform view_transform;
+@@ -3742,7 +3743,9 @@ meta_renderer_native_create_view (MetaRenderer       *renderer,
+         g_error ("Failed to allocate shadow buffer texture: %s", error->message);
+     }
+ 
++  monitor = meta_logical_monitor_get_monitors (logical_monitor)->data;
+   view = g_object_new (META_TYPE_RENDERER_VIEW,
++                       "name", meta_monitor_get_connector (monitor),
+                        "layout", &logical_monitor->rect,
+                        "scale", scale,
+                        "framebuffer", onscreen,
+diff --git a/src/backends/x11/nested/meta-renderer-x11-nested.c b/src/backends/x11/nested/meta-renderer-x11-nested.c
+index 5000bf3579..f3a5547dbb 100644
+--- a/src/backends/x11/nested/meta-renderer-x11-nested.c
++++ b/src/backends/x11/nested/meta-renderer-x11-nested.c
+@@ -163,6 +163,7 @@ meta_renderer_x11_nested_ensure_legacy_view (MetaRendererX11Nested *renderer_x11
+     .height = height
+   };
+   legacy_view = g_object_new (META_TYPE_RENDERER_VIEW,
++                              "name", "legacy nested",
+                               "layout", &view_layout,
+                               "framebuffer", COGL_FRAMEBUFFER (fake_onscreen),
+                               NULL);
+@@ -179,6 +180,7 @@ meta_renderer_x11_nested_create_view (MetaRenderer       *renderer,
+     meta_backend_get_monitor_manager (backend);
+   ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
+   CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend);
++  MetaMonitor *monitor;
+   MetaMonitorTransform view_transform;
+   float view_scale;
+   int width, height;
+@@ -212,7 +214,9 @@ meta_renderer_x11_nested_create_view (MetaRenderer       *renderer,
+   else
+     offscreen = NULL;
+ 
++  monitor = meta_logical_monitor_get_monitors (logical_monitor)->data;
+   return g_object_new (META_TYPE_RENDERER_VIEW,
++                       "name", meta_monitor_get_connector (monitor),
+                        "layout", &logical_monitor->rect,
+                        "framebuffer", COGL_FRAMEBUFFER (fake_onscreen),
+                        "offscreen", COGL_FRAMEBUFFER (offscreen),
+-- 
+2.28.0
+
+
+From 6716fde14c5b1a00a02a80b46db67d3f236a87c1 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 30 Apr 2020 17:53:30 +0200
+Subject: [PATCH 03/20] renderer-native: Move shadow fb construction to the
+ stage view
+
+The stage view will need a more involved approach to shadow buffers, in
+order to implement things such double buffered shadow buffers and damage
+detection.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 3ab89be574f0e02dc67e1b1f538bb24f94612bcf)
+---
+ clutter/clutter/clutter-stage-view.c       | 115 ++++++++++++++++++---
+ src/backends/native/meta-renderer-native.c |  25 +----
+ 2 files changed, 106 insertions(+), 34 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 6b543b5d51..db0067297c 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -31,7 +31,7 @@ enum
+   PROP_LAYOUT,
+   PROP_FRAMEBUFFER,
+   PROP_OFFSCREEN,
+-  PROP_SHADOWFB,
++  PROP_USE_SHADOWFB,
+   PROP_SCALE,
+ 
+   PROP_LAST
+@@ -50,6 +50,7 @@ typedef struct _ClutterStageViewPrivate
+   CoglOffscreen *offscreen;
+   CoglPipeline *offscreen_pipeline;
+ 
++  gboolean use_shadowfb;
+   CoglOffscreen *shadowfb;
+   CoglPipeline *shadowfb_pipeline;
+ 
+@@ -206,6 +207,80 @@ clutter_stage_view_copy_to_framebuffer (ClutterStageView            *view,
+   cogl_framebuffer_pop_matrix (dst_framebuffer);
+ }
+ 
++static CoglOffscreen *
++create_offscreen_framebuffer (CoglContext  *context,
++                              int           width,
++                              int           height,
++                              GError      **error)
++{
++  CoglOffscreen *framebuffer;
++  CoglTexture2D *texture;
++
++  texture = cogl_texture_2d_new_with_size (context, width, height);
++  cogl_primitive_texture_set_auto_mipmap (COGL_PRIMITIVE_TEXTURE (texture),
++                                          FALSE);
++
++  if (!cogl_texture_allocate (COGL_TEXTURE (texture), error))
++    {
++      cogl_object_unref (texture);
++      return FALSE;
++    }
++
++  framebuffer = cogl_offscreen_new_with_texture (COGL_TEXTURE (texture));
++  cogl_object_unref (texture);
++  if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (framebuffer), error))
++    {
++      cogl_object_unref (framebuffer);
++      return FALSE;
++    }
++
++  return framebuffer;
++}
++
++static gboolean
++init_offscreen_shadowfb (ClutterStageView  *view,
++                         CoglContext       *cogl_context,
++                         int                width,
++                         int                height,
++                         GError           **error)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  CoglOffscreen *offscreen;
++
++  offscreen = create_offscreen_framebuffer (cogl_context, width, height, error);
++  if (!offscreen)
++    return FALSE;
++
++  priv->shadowfb = offscreen;
++  return TRUE;
++}
++
++static void
++init_shadowfb (ClutterStageView *view)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  g_autoptr (GError) error = NULL;
++  int width;
++  int height;
++  CoglContext *cogl_context;
++
++  width = cogl_framebuffer_get_width (priv->framebuffer);
++  height = cogl_framebuffer_get_height (priv->framebuffer);
++  cogl_context = cogl_framebuffer_get_context (priv->framebuffer);
++
++  if (!init_offscreen_shadowfb (view, cogl_context, width, height, &error))
++    {
++      g_warning ("Failed to initialize single buffered shadow fb for %s: %s",
++                 priv->name, error->message);
++    }
++  else
++    {
++      g_message ("Initialized single buffered shadow fb for %s", priv->name);
++    }
++}
++
+ void
+ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+                                    const cairo_rectangle_int_t *rect)
+@@ -354,8 +429,8 @@ clutter_stage_view_get_property (GObject    *object,
+     case PROP_OFFSCREEN:
+       g_value_set_boxed (value, priv->offscreen);
+       break;
+-    case PROP_SHADOWFB:
+-      g_value_set_boxed (value, priv->shadowfb);
++    case PROP_USE_SHADOWFB:
++      g_value_set_boolean (value, priv->use_shadowfb);
+       break;
+     case PROP_SCALE:
+       g_value_set_float (value, priv->scale);
+@@ -405,8 +480,8 @@ clutter_stage_view_set_property (GObject      *object,
+     case PROP_OFFSCREEN:
+       priv->offscreen = g_value_dup_boxed (value);
+       break;
+-    case PROP_SHADOWFB:
+-      priv->shadowfb = g_value_dup_boxed (value);
++    case PROP_USE_SHADOWFB:
++      priv->use_shadowfb = g_value_get_boolean (value);
+       break;
+     case PROP_SCALE:
+       priv->scale = g_value_get_float (value);
+@@ -416,6 +491,19 @@ clutter_stage_view_set_property (GObject      *object,
+     }
+ }
+ 
++static void
++clutter_stage_view_constructed (GObject *object)
++{
++  ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++
++  if (priv->use_shadowfb)
++    init_shadowfb (view);
++
++  G_OBJECT_CLASS (clutter_stage_view_parent_class)->constructed (object);
++}
++
+ static void
+ clutter_stage_view_dispose (GObject *object)
+ {
+@@ -454,6 +542,7 @@ clutter_stage_view_class_init (ClutterStageViewClass *klass)
+ 
+   object_class->get_property = clutter_stage_view_get_property;
+   object_class->set_property = clutter_stage_view_set_property;
++  object_class->constructed = clutter_stage_view_constructed;
+   object_class->dispose = clutter_stage_view_dispose;
+ 
+   obj_props[PROP_NAME] =
+@@ -491,14 +580,14 @@ clutter_stage_view_class_init (ClutterStageViewClass *klass)
+                         G_PARAM_CONSTRUCT_ONLY |
+                         G_PARAM_STATIC_STRINGS);
+ 
+-  obj_props[PROP_SHADOWFB] =
+-    g_param_spec_boxed ("shadowfb",
+-                        "Shadow framebuffer",
+-                        "Framebuffer used as intermediate shadow buffer",
+-                        COGL_TYPE_HANDLE,
+-                        G_PARAM_READWRITE |
+-                        G_PARAM_CONSTRUCT_ONLY |
+-                        G_PARAM_STATIC_STRINGS);
++  obj_props[PROP_USE_SHADOWFB] =
++    g_param_spec_boolean ("use-shadowfb",
++                          "Use shadowfb",
++                          "Whether to use one or more shadow framebuffers",
++                          FALSE,
++                          G_PARAM_READWRITE |
++                          G_PARAM_CONSTRUCT_ONLY |
++                          G_PARAM_STATIC_STRINGS);
+ 
+   obj_props[PROP_SCALE] =
+     g_param_spec_float ("scale",
+diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c
+index d3fb5b3c55..463dddd3a7 100644
+--- a/src/backends/native/meta-renderer-native.c
++++ b/src/backends/native/meta-renderer-native.c
+@@ -3685,7 +3685,7 @@ meta_renderer_native_create_view (MetaRenderer       *renderer,
+   MetaMonitorTransform view_transform;
+   CoglOnscreen *onscreen = NULL;
+   CoglOffscreen *offscreen = NULL;
+-  CoglOffscreen *shadowfb = NULL;
++  gboolean use_shadowfb;
+   float scale;
+   int width, height;
+   MetaRendererView *view;
+@@ -3724,24 +3724,8 @@ meta_renderer_native_create_view (MetaRenderer       *renderer,
+ 
+     }
+ 
+-  if (should_force_shadow_fb (renderer_native,
+-                              renderer_native->primary_gpu_kms))
+-    {
+-      int shadow_width;
+-      int shadow_height;
+-
+-      /* The shadowfb must be the same size as the on-screen framebuffer */
+-      shadow_width = cogl_framebuffer_get_width (COGL_FRAMEBUFFER (onscreen));
+-      shadow_height = cogl_framebuffer_get_height (COGL_FRAMEBUFFER (onscreen));
+-
+-      shadowfb = meta_renderer_native_create_offscreen (renderer_native,
+-                                                        cogl_context,
+-                                                        shadow_width,
+-                                                        shadow_height,
+-                                                        &error);
+-      if (!shadowfb)
+-        g_error ("Failed to allocate shadow buffer texture: %s", error->message);
+-    }
++  use_shadowfb = should_force_shadow_fb (renderer_native,
++                                         renderer_native->primary_gpu_kms);
+ 
+   monitor = meta_logical_monitor_get_monitors (logical_monitor)->data;
+   view = g_object_new (META_TYPE_RENDERER_VIEW,
+@@ -3750,12 +3734,11 @@ meta_renderer_native_create_view (MetaRenderer       *renderer,
+                        "scale", scale,
+                        "framebuffer", onscreen,
+                        "offscreen", offscreen,
+-                       "shadowfb", shadowfb,
++                       "use-shadowfb", use_shadowfb,
+                        "logical-monitor", logical_monitor,
+                        "transform", view_transform,
+                        NULL);
+   g_clear_pointer (&offscreen, cogl_object_unref);
+-  g_clear_pointer (&shadowfb, cogl_object_unref);
+ 
+   meta_onscreen_native_set_view (onscreen, view);
+ 
+-- 
+2.28.0
+
+
+From f79b37583e575d34edb4b7965cb0e48eb2736749 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 30 Apr 2020 18:19:30 +0200
+Subject: [PATCH 04/20] clutter/stage-view: Move shadowfb struct fields into
+ anonymous struct
+
+With the aim to collect shadow buffer related things in one place, place
+them in an anonymous struct.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 310ca695d90b48074a06327e87bd7e924f49cb7f)
+---
+ clutter/clutter/clutter-stage-view.c | 32 +++++++++++++++-------------
+ 1 file changed, 17 insertions(+), 15 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index db0067297c..9bbe158f36 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -51,8 +51,10 @@ typedef struct _ClutterStageViewPrivate
+   CoglPipeline *offscreen_pipeline;
+ 
+   gboolean use_shadowfb;
+-  CoglOffscreen *shadowfb;
+-  CoglPipeline *shadowfb_pipeline;
++  struct {
++    CoglOffscreen *framebuffer;
++    CoglPipeline *pipeline;
++  } shadow;
+ 
+   guint dirty_viewport   : 1;
+   guint dirty_projection : 1;
+@@ -86,8 +88,8 @@ clutter_stage_view_get_framebuffer (ClutterStageView *view)
+ 
+   if (priv->offscreen)
+     return priv->offscreen;
+-  else if (priv->shadowfb)
+-    return priv->shadowfb;
++  else if (priv->shadow.framebuffer)
++    return priv->shadow.framebuffer;
+   else
+     return priv->framebuffer;
+ }
+@@ -153,11 +155,11 @@ clutter_stage_view_ensure_shadowfb_blit_pipeline (ClutterStageView *view)
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+ 
+-  if (priv->shadowfb_pipeline)
++  if (priv->shadow.pipeline)
+     return;
+ 
+-  priv->shadowfb_pipeline =
+-    clutter_stage_view_create_framebuffer_pipeline (priv->shadowfb);
++  priv->shadow.pipeline =
++    clutter_stage_view_create_framebuffer_pipeline (priv->shadow.framebuffer);
+ }
+ 
+ void
+@@ -252,7 +254,7 @@ init_offscreen_shadowfb (ClutterStageView  *view,
+   if (!offscreen)
+     return FALSE;
+ 
+-  priv->shadowfb = offscreen;
++  priv->shadow.framebuffer = offscreen;
+   return TRUE;
+ }
+ 
+@@ -297,13 +299,13 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+       clutter_stage_view_get_offscreen_transformation_matrix (view, &matrix);
+       can_blit = cogl_matrix_is_identity (&matrix);
+ 
+-      if (priv->shadowfb)
++      if (priv->shadow.framebuffer)
+         {
+           clutter_stage_view_copy_to_framebuffer (view,
+                                                   rect,
+                                                   priv->offscreen_pipeline,
+                                                   priv->offscreen,
+-                                                  priv->shadowfb,
++                                                  priv->shadow.framebuffer,
+                                                   can_blit);
+         }
+       else
+@@ -317,13 +319,13 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+         }
+     }
+ 
+-  if (priv->shadowfb)
++  if (priv->shadow.framebuffer)
+     {
+       clutter_stage_view_ensure_shadowfb_blit_pipeline (view);
+       clutter_stage_view_copy_to_framebuffer (view,
+                                               rect,
+-                                              priv->shadowfb_pipeline,
+-                                              priv->shadowfb,
++                                              priv->shadow.pipeline,
++                                              priv->shadow.framebuffer,
+                                               priv->framebuffer,
+                                               TRUE);
+     }
+@@ -513,10 +515,10 @@ clutter_stage_view_dispose (GObject *object)
+ 
+   g_clear_pointer (&priv->name, g_free);
+   g_clear_pointer (&priv->framebuffer, cogl_object_unref);
+-  g_clear_pointer (&priv->shadowfb, cogl_object_unref);
++  g_clear_pointer (&priv->shadow.framebuffer, cogl_object_unref);
++  g_clear_pointer (&priv->shadow.pipeline, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
+-  g_clear_pointer (&priv->shadowfb_pipeline, cogl_object_unref);
+ 
+   G_OBJECT_CLASS (clutter_stage_view_parent_class)->dispose (object);
+ }
+-- 
+2.28.0
+
+
+From 7bf71e7b5f39fcf34c4a636640636f9452b4b06c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 30 Apr 2020 21:51:10 +0200
+Subject: [PATCH 05/20] clutter/stage-view: Move fb viewport and projection
+ setting to here
+
+The stage would fetch the front framebuffer and set the viewport and
+projection matrix, but if we are going to more than one front buffer,
+that won't work, so let the stage just pass the viewport and projection
+matrix to the view and have the view deal with the framebuffer(s).
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit c79bcf0d7e35cf9e85864cf72ea53659a6b8d8a7)
+---
+ clutter/clutter/clutter-stage-view-private.h |  8 ++++++
+ clutter/clutter/clutter-stage-view.c         | 29 ++++++++++++++++++++
+ clutter/clutter/clutter-stage.c              | 16 ++++-------
+ 3 files changed, 42 insertions(+), 11 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view-private.h b/clutter/clutter/clutter-stage-view-private.h
+index 89c42599fc..78aa37c9f4 100644
+--- a/clutter/clutter/clutter-stage-view-private.h
++++ b/clutter/clutter/clutter-stage-view-private.h
+@@ -28,10 +28,18 @@ gboolean clutter_stage_view_is_dirty_viewport (ClutterStageView *view);
+ void clutter_stage_view_set_dirty_viewport (ClutterStageView *view,
+                                             gboolean          dirty);
+ 
++void clutter_stage_view_set_viewport (ClutterStageView *view,
++                                      float             x,
++                                      float             y,
++                                      float             width,
++                                      float             height);
++
+ gboolean clutter_stage_view_is_dirty_projection (ClutterStageView *view);
+ 
+ void clutter_stage_view_set_dirty_projection (ClutterStageView *view,
+                                               gboolean          dirty);
+ 
++void clutter_stage_view_set_projection (ClutterStageView *view,
++                                        const CoglMatrix *matrix);
+ 
+ #endif /* __CLUTTER_STAGE_VIEW_PRIVATE_H__ */
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 9bbe158f36..4d8bbddc9d 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -359,6 +359,22 @@ clutter_stage_view_set_dirty_viewport (ClutterStageView *view,
+   priv->dirty_viewport = dirty;
+ }
+ 
++void
++clutter_stage_view_set_viewport (ClutterStageView *view,
++                                 float             x,
++                                 float             y,
++                                 float             width,
++                                 float             height)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  CoglFramebuffer *framebuffer;
++
++  priv->dirty_viewport = FALSE;
++  framebuffer = clutter_stage_view_get_framebuffer (view);
++  cogl_framebuffer_set_viewport (framebuffer, x, y, width, height);
++}
++
+ gboolean
+ clutter_stage_view_is_dirty_projection (ClutterStageView *view)
+ {
+@@ -378,6 +394,19 @@ clutter_stage_view_set_dirty_projection (ClutterStageView *view,
+   priv->dirty_projection = dirty;
+ }
+ 
++void
++clutter_stage_view_set_projection (ClutterStageView *view,
++                                   const CoglMatrix *matrix)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  CoglFramebuffer *framebuffer;
++
++  priv->dirty_projection = FALSE;
++  framebuffer = clutter_stage_view_get_framebuffer (view);
++  cogl_framebuffer_set_projection_matrix (framebuffer, matrix);
++}
++
+ void
+ clutter_stage_view_get_offscreen_transformation_matrix (ClutterStageView *view,
+                                                         CoglMatrix       *matrix)
+diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
+index 34c4e0119a..4bde234dbf 100644
+--- a/clutter/clutter/clutter-stage.c
++++ b/clutter/clutter/clutter-stage.c
+@@ -3687,7 +3687,6 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+                                      ClutterStageView *view)
+ {
+   ClutterStagePrivate *priv = stage->priv;
+-  CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view);
+ 
+   if (clutter_stage_view_is_dirty_viewport (view))
+     {
+@@ -3716,9 +3715,10 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+       viewport_y = roundf (priv->viewport[1] * fb_scale - viewport_offset_y);
+       viewport_width = roundf (priv->viewport[2] * fb_scale);
+       viewport_height = roundf (priv->viewport[3] * fb_scale);
+-      cogl_framebuffer_set_viewport (fb,
+-                                     viewport_x, viewport_y,
+-                                     viewport_width, viewport_height);
++
++      clutter_stage_view_set_viewport (view,
++                                       viewport_x, viewport_y,
++                                       viewport_width, viewport_height);
+ 
+       perspective = priv->perspective;
+ 
+@@ -3751,16 +3751,10 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+                                           z_2d,
+                                           priv->viewport[2],
+                                           priv->viewport[3]);
+-
+-      clutter_stage_view_set_dirty_viewport (view, FALSE);
+     }
+ 
+   if (clutter_stage_view_is_dirty_projection (view))
+-    {
+-      cogl_framebuffer_set_projection_matrix (fb, &priv->projection);
+-
+-      clutter_stage_view_set_dirty_projection (view, FALSE);
+-    }
++    clutter_stage_view_set_projection (view, &priv->projection);
+ }
+ 
+ #undef _DEG_TO_RAD
+-- 
+2.28.0
+
+
+From 0b345dc3a108f12ebc00e831692b43291c84cd07 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 30 Apr 2020 21:59:49 +0200
+Subject: [PATCH 06/20] clutter/stage-view: Change set_dirty..() API to
+ invalidate..()
+
+The manual "cleaning" of the viewport and projection state is removed,
+and we only ever try to invalidate the state so that it'll be updated
+next time. Change the API used to reflect this.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 3080ee672a366a3a52d9f43523c40e3afd08e874)
+---
+ clutter/clutter/clutter-stage-view-private.h |  6 ++----
+ clutter/clutter/clutter-stage-view.c         | 10 ++++------
+ clutter/clutter/clutter-stage.c              |  4 ++--
+ 3 files changed, 8 insertions(+), 12 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view-private.h b/clutter/clutter/clutter-stage-view-private.h
+index 78aa37c9f4..e27f140b8a 100644
+--- a/clutter/clutter/clutter-stage-view-private.h
++++ b/clutter/clutter/clutter-stage-view-private.h
+@@ -25,8 +25,7 @@ void clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+ 
+ gboolean clutter_stage_view_is_dirty_viewport (ClutterStageView *view);
+ 
+-void clutter_stage_view_set_dirty_viewport (ClutterStageView *view,
+-                                            gboolean          dirty);
++void clutter_stage_view_invalidate_viewport (ClutterStageView *view);
+ 
+ void clutter_stage_view_set_viewport (ClutterStageView *view,
+                                       float             x,
+@@ -36,8 +35,7 @@ void clutter_stage_view_set_viewport (ClutterStageView *view,
+ 
+ gboolean clutter_stage_view_is_dirty_projection (ClutterStageView *view);
+ 
+-void clutter_stage_view_set_dirty_projection (ClutterStageView *view,
+-                                              gboolean          dirty);
++void clutter_stage_view_invalidate_projection (ClutterStageView *view);
+ 
+ void clutter_stage_view_set_projection (ClutterStageView *view,
+                                         const CoglMatrix *matrix);
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 4d8bbddc9d..40edfad6e1 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -350,13 +350,12 @@ clutter_stage_view_is_dirty_viewport (ClutterStageView *view)
+ }
+ 
+ void
+-clutter_stage_view_set_dirty_viewport (ClutterStageView *view,
+-                                       gboolean          dirty)
++clutter_stage_view_invalidate_viewport (ClutterStageView *view)
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+ 
+-  priv->dirty_viewport = dirty;
++  priv->dirty_viewport = TRUE;
+ }
+ 
+ void
+@@ -385,13 +384,12 @@ clutter_stage_view_is_dirty_projection (ClutterStageView *view)
+ }
+ 
+ void
+-clutter_stage_view_set_dirty_projection (ClutterStageView *view,
+-                                         gboolean          dirty)
++clutter_stage_view_invalidate_projection (ClutterStageView *view)
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+ 
+-  priv->dirty_projection = dirty;
++  priv->dirty_projection = TRUE;
+ }
+ 
+ void
+diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
+index 4bde234dbf..aaa77d9ede 100644
+--- a/clutter/clutter/clutter-stage.c
++++ b/clutter/clutter/clutter-stage.c
+@@ -2636,7 +2636,7 @@ _clutter_stage_dirty_projection (ClutterStage *stage)
+     {
+       ClutterStageView *view = l->data;
+ 
+-      clutter_stage_view_set_dirty_projection (view, TRUE);
++      clutter_stage_view_invalidate_projection (view);
+     }
+ }
+ 
+@@ -2725,7 +2725,7 @@ _clutter_stage_dirty_viewport (ClutterStage *stage)
+     {
+       ClutterStageView *view = l->data;
+ 
+-      clutter_stage_view_set_dirty_viewport (view, TRUE);
++      clutter_stage_view_invalidate_viewport (view);
+     }
+ }
+ 
+-- 
+2.28.0
+
+
+From 32da7b5c31277c56089e4b3b8ccf43bc552e8974 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 17:05:36 +0200
+Subject: [PATCH 07/20] cogl: Make private BLIT_FRAMEBUFFER feature public
+
+Will be a requirement for enabling shadow buffers.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit b3153760bf81af07f5328ba07b0ff3009bd8305b)
+---
+ cogl/cogl/cogl-blit.c                       | 2 +-
+ cogl/cogl/cogl-context.h                    | 3 +++
+ cogl/cogl/cogl-framebuffer.c                | 2 +-
+ cogl/cogl/cogl-framebuffer.h                | 2 +-
+ cogl/cogl/cogl-private.h                    | 1 -
+ cogl/cogl/driver/gl/cogl-framebuffer-gl.c   | 4 ++--
+ cogl/cogl/driver/gl/gl/cogl-driver-gl.c     | 4 ++--
+ cogl/cogl/driver/gl/gles/cogl-driver-gles.c | 4 ++--
+ 8 files changed, 12 insertions(+), 10 deletions(-)
+
+diff --git a/cogl/cogl/cogl-blit.c b/cogl/cogl/cogl-blit.c
+index ae5a8a345d..dd5fffff37 100644
+--- a/cogl/cogl/cogl-blit.c
++++ b/cogl/cogl/cogl-blit.c
+@@ -158,7 +158,7 @@ _cogl_blit_framebuffer_begin (CoglBlitData *data)
+      supported. */
+   if ((_cogl_texture_get_format (data->src_tex) & COGL_PREMULT_BIT) !=
+       (_cogl_texture_get_format (data->dst_tex) & COGL_PREMULT_BIT) ||
+-      !_cogl_has_private_feature (ctx, COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER))
++      !cogl_has_feature (ctx, COGL_FEATURE_ID_BLIT_FRAMEBUFFER))
+     return FALSE;
+ 
+   dst_offscreen = _cogl_offscreen_new_with_texture_full
+diff --git a/cogl/cogl/cogl-context.h b/cogl/cogl/cogl-context.h
+index d4104625e6..ec90491e94 100644
+--- a/cogl/cogl/cogl-context.h
++++ b/cogl/cogl/cogl-context.h
+@@ -227,6 +227,8 @@ cogl_is_context (void *object);
+  *     the depth buffer to a texture.
+  * @COGL_FEATURE_ID_PRESENTATION_TIME: Whether frame presentation
+  *    time stamps will be recorded in #CoglFrameInfo objects.
++ * @COGL_FEATURE_ID_BLIT_FRAMEBUFFER: Whether blitting using
++ *    cogl_blit_framebuffer() is supported.
+  *
+  * All the capabilities that can vary between different GPUs supported
+  * by Cogl. Applications that depend on any of these features should explicitly
+@@ -261,6 +263,7 @@ typedef enum _CoglFeatureID
+   COGL_FEATURE_ID_TEXTURE_RG,
+   COGL_FEATURE_ID_BUFFER_AGE,
+   COGL_FEATURE_ID_TEXTURE_EGL_IMAGE_EXTERNAL,
++  COGL_FEATURE_ID_BLIT_FRAMEBUFFER,
+ 
+   /*< private >*/
+   _COGL_N_FEATURE_IDS   /*< skip >*/
+diff --git a/cogl/cogl/cogl-framebuffer.c b/cogl/cogl/cogl-framebuffer.c
+index d64fc89fb6..fffac3f685 100644
+--- a/cogl/cogl/cogl-framebuffer.c
++++ b/cogl/cogl/cogl-framebuffer.c
+@@ -1464,7 +1464,7 @@ cogl_blit_framebuffer (CoglFramebuffer *src,
+   int src_x1, src_y1, src_x2, src_y2;
+   int dst_x1, dst_y1, dst_x2, dst_y2;
+ 
+-  if (!_cogl_has_private_feature (ctx, COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER))
++  if (!cogl_has_feature (ctx, COGL_FEATURE_ID_BLIT_FRAMEBUFFER))
+     {
+       g_set_error_literal (error, COGL_SYSTEM_ERROR,
+                            COGL_SYSTEM_ERROR_UNSUPPORTED,
+diff --git a/cogl/cogl/cogl-framebuffer.h b/cogl/cogl/cogl-framebuffer.h
+index 38ada9feb7..c347076919 100644
+--- a/cogl/cogl/cogl-framebuffer.h
++++ b/cogl/cogl/cogl-framebuffer.h
+@@ -1863,7 +1863,7 @@ cogl_is_framebuffer (void *object);
+  *
+  * This blits a region of the color buffer of the source buffer
+  * to the destination buffer. This function should only be
+- * called if the COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER feature is
++ * called if the COGL_FEATURE_ID_BLIT_FRAMEBUFFER feature is
+  * advertised.
+  *
+  * The source and destination rectangles are defined in offscreen
+diff --git a/cogl/cogl/cogl-private.h b/cogl/cogl/cogl-private.h
+index d9fbe68c76..07ac7eb2d8 100644
+--- a/cogl/cogl/cogl-private.h
++++ b/cogl/cogl/cogl-private.h
+@@ -42,7 +42,6 @@ typedef enum
+ {
+   COGL_PRIVATE_FEATURE_TEXTURE_2D_FROM_EGL_IMAGE,
+   COGL_PRIVATE_FEATURE_MESA_PACK_INVERT,
+-  COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER,
+   COGL_PRIVATE_FEATURE_FOUR_CLIP_PLANES,
+   COGL_PRIVATE_FEATURE_PBOS,
+   COGL_PRIVATE_FEATURE_VBOS,
+diff --git a/cogl/cogl/driver/gl/cogl-framebuffer-gl.c b/cogl/cogl/driver/gl/cogl-framebuffer-gl.c
+index 6466fd6bcf..2c0613462f 100644
+--- a/cogl/cogl/driver/gl/cogl-framebuffer-gl.c
++++ b/cogl/cogl/driver/gl/cogl-framebuffer-gl.c
+@@ -401,8 +401,8 @@ _cogl_framebuffer_gl_flush_state (CoglFramebuffer *draw_buffer,
+         {
+           /* NB: Currently we only take advantage of binding separate
+            * read/write buffers for framebuffer blit purposes. */
+-          _COGL_RETURN_IF_FAIL (_cogl_has_private_feature
+-                                (ctx, COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER));
++          _COGL_RETURN_IF_FAIL (cogl_has_feature
++                                (ctx, COGL_FEATURE_ID_BLIT_FRAMEBUFFER));
+ 
+           _cogl_framebuffer_gl_bind (draw_buffer, GL_DRAW_FRAMEBUFFER);
+           _cogl_framebuffer_gl_bind (read_buffer, GL_READ_FRAMEBUFFER);
+diff --git a/cogl/cogl/driver/gl/gl/cogl-driver-gl.c b/cogl/cogl/driver/gl/gl/cogl-driver-gl.c
+index 716617b54b..f905267c53 100644
+--- a/cogl/cogl/driver/gl/gl/cogl-driver-gl.c
++++ b/cogl/cogl/driver/gl/gl/cogl-driver-gl.c
+@@ -466,8 +466,8 @@ _cogl_driver_update_features (CoglContext *ctx,
+     }
+ 
+   if (ctx->glBlitFramebuffer)
+-    COGL_FLAGS_SET (private_features,
+-                    COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER, TRUE);
++    COGL_FLAGS_SET (ctx->features,
++                    COGL_FEATURE_ID_BLIT_FRAMEBUFFER, TRUE);
+ 
+   if (ctx->glRenderbufferStorageMultisampleIMG)
+     {
+diff --git a/cogl/cogl/driver/gl/gles/cogl-driver-gles.c b/cogl/cogl/driver/gl/gles/cogl-driver-gles.c
+index 902bd0bd3a..e55bb302c4 100644
+--- a/cogl/cogl/driver/gl/gles/cogl-driver-gles.c
++++ b/cogl/cogl/driver/gl/gles/cogl-driver-gles.c
+@@ -325,8 +325,8 @@ _cogl_driver_update_features (CoglContext *context,
+     }
+ 
+   if (context->glBlitFramebuffer)
+-    COGL_FLAGS_SET (private_features,
+-                    COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER, TRUE);
++    COGL_FLAGS_SET (context->features,
++                    COGL_FEATURE_ID_BLIT_FRAMEBUFFER, TRUE);
+ 
+   if (_cogl_check_extension ("GL_OES_element_index_uint", gl_extensions))
+     {
+-- 
+2.28.0
+
+
+From 32aa92e50a12a5fd9652866937750a3c86c4845f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 17:06:35 +0200
+Subject: [PATCH 08/20] renderer/native: Only enable shadowfbs if we can blit
+
+There is no point in enabling shadow buffers if we can't as that'd be
+even slower than not having them at all.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit f191c3b74f572547707fcb6522db76a88689eae2)
+---
+ src/backends/native/meta-renderer-native.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c
+index 463dddd3a7..62ca4bcbd4 100644
+--- a/src/backends/native/meta-renderer-native.c
++++ b/src/backends/native/meta-renderer-native.c
+@@ -3649,6 +3649,9 @@ should_force_shadow_fb (MetaRendererNative *renderer_native,
+       break;
+     }
+ 
++  if (!cogl_has_feature (cogl_context, COGL_FEATURE_ID_BLIT_FRAMEBUFFER))
++    return FALSE;
++
+   kms_fd = meta_gpu_kms_get_fd (primary_gpu);
+   if (drmGetCap (kms_fd, DRM_CAP_DUMB_PREFER_SHADOW, &prefer_shadow) == 0)
+     {
+-- 
+2.28.0
+
+
+From 5f247503e261f5bbb6baedc40c737c96b8144218 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 18:55:03 +0200
+Subject: [PATCH 09/20] clutter/stage-view: Always use cogl_blit_framebuffer()
+ from shadowfb
+
+It should only be used when direct blitting is supported, so there is no
+reason we should have to deal with pipelines etc when blitting from the
+shadow buffer to the onscreen.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 130f696f303a01d6d666ac967c53b4b5dc372f08)
+---
+ clutter/clutter/clutter-stage-view.c | 37 +++++++++++-----------------
+ 1 file changed, 15 insertions(+), 22 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 40edfad6e1..e7e33963a6 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -53,7 +53,6 @@ typedef struct _ClutterStageViewPrivate
+   gboolean use_shadowfb;
+   struct {
+     CoglOffscreen *framebuffer;
+-    CoglPipeline *pipeline;
+   } shadow;
+ 
+   guint dirty_viewport   : 1;
+@@ -149,19 +148,6 @@ clutter_stage_view_ensure_offscreen_blit_pipeline (ClutterStageView *view)
+     view_class->setup_offscreen_blit_pipeline (view, priv->offscreen_pipeline);
+ }
+ 
+-static void
+-clutter_stage_view_ensure_shadowfb_blit_pipeline (ClutterStageView *view)
+-{
+-  ClutterStageViewPrivate *priv =
+-    clutter_stage_view_get_instance_private (view);
+-
+-  if (priv->shadow.pipeline)
+-    return;
+-
+-  priv->shadow.pipeline =
+-    clutter_stage_view_create_framebuffer_pipeline (priv->shadow.framebuffer);
+-}
+-
+ void
+ clutter_stage_view_invalidate_offscreen_blit_pipeline (ClutterStageView *view)
+ {
+@@ -321,13 +307,21 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+ 
+   if (priv->shadow.framebuffer)
+     {
+-      clutter_stage_view_ensure_shadowfb_blit_pipeline (view);
+-      clutter_stage_view_copy_to_framebuffer (view,
+-                                              rect,
+-                                              priv->shadow.pipeline,
+-                                              priv->shadow.framebuffer,
+-                                              priv->framebuffer,
+-                                              TRUE);
++      int width, height;
++      g_autoptr (GError) error = NULL;
++
++      width = cogl_framebuffer_get_width (priv->framebuffer);
++      height = cogl_framebuffer_get_height (priv->framebuffer);
++      if (!cogl_blit_framebuffer (priv->shadow.framebuffer,
++                                  priv->framebuffer,
++                                  0, 0,
++                                  0, 0,
++                                  width, height,
++                                  &error))
++        {
++          g_warning ("Failed to blit shadow buffer: %s", error->message);
++          return;
++        }
+     }
+ }
+ 
+@@ -543,7 +537,6 @@ clutter_stage_view_dispose (GObject *object)
+   g_clear_pointer (&priv->name, g_free);
+   g_clear_pointer (&priv->framebuffer, cogl_object_unref);
+   g_clear_pointer (&priv->shadow.framebuffer, cogl_object_unref);
+-  g_clear_pointer (&priv->shadow.pipeline, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
+ 
+-- 
+2.28.0
+
+
+From d20008aa8630c87d8607e64ff77188fc67b3d22a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 18:59:32 +0200
+Subject: [PATCH 10/20] clutter/stage-view: Simplify painting of offscreen
+ slightly
+
+We will only ever have an "offscreen" if we're painting transformed in
+some way, so the 'can_blit' checking is unnecessary. Remove it.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 32d5e7d3d77c7ba29b8a7da45731aa31bd486056)
+---
+ clutter/clutter/clutter-stage-view.c | 49 +++++++---------------------
+ 1 file changed, 12 insertions(+), 37 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index e7e33963a6..64fb20cb00 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -158,29 +158,13 @@ clutter_stage_view_invalidate_offscreen_blit_pipeline (ClutterStageView *view)
+ }
+ 
+ static void
+-clutter_stage_view_copy_to_framebuffer (ClutterStageView            *view,
+-                                        const cairo_rectangle_int_t *rect,
+-                                        CoglPipeline                *pipeline,
+-                                        CoglFramebuffer             *src_framebuffer,
+-                                        CoglFramebuffer             *dst_framebuffer,
+-                                        gboolean                     can_blit)
++paint_transformed_framebuffer (ClutterStageView *view,
++                               CoglPipeline     *pipeline,
++                               CoglFramebuffer  *src_framebuffer,
++                               CoglFramebuffer  *dst_framebuffer)
+ {
+   CoglMatrix matrix;
+ 
+-  /* First, try with blit */
+-  if (can_blit)
+-    {
+-      if (cogl_blit_framebuffer (src_framebuffer,
+-                                 dst_framebuffer,
+-                                 0, 0,
+-                                 0, 0,
+-                                 cogl_framebuffer_get_width (dst_framebuffer),
+-                                 cogl_framebuffer_get_height (dst_framebuffer),
+-                                 NULL))
+-        return;
+-    }
+-
+-  /* If blit fails, fallback to the slower painting method */
+   cogl_framebuffer_push_matrix (dst_framebuffer);
+ 
+   cogl_matrix_init_identity (&matrix);
+@@ -278,30 +262,21 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+ 
+   if (priv->offscreen)
+     {
+-      gboolean can_blit;
+-      CoglMatrix matrix;
+-
+       clutter_stage_view_ensure_offscreen_blit_pipeline (view);
+-      clutter_stage_view_get_offscreen_transformation_matrix (view, &matrix);
+-      can_blit = cogl_matrix_is_identity (&matrix);
+ 
+       if (priv->shadow.framebuffer)
+         {
+-          clutter_stage_view_copy_to_framebuffer (view,
+-                                                  rect,
+-                                                  priv->offscreen_pipeline,
+-                                                  priv->offscreen,
+-                                                  priv->shadow.framebuffer,
+-                                                  can_blit);
++          paint_transformed_framebuffer (view,
++                                         priv->offscreen_pipeline,
++                                         priv->offscreen,
++                                         priv->shadow.framebuffer);
+         }
+       else
+         {
+-          clutter_stage_view_copy_to_framebuffer (view,
+-                                                  rect,
+-                                                  priv->offscreen_pipeline,
+-                                                  priv->offscreen,
+-                                                  priv->framebuffer,
+-                                                  can_blit);
++          paint_transformed_framebuffer (view,
++                                         priv->offscreen_pipeline,
++                                         priv->offscreen,
++                                         priv->framebuffer);
+         }
+     }
+ 
+-- 
+2.28.0
+
+
+From 8fca65cc3ff989529bf08a47f20b80691f91f95f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 19:08:03 +0200
+Subject: [PATCH 11/20] region-utils: Make transform util const correct
+
+The input should be const, as it will not be altered.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 761bc64cdd4746389625173454b8861cf211cd79)
+---
+ src/compositor/region-utils.c | 2 +-
+ src/compositor/region-utils.h | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/compositor/region-utils.c b/src/compositor/region-utils.c
+index 752af85c5c..8edb89322c 100644
+--- a/src/compositor/region-utils.c
++++ b/src/compositor/region-utils.c
+@@ -376,7 +376,7 @@ meta_make_border_region (cairo_region_t *region,
+ }
+ 
+ cairo_region_t *
+-meta_region_transform (cairo_region_t       *region,
++meta_region_transform (const cairo_region_t *region,
+                        MetaMonitorTransform  transform,
+                        int                   width,
+                        int                   height)
+diff --git a/src/compositor/region-utils.h b/src/compositor/region-utils.h
+index 84e4d83bc2..ca1b8b7b45 100644
+--- a/src/compositor/region-utils.h
++++ b/src/compositor/region-utils.h
+@@ -106,7 +106,7 @@ cairo_region_t * meta_make_border_region (cairo_region_t *region,
+                                           int             y_amount,
+                                           gboolean        flip);
+ 
+-cairo_region_t * meta_region_transform (cairo_region_t       *region,
++cairo_region_t * meta_region_transform (const cairo_region_t *region,
+                                         MetaMonitorTransform  transform,
+                                         int                   width,
+                                         int                   height);
+-- 
+2.28.0
+
+
+From 58331ff2f10aad87f537e3ebdaa5707c13c9e41b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 19:05:36 +0200
+Subject: [PATCH 12/20] clutter/stage-cogl: Use buffer age when view monitor is
+ rotated
+
+We failed to use the buffer age when monitors were rotated, as when they
+are, we first composite to an offscreen framebuffer, then later again to
+the onscreen. The buffer age checking happened on the offscreen, and an
+offscreen being single buffered, they can't possible support buffer
+ages.
+
+Instead, move the buffer age check to check the actual onscreen
+framebuffer. The offscreen to onscreen painting is still always full
+frame, but that will be fixed in a later commit.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 41c2c2c7d72a0bc8ac1970d35183345424642cf1)
+---
+ clutter/clutter/clutter-stage-view-private.h |  6 +++
+ clutter/clutter/clutter-stage-view.c         | 29 ++++++-----
+ clutter/clutter/clutter-stage-view.h         | 11 ++--
+ clutter/clutter/cogl/clutter-stage-cogl.c    | 54 +++++++-------------
+ src/backends/meta-renderer-view.c            | 22 ++++++++
+ 5 files changed, 68 insertions(+), 54 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view-private.h b/clutter/clutter/clutter-stage-view-private.h
+index e27f140b8a..10f9847b70 100644
+--- a/clutter/clutter/clutter-stage-view-private.h
++++ b/clutter/clutter/clutter-stage-view-private.h
+@@ -40,4 +40,10 @@ void clutter_stage_view_invalidate_projection (ClutterStageView *view);
+ void clutter_stage_view_set_projection (ClutterStageView *view,
+                                         const CoglMatrix *matrix);
+ 
++void clutter_stage_view_transform_rect_to_onscreen (ClutterStageView            *view,
++                                                    const cairo_rectangle_int_t *src_rect,
++                                                    int                          dst_width,
++                                                    int                          dst_height,
++                                                    cairo_rectangle_int_t       *dst_rect);
++
+ #endif /* __CLUTTER_STAGE_VIEW_PRIVATE_H__ */
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 64fb20cb00..080bfd6669 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -157,6 +157,22 @@ clutter_stage_view_invalidate_offscreen_blit_pipeline (ClutterStageView *view)
+   g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
+ }
+ 
++void
++clutter_stage_view_transform_rect_to_onscreen (ClutterStageView            *view,
++                                               const cairo_rectangle_int_t *src_rect,
++                                               int                          dst_width,
++                                               int                          dst_height,
++                                               cairo_rectangle_int_t       *dst_rect)
++{
++  ClutterStageViewClass *view_class = CLUTTER_STAGE_VIEW_GET_CLASS (view);
++
++  return view_class->transform_rect_to_onscreen (view,
++                                                 src_rect,
++                                                 dst_width,
++                                                 dst_height,
++                                                 dst_rect);
++}
++
+ static void
+ paint_transformed_framebuffer (ClutterStageView *view,
+                                CoglPipeline     *pipeline,
+@@ -383,19 +399,6 @@ clutter_stage_view_get_offscreen_transformation_matrix (ClutterStageView *view,
+   view_class->get_offscreen_transformation_matrix (view, matrix);
+ }
+ 
+-void
+-clutter_stage_view_transform_to_onscreen (ClutterStageView *view,
+-                                          gfloat           *x,
+-                                          gfloat           *y)
+-{
+-  gfloat z = 0, w = 1;
+-  CoglMatrix matrix;
+-
+-  clutter_stage_view_get_offscreen_transformation_matrix (view, &matrix);
+-  cogl_matrix_get_inverse (&matrix, &matrix);
+-  cogl_matrix_transform_point (&matrix, x, y, &z, &w);
+-}
+-
+ static void
+ clutter_stage_default_get_offscreen_transformation_matrix (ClutterStageView *view,
+                                                            CoglMatrix       *matrix)
+diff --git a/clutter/clutter/clutter-stage-view.h b/clutter/clutter/clutter-stage-view.h
+index 26bf10e798..eb0184e9ab 100644
+--- a/clutter/clutter/clutter-stage-view.h
++++ b/clutter/clutter/clutter-stage-view.h
+@@ -43,6 +43,12 @@ struct _ClutterStageViewClass
+ 
+   void (* get_offscreen_transformation_matrix) (ClutterStageView *view,
+                                                 CoglMatrix       *matrix);
++
++  void (* transform_rect_to_onscreen) (ClutterStageView            *view,
++                                       const cairo_rectangle_int_t *src_rect,
++                                       int                          dst_width,
++                                       int                          dst_height,
++                                       cairo_rectangle_int_t       *dst_rect);
+ };
+ 
+ CLUTTER_EXPORT
+@@ -56,11 +62,6 @@ CoglFramebuffer *clutter_stage_view_get_onscreen (ClutterStageView *view);
+ CLUTTER_EXPORT
+ void             clutter_stage_view_invalidate_offscreen_blit_pipeline (ClutterStageView *view);
+ 
+-CLUTTER_EXPORT
+-void             clutter_stage_view_transform_to_onscreen (ClutterStageView *view,
+-                                                           gfloat           *x,
+-                                                           gfloat           *y);
+-
+ CLUTTER_EXPORT
+ float clutter_stage_view_get_scale (ClutterStageView *view);
+ 
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index 005c6f6922..821f78ee7c 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -509,36 +509,17 @@ static void
+ transform_swap_region_to_onscreen (ClutterStageView      *view,
+                                    cairo_rectangle_int_t *swap_region)
+ {
+-  CoglFramebuffer *framebuffer;
+-  cairo_rectangle_int_t layout;
+-  gfloat x1, y1, x2, y2;
+-  gint width, height;
+-
+-  framebuffer = clutter_stage_view_get_onscreen (view);
+-  clutter_stage_view_get_layout (view, &layout);
+-
+-  x1 = (float) swap_region->x / layout.width;
+-  y1 = (float) swap_region->y / layout.height;
+-  x2 = (float) (swap_region->x + swap_region->width) / layout.width;
+-  y2 = (float) (swap_region->y + swap_region->height) / layout.height;
+-
+-  clutter_stage_view_transform_to_onscreen (view, &x1, &y1);
+-  clutter_stage_view_transform_to_onscreen (view, &x2, &y2);
+-
+-  width = cogl_framebuffer_get_width (framebuffer);
+-  height = cogl_framebuffer_get_height (framebuffer);
+-
+-  x1 = floor (x1 * width);
+-  y1 = floor (height - (y1 * height));
+-  x2 = ceil (x2 * width);
+-  y2 = ceil (height - (y2 * height));
+-
+-  *swap_region = (cairo_rectangle_int_t) {
+-    .x = x1,
+-    .y = y1,
+-    .width = x2 - x1,
+-    .height = y2 - y1
+-  };
++  CoglFramebuffer *onscreen = clutter_stage_view_get_onscreen (view);
++  int width, height;
++
++  width = cogl_framebuffer_get_width (onscreen);
++  height = cogl_framebuffer_get_height (onscreen);
++
++  clutter_stage_view_transform_rect_to_onscreen (view,
++                                                 swap_region,
++                                                 width,
++                                                 height,
++                                                 swap_region);
+ }
+ 
+ static void
+@@ -593,6 +574,7 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+   ClutterStageViewCoglPrivate *view_priv =
+     clutter_stage_view_cogl_get_instance_private (view_cogl);
+   CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view);
++  CoglFramebuffer *onscreen = clutter_stage_view_get_onscreen (view);
+   cairo_rectangle_int_t view_rect;
+   gboolean have_clip;
+   gboolean may_use_clipped_redraw;
+@@ -618,10 +600,10 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+   fb_height = cogl_framebuffer_get_height (fb);
+ 
+   can_blit_sub_buffer =
+-    cogl_is_onscreen (fb) &&
++    cogl_is_onscreen (onscreen) &&
+     cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_REGION);
+ 
+-  has_buffer_age = cogl_is_onscreen (fb) && is_buffer_age_enabled ();
++  has_buffer_age = cogl_is_onscreen (onscreen) && is_buffer_age_enabled ();
+ 
+   /* NB: a zero width redraw clip == full stage redraw */
+   if (stage_cogl->bounding_redraw_clip.width == 0)
+@@ -645,7 +627,7 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+       have_clip &&
+       /* some drivers struggle to get going and produce some junk
+        * frames when starting up... */
+-      cogl_onscreen_get_frame_counter (COGL_ONSCREEN (fb)) > 3)
++      cogl_onscreen_get_frame_counter (COGL_ONSCREEN (onscreen)) > 3)
+     {
+       ClutterRect rect;
+ 
+@@ -686,7 +668,7 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+           cairo_rectangle_int_t *current_fb_damage =
+             &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index++)];
+ 
+-          age = cogl_onscreen_get_buffer_age (COGL_ONSCREEN (fb));
++          age = cogl_onscreen_get_buffer_age (COGL_ONSCREEN (onscreen));
+ 
+           if (valid_buffer_age (view_cogl, age))
+             {
+@@ -961,9 +943,9 @@ clutter_stage_cogl_get_dirty_pixel (ClutterStageWindow *stage_window,
+                                     int                *x,
+                                     int                *y)
+ {
+-  CoglFramebuffer *framebuffer = clutter_stage_view_get_framebuffer (view);
++  CoglFramebuffer *onscreen = clutter_stage_view_get_onscreen (view);
+   gboolean has_buffer_age =
+-    cogl_is_onscreen (framebuffer) &&
++    cogl_is_onscreen (onscreen) &&
+     is_buffer_age_enabled ();
+   float fb_scale;
+   gboolean scale_is_fractional;
+diff --git a/src/backends/meta-renderer-view.c b/src/backends/meta-renderer-view.c
+index cab1f5f483..4e45f2ef02 100644
+--- a/src/backends/meta-renderer-view.c
++++ b/src/backends/meta-renderer-view.c
+@@ -34,6 +34,7 @@
+ 
+ #include "backends/meta-renderer.h"
+ #include "clutter/clutter-mutter.h"
++#include "compositor/region-utils.h"
+ 
+ enum
+ {
+@@ -125,6 +126,25 @@ meta_renderer_view_setup_offscreen_blit_pipeline (ClutterStageView *view,
+   cogl_pipeline_set_layer_matrix (pipeline, 0, &matrix);
+ }
+ 
++static void
++meta_renderer_view_transform_rect_to_onscreen (ClutterStageView            *view,
++                                               const cairo_rectangle_int_t *src_rect,
++                                               int                          dst_width,
++                                               int                          dst_height,
++                                               cairo_rectangle_int_t       *dst_rect)
++{
++  MetaRendererView *renderer_view = META_RENDERER_VIEW (view);
++  MetaMonitorTransform inverted_transform;
++
++  inverted_transform =
++    meta_monitor_transform_invert (renderer_view->transform);
++  return meta_rectangle_transform (src_rect,
++                                   inverted_transform,
++                                   dst_width,
++                                   dst_height,
++                                   dst_rect);
++}
++
+ static void
+ meta_renderer_view_set_transform (MetaRendererView     *view,
+                                   MetaMonitorTransform  transform)
+@@ -195,6 +215,8 @@ meta_renderer_view_class_init (MetaRendererViewClass *klass)
+     meta_renderer_view_setup_offscreen_blit_pipeline;
+   view_class->get_offscreen_transformation_matrix =
+     meta_renderer_view_get_offscreen_transformation_matrix;
++  view_class->transform_rect_to_onscreen =
++    meta_renderer_view_transform_rect_to_onscreen;
+ 
+   object_class->get_property = meta_renderer_view_get_property;
+   object_class->set_property = meta_renderer_view_set_property;
+-- 
+2.28.0
+
+
+From 6fc1da9dd3ac2753771bb68adb780d1d55494cba Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 19:22:10 +0200
+Subject: [PATCH 13/20] clutter/stage-view: Only paint redraw clip from
+ offscreen
+
+The rest didn't change, so only actually paint the part of the offscreen
+that was composited as part of the stage painting. In practice, this
+means that, unless a shadow buffer is used, we now only paint the
+damaged part of the stage, and copy the damage part of the offscreen to
+the onscreen.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit acf6b79e3a5b9d8d285886c471961e8c0bec48ce)
+---
+ clutter/clutter/clutter-stage-view.c | 85 ++++++++++++++++++++++++----
+ 1 file changed, 73 insertions(+), 12 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 080bfd6669..b686272db0 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -19,6 +19,7 @@
+ 
+ #include "clutter/clutter-stage-view.h"
+ #include "clutter/clutter-stage-view-private.h"
++#include "clutter/clutter-private.h"
+ 
+ #include <cairo-gobject.h>
+ #include <math.h>
+@@ -174,23 +175,81 @@ clutter_stage_view_transform_rect_to_onscreen (ClutterStageView            *view
+ }
+ 
+ static void
+-paint_transformed_framebuffer (ClutterStageView *view,
+-                               CoglPipeline     *pipeline,
+-                               CoglFramebuffer  *src_framebuffer,
+-                               CoglFramebuffer  *dst_framebuffer)
++paint_transformed_framebuffer (ClutterStageView            *view,
++                               CoglPipeline                *pipeline,
++                               CoglFramebuffer             *src_framebuffer,
++                               CoglFramebuffer             *dst_framebuffer,
++                               const cairo_rectangle_int_t *redraw_clip)
+ {
+   CoglMatrix matrix;
++  int dst_width, dst_height;
++  cairo_rectangle_int_t view_layout;
++  cairo_rectangle_int_t onscreen_layout;
++  float view_scale;
++  float *coordinates;
++  cairo_rectangle_int_t src_rect;
++  cairo_rectangle_int_t dst_rect;
++
++  dst_width = cogl_framebuffer_get_width (dst_framebuffer);
++  dst_height = cogl_framebuffer_get_height (dst_framebuffer);
++  clutter_stage_view_get_layout (view, &view_layout);
++  clutter_stage_view_transform_rect_to_onscreen (view,
++                                                 &(cairo_rectangle_int_t) {
++                                                   .width = view_layout.width,
++                                                   .height = view_layout.height,
++                                                 },
++                                                 view_layout.width,
++                                                 view_layout.height,
++                                                 &onscreen_layout);
++  view_scale = clutter_stage_view_get_scale (view);
+ 
+   cogl_framebuffer_push_matrix (dst_framebuffer);
+ 
+   cogl_matrix_init_identity (&matrix);
+-  cogl_matrix_translate (&matrix, -1, 1, 0);
+-  cogl_matrix_scale (&matrix, 2, -2, 0);
++  cogl_matrix_scale (&matrix,
++                     1.0 / (dst_width / 2.0),
++                     -1.0 / (dst_height / 2.0), 0);
++  cogl_matrix_translate (&matrix,
++                         -(dst_width / 2.0),
++                         -(dst_height / 2.0), 0);
+   cogl_framebuffer_set_projection_matrix (dst_framebuffer, &matrix);
+-
+-  cogl_framebuffer_draw_rectangle (dst_framebuffer,
+-                                   pipeline,
+-                                   0, 0, 1, 1);
++  cogl_framebuffer_set_viewport (dst_framebuffer,
++                                 0, 0, dst_width, dst_height);
++
++  coordinates = g_newa (float, 2 * 4);
++
++  src_rect = *redraw_clip;
++  _clutter_util_rectangle_offset (&src_rect,
++                                  -view_layout.x,
++                                  -view_layout.y,
++                                  &src_rect);
++
++  clutter_stage_view_transform_rect_to_onscreen (view,
++                                                 &src_rect,
++                                                 onscreen_layout.width,
++                                                 onscreen_layout.height,
++                                                 &dst_rect);
++
++  coordinates[0] = (float) dst_rect.x * view_scale;
++  coordinates[1] = (float) dst_rect.y * view_scale;
++  coordinates[2] = ((float) (dst_rect.x + dst_rect.width) *
++                    view_scale);
++  coordinates[3] = ((float) (dst_rect.y + dst_rect.height) *
++                    view_scale);
++
++  coordinates[4] = (((float) dst_rect.x / (float) dst_width) *
++                    view_scale);
++  coordinates[5] = (((float) dst_rect.y / (float) dst_height) *
++                    view_scale);
++  coordinates[6] = ((float) (dst_rect.x + dst_rect.width) /
++                    (float) dst_width) * view_scale;
++  coordinates[7] = ((float) (dst_rect.y + dst_rect.height) /
++                    (float) dst_height) * view_scale;
++
++  cogl_framebuffer_draw_textured_rectangles (dst_framebuffer,
++                                             pipeline,
++                                             coordinates,
++                                             1);
+ 
+   cogl_framebuffer_pop_matrix (dst_framebuffer);
+ }
+@@ -285,14 +344,16 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+           paint_transformed_framebuffer (view,
+                                          priv->offscreen_pipeline,
+                                          priv->offscreen,
+-                                         priv->shadow.framebuffer);
++                                         priv->shadow.framebuffer,
++                                         rect);
+         }
+       else
+         {
+           paint_transformed_framebuffer (view,
+                                          priv->offscreen_pipeline,
+                                          priv->offscreen,
+-                                         priv->framebuffer);
++                                         priv->framebuffer,
++                                         rect);
+         }
+     }
+ 
+-- 
+2.28.0
+
+
+From ff3164440e6bbb3e845a1d4a23843a5792afc16f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 13 May 2020 17:18:50 +0200
+Subject: [PATCH 14/20] clutter/stage-cogl: Only construct damage array if
+ it'll be used
+
+It's only used when we actually swap buffers, which we only do if the
+target framebuffer is an onscreen.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 95a80c442b6300ce5b41b4b3975a372f1eabd166)
+---
+ clutter/clutter/cogl/clutter-stage-cogl.c | 22 +++++++++++-----------
+ 1 file changed, 11 insertions(+), 11 deletions(-)
+
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index 821f78ee7c..fc6d0d031d 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -413,17 +413,6 @@ swap_framebuffer (ClutterStageWindow    *stage_window,
+                   gboolean               swap_with_damage)
+ {
+   CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (view);
+-  int damage[4], ndamage;
+-
+-  damage[0] = swap_region->x;
+-  damage[1] = swap_region->y;
+-  damage[2] = swap_region->width;
+-  damage[3] = swap_region->height;
+-
+-  if (swap_region->width != 0)
+-    ndamage = 1;
+-  else
+-    ndamage = 0;
+ 
+   if (G_UNLIKELY ((clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_DAMAGE_REGION)))
+     paint_damage_region (stage_window, view, swap_region);
+@@ -431,6 +420,17 @@ swap_framebuffer (ClutterStageWindow    *stage_window,
+   if (cogl_is_onscreen (framebuffer))
+     {
+       CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
++      int damage[4], ndamage;
++
++      damage[0] = swap_region->x;
++      damage[1] = swap_region->y;
++      damage[2] = swap_region->width;
++      damage[3] = swap_region->height;
++
++      if (swap_region->width != 0)
++        ndamage = 1;
++      else
++        ndamage = 0;
+ 
+       /* push on the screen */
+       if (ndamage == 1 && !swap_with_damage)
+-- 
+2.28.0
+
+
+From f946746f5938e7d6c48b688827fb991f22dc1364 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 19:25:23 +0200
+Subject: [PATCH 15/20] clutter/stage-view: Only blit the damage part of the
+ shadow buffer
+
+This fixes the last "copy everything" paths when clutter doesn't
+directly paint onto the onscreen framebuffer. It adds a new hook into
+the stage view called before the swap buffer, as at this point, we have
+the swap buffer damag regions ready, which corresponds to the regions we
+must blit according to the damage reported to clutter.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 851e7727ec6f3719139ab562ac2524cdc1bd64ae)
+---
+ clutter/clutter/clutter-stage-view-private.h |  3 +++
+ clutter/clutter/clutter-stage-view.c         | 25 ++++++++++++++++++--
+ clutter/clutter/cogl/clutter-stage-cogl.c    |  2 ++
+ 3 files changed, 28 insertions(+), 2 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view-private.h b/clutter/clutter/clutter-stage-view-private.h
+index 10f9847b70..bddc38ded6 100644
+--- a/clutter/clutter/clutter-stage-view-private.h
++++ b/clutter/clutter/clutter-stage-view-private.h
+@@ -23,6 +23,9 @@
+ void clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+                                         const cairo_rectangle_int_t *clip);
+ 
++void clutter_stage_view_before_swap_buffer (ClutterStageView            *view,
++                                            const cairo_rectangle_int_t *swap_region);
++
+ gboolean clutter_stage_view_is_dirty_viewport (ClutterStageView *view);
+ 
+ void clutter_stage_view_invalidate_viewport (ClutterStageView *view);
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index b686272db0..21ab02c97b 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -356,11 +356,22 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+                                          rect);
+         }
+     }
++}
++
++void
++clutter_stage_view_before_swap_buffer (ClutterStageView            *view,
++                                       const cairo_rectangle_int_t *swap_region)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  g_autoptr (GError) error = NULL;
+ 
+-  if (priv->shadow.framebuffer)
++  if (!priv->shadow.framebuffer)
++    return;
++
++  if (swap_region->width == 0 || swap_region->height == 0)
+     {
+       int width, height;
+-      g_autoptr (GError) error = NULL;
+ 
+       width = cogl_framebuffer_get_width (priv->framebuffer);
+       height = cogl_framebuffer_get_height (priv->framebuffer);
+@@ -370,6 +381,16 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+                                   0, 0,
+                                   width, height,
+                                   &error))
++        g_warning ("Failed to blit shadow buffer: %s", error->message);
++    }
++  else
++    {
++      if (!cogl_blit_framebuffer (priv->shadow.framebuffer,
++                                  priv->framebuffer,
++                                  swap_region->x, swap_region->y,
++                                  swap_region->x, swap_region->y,
++                                  swap_region->width, swap_region->height,
++                                  &error))
+         {
+           g_warning ("Failed to blit shadow buffer: %s", error->message);
+           return;
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index fc6d0d031d..884819ebd3 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -417,6 +417,8 @@ swap_framebuffer (ClutterStageWindow    *stage_window,
+   if (G_UNLIKELY ((clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_DAMAGE_REGION)))
+     paint_damage_region (stage_window, view, swap_region);
+ 
++  clutter_stage_view_before_swap_buffer (view, swap_region);
++
+   if (cogl_is_onscreen (framebuffer))
+     {
+       CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
+-- 
+2.28.0
+
+
+From 757dd09dc9b76a7654f087679db1c7f005b7653c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 6 May 2020 09:11:34 +0200
+Subject: [PATCH 16/20] clutter/stage-cogl: Extract damage history logic
+
+Move the damage history tracking to a new ClutterDamageHistory helper
+type. The aim is to be able to track damage history elsewhere without
+reimplementing the data structure and tracking logic.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 09271bcfef8889022f15a3b2949843e55f3df9da)
+---
+ clutter/clutter/clutter-damage-history.c  |  89 +++++++++++++++++
+ clutter/clutter/clutter-damage-history.h  |  42 ++++++++
+ clutter/clutter/cogl/clutter-stage-cogl.c | 116 ++++++++++++----------
+ clutter/clutter/meson.build               |   2 +
+ 4 files changed, 195 insertions(+), 54 deletions(-)
+ create mode 100644 clutter/clutter/clutter-damage-history.c
+ create mode 100644 clutter/clutter/clutter-damage-history.h
+
+diff --git a/clutter/clutter/clutter-damage-history.c b/clutter/clutter/clutter-damage-history.c
+new file mode 100644
+index 0000000000..78ab0f7b5e
+--- /dev/null
++++ b/clutter/clutter/clutter-damage-history.c
+@@ -0,0 +1,89 @@
++/*
++ * Copyright (C) 2007,2008,2009,2010,2011  Intel Corporation.
++ * Copyright (C) 2020 Red Hat Inc
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library 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
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "clutter-build-config.h"
++
++#include "clutter-damage-history.h"
++
++#define DAMAGE_HISTORY_LENGTH 0x10
++
++struct _ClutterDamageHistory
++{
++  cairo_rectangle_int_t damages[DAMAGE_HISTORY_LENGTH];
++  int index;
++};
++
++ClutterDamageHistory *
++clutter_damage_history_new (void)
++{
++  ClutterDamageHistory *history;
++
++  history = g_new0 (ClutterDamageHistory, 1);
++
++  return history;
++}
++
++void
++clutter_damage_history_free (ClutterDamageHistory *history)
++{
++  g_free (history);
++}
++
++gboolean
++clutter_damage_history_is_age_valid (ClutterDamageHistory *history,
++                                     int                   age)
++{
++  const cairo_rectangle_int_t *damage;
++
++  if (age >= DAMAGE_HISTORY_LENGTH ||
++      age < 1)
++    return FALSE;
++
++  damage = clutter_damage_history_lookup (history, age);
++  if (damage->width == 0 || damage->height == 0)
++    return FALSE;
++
++  return TRUE;
++}
++
++void
++clutter_damage_history_record (ClutterDamageHistory        *history,
++                               const cairo_rectangle_int_t *damage)
++{
++  history->damages[history->index] = *damage;
++}
++
++static inline int
++step_damage_index (int current,
++                   int diff)
++{
++  return (current + diff) & (DAMAGE_HISTORY_LENGTH - 1);
++}
++
++void
++clutter_damage_history_step (ClutterDamageHistory *history)
++{
++  history->index = step_damage_index (history->index, 1);
++}
++
++const cairo_rectangle_int_t *
++clutter_damage_history_lookup (ClutterDamageHistory *history,
++                               int                   age)
++{
++  return &history->damages[step_damage_index (history->index, -age)];
++}
+diff --git a/clutter/clutter/clutter-damage-history.h b/clutter/clutter/clutter-damage-history.h
+new file mode 100644
+index 0000000000..6c483acab7
+--- /dev/null
++++ b/clutter/clutter/clutter-damage-history.h
+@@ -0,0 +1,42 @@
++/*
++ * Copyright (C) 2007,2008,2009,2010,2011  Intel Corporation.
++ * Copyright (C) 2020 Red Hat Inc
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library 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
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef CLUTTER_DAMAGE_HISTORY_H
++#define CLUTTER_DAMAGE_HISTORY_H
++
++#include <cairo.h>
++#include <glib.h>
++
++typedef struct _ClutterDamageHistory ClutterDamageHistory;
++
++ClutterDamageHistory * clutter_damage_history_new (void);
++
++void clutter_damage_history_free (ClutterDamageHistory *history);
++
++gboolean clutter_damage_history_is_age_valid (ClutterDamageHistory *history,
++                                              int                   age);
++
++void clutter_damage_history_record (ClutterDamageHistory        *history,
++                                    const cairo_rectangle_int_t *damage);
++
++void clutter_damage_history_step (ClutterDamageHistory *history);
++
++const cairo_rectangle_int_t * clutter_damage_history_lookup (ClutterDamageHistory *history,
++                                                             int                   age);
++
++#endif /* CLUTTER_DAMAGE_HISTORY_H */
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index 884819ebd3..11273ec894 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -38,6 +38,7 @@
+ 
+ #include "clutter-actor-private.h"
+ #include "clutter-backend-private.h"
++#include "clutter-damage-history.h"
+ #include "clutter-debug.h"
+ #include "clutter-event.h"
+ #include "clutter-enum-types.h"
+@@ -49,13 +50,9 @@
+ 
+ typedef struct _ClutterStageViewCoglPrivate
+ {
+-  /*
+-   * List of previous damaged areas in stage view framebuffer coordinate space.
++  /* Damage history, in stage view render target framebuffer coordinate space.
+    */
+-#define DAMAGE_HISTORY_MAX 16
+-#define DAMAGE_HISTORY(x) ((x) & (DAMAGE_HISTORY_MAX - 1))
+-  cairo_rectangle_int_t damage_history[DAMAGE_HISTORY_MAX];
+-  unsigned int damage_index;
++  ClutterDamageHistory *damage_history;
+ } ClutterStageViewCoglPrivate;
+ 
+ G_DEFINE_TYPE_WITH_PRIVATE (ClutterStageViewCogl, clutter_stage_view_cogl,
+@@ -348,10 +345,7 @@ valid_buffer_age (ClutterStageViewCogl *view_cogl,
+   ClutterStageViewCoglPrivate *view_priv =
+     clutter_stage_view_cogl_get_instance_private (view_cogl);
+ 
+-  if (age <= 0)
+-    return FALSE;
+-
+-  return age < MIN (view_priv->damage_index, DAMAGE_HISTORY_MAX);
++  return clutter_damage_history_is_age_valid (view_priv->damage_history, age);
+ }
+ 
+ static void
+@@ -483,30 +477,6 @@ paint_stage (ClutterStageCogl            *stage_cogl,
+   clutter_stage_view_blit_offscreen (view, clip);
+ }
+ 
+-static void
+-fill_current_damage_history_and_step (ClutterStageView *view)
+-{
+-  ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
+-  ClutterStageViewCoglPrivate *view_priv =
+-    clutter_stage_view_cogl_get_instance_private (view_cogl);
+-  cairo_rectangle_int_t view_rect;
+-  float fb_scale;
+-  cairo_rectangle_int_t *current_fb_damage;
+-
+-  current_fb_damage =
+-    &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index)];
+-  clutter_stage_view_get_layout (view, &view_rect);
+-  fb_scale = clutter_stage_view_get_scale (view);
+-
+-  *current_fb_damage = (cairo_rectangle_int_t) {
+-    .x = 0,
+-    .y = 0,
+-    .width = view_rect.width * fb_scale,
+-    .height = view_rect.height * fb_scale
+-  };
+-  view_priv->damage_index++;
+-}
+-
+ static void
+ transform_swap_region_to_onscreen (ClutterStageView      *view,
+                                    cairo_rectangle_int_t *swap_region)
+@@ -567,6 +537,24 @@ scale_and_clamp_rect (const ClutterRect     *rect,
+   _clutter_util_rectangle_int_extents (&tmp, dest);
+ }
+ 
++static void
++record_full_damage (ClutterStageView *view)
++{
++  ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
++  ClutterStageViewCoglPrivate *view_priv =
++    clutter_stage_view_cogl_get_instance_private (view_cogl);
++  CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view);
++  int fb_width, fb_height;
++
++  fb_width = cogl_framebuffer_get_width (fb);
++  fb_height = cogl_framebuffer_get_height (fb);
++  clutter_damage_history_record (view_priv->damage_history,
++                                 &(cairo_rectangle_int_t) {
++                                   .width = fb_width,
++                                   .height = fb_height
++                                 });
++}
++
+ static gboolean
+ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+                                 ClutterStageView   *view)
+@@ -666,9 +654,7 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+     {
+       if (use_clipped_redraw && !clip_region_empty)
+         {
+-          int age, i;
+-          cairo_rectangle_int_t *current_fb_damage =
+-            &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index++)];
++          int age;
+ 
+           age = cogl_onscreen_get_buffer_age (COGL_ONSCREEN (onscreen));
+ 
+@@ -676,16 +662,20 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+             {
+               ClutterRect rect;
+               cairo_rectangle_int_t damage_region;
++              int i;
+ 
+-              *current_fb_damage = fb_clip_region;
++              clutter_damage_history_record (view_priv->damage_history,
++                                             &fb_clip_region);
+ 
+               for (i = 1; i <= age; i++)
+                 {
+-                  cairo_rectangle_int_t *fb_damage =
+-                    &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index - i - 1)];
++                  const cairo_rectangle_int_t *old_damage;
++
++                  old_damage =
++                    clutter_damage_history_lookup (view_priv->damage_history, i);
+ 
+                   _clutter_util_rectangle_union (&fb_clip_region,
+-                                                 fb_damage,
++                                                 old_damage,
+                                                  &fb_clip_region);
+                 }
+ 
+@@ -713,18 +703,15 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+             {
+               CLUTTER_NOTE (CLIPPING, "Invalid back buffer(age=%d): forcing full redraw\n", age);
+               use_clipped_redraw = FALSE;
+-              *current_fb_damage = (cairo_rectangle_int_t) {
+-                .x = 0,
+-                .y = 0,
+-                .width = view_rect.width * fb_scale,
+-                .height = view_rect.height * fb_scale
+-              };
++              record_full_damage (view);
+             }
+         }
+       else if (!use_clipped_redraw)
+         {
+-          fill_current_damage_history_and_step (view);
++          record_full_damage (view);
+         }
++
++      clutter_damage_history_step (view_priv->damage_history);
+     }
+ 
+   cogl_push_framebuffer (fb);
+@@ -946,6 +933,9 @@ clutter_stage_cogl_get_dirty_pixel (ClutterStageWindow *stage_window,
+                                     int                *y)
+ {
+   CoglFramebuffer *onscreen = clutter_stage_view_get_onscreen (view);
++  ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
++  ClutterStageViewCoglPrivate *view_priv =
++    clutter_stage_view_cogl_get_instance_private (view_cogl);
+   gboolean has_buffer_age =
+     cogl_is_onscreen (onscreen) &&
+     is_buffer_age_enabled ();
+@@ -967,22 +957,21 @@ clutter_stage_cogl_get_dirty_pixel (ClutterStageWindow *stage_window,
+    * For now, always use the (0, 0) pixel for picking when using fractional
+    * framebuffer scaling.
+    */
+-  if (!has_buffer_age || scale_is_fractional)
++  if (!has_buffer_age ||
++      scale_is_fractional ||
++      !clutter_damage_history_is_age_valid (view_priv->damage_history, 0))
+     {
+       *x = 0;
+       *y = 0;
+     }
+   else
+     {
+-      ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
+-      ClutterStageViewCoglPrivate *view_priv =
+-        clutter_stage_view_cogl_get_instance_private (view_cogl);
+       cairo_rectangle_int_t view_layout;
+-      cairo_rectangle_int_t *fb_damage;
++      const cairo_rectangle_int_t *fb_damage;
+ 
+       clutter_stage_view_get_layout (view, &view_layout);
+ 
+-      fb_damage = &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index - 1)];
++      fb_damage = clutter_damage_history_lookup (view_priv->damage_history, 0);
+       *x = fb_damage->x / fb_scale;
+       *y = fb_damage->y / fb_scale;
+     }
+@@ -1052,12 +1041,31 @@ _clutter_stage_cogl_init (ClutterStageCogl *stage)
+   stage->update_time = -1;
+ }
+ 
++static void
++clutter_stage_view_cogl_finalize (GObject *object)
++{
++  ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (object);
++  ClutterStageViewCoglPrivate *view_priv =
++    clutter_stage_view_cogl_get_instance_private (view_cogl);
++
++  clutter_damage_history_free (view_priv->damage_history);
++
++  G_OBJECT_CLASS (clutter_stage_view_cogl_parent_class)->finalize (object);
++}
++
+ static void
+ clutter_stage_view_cogl_init (ClutterStageViewCogl *view_cogl)
+ {
++  ClutterStageViewCoglPrivate *view_priv =
++    clutter_stage_view_cogl_get_instance_private (view_cogl);
++
++  view_priv->damage_history = clutter_damage_history_new ();
+ }
+ 
+ static void
+ clutter_stage_view_cogl_class_init (ClutterStageViewCoglClass *klass)
+ {
++  GObjectClass *object_class = G_OBJECT_CLASS (klass);
++
++  object_class->finalize = clutter_stage_view_cogl_finalize;
+ }
+diff --git a/clutter/clutter/meson.build b/clutter/clutter/meson.build
+index 8e0484453d..c9eab96d29 100644
+--- a/clutter/clutter/meson.build
++++ b/clutter/clutter/meson.build
+@@ -116,6 +116,7 @@ clutter_sources = [
+   'clutter-constraint.c',
+   'clutter-container.c',
+   'clutter-content.c',
++  'clutter-damage-history.c',
+   'clutter-deform-effect.c',
+   'clutter-desaturate-effect.c',
+   'clutter-device-manager.c',
+@@ -186,6 +187,7 @@ clutter_private_headers = [
+   'clutter-bezier.h',
+   'clutter-constraint-private.h',
+   'clutter-content-private.h',
++  'clutter-damage-history.h',
+   'clutter-debug.h',
+   'clutter-device-manager-private.h',
+   'clutter-easing.h',
+-- 
+2.28.0
+
+
+From 5da1c8083784a351a7763a0c9a9ce4c8359522a4 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 6 May 2020 21:40:40 +0200
+Subject: [PATCH 17/20] cogl/dma-buf: Add API to synchronize reading
+
+Used before and after accessing DMA buffer content using mmap().
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 2d972fc761b9e39f78e66dd84eab57309cdc8658)
+---
+ cogl/cogl/cogl-dma-buf-handle.c | 51 +++++++++++++++++++++++++++++++++
+ cogl/cogl/cogl-dma-buf-handle.h |  8 ++++++
+ cogl/meson.build                |  1 +
+ 3 files changed, 60 insertions(+)
+
+diff --git a/cogl/cogl/cogl-dma-buf-handle.c b/cogl/cogl/cogl-dma-buf-handle.c
+index d8b4e57c55..7e86e2267b 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.c
++++ b/cogl/cogl/cogl-dma-buf-handle.c
+@@ -34,6 +34,10 @@
+ #include "cogl-dma-buf-handle.h"
+ #include "cogl-object.h"
+ 
++#include <errno.h>
++#include <gio/gio.h>
++#include <linux/dma-buf.h>
++#include <sys/ioctl.h>
+ #include <unistd.h>
+ 
+ struct _CoglDmaBufHandle
+@@ -96,6 +100,53 @@ cogl_dma_buf_handle_free (CoglDmaBufHandle *dmabuf_handle)
+   g_free (dmabuf_handle);
+ }
+ 
++static gboolean
++sync_read (CoglDmaBufHandle  *dmabuf_handle,
++           uint64_t           start_or_end,
++           GError           **error)
++{
++  struct dma_buf_sync sync = { 0 };
++
++  sync.flags = start_or_end | DMA_BUF_SYNC_READ;
++
++  while (TRUE)
++    {
++      int ret;
++
++      ret = ioctl (dmabuf_handle->dmabuf_fd, DMA_BUF_IOCTL_SYNC, &sync);
++      if (ret == -1 && errno == EINTR)
++        {
++          continue;
++        }
++      else if (ret == -1)
++        {
++          g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                       "ioctl: %s", g_strerror (errno));
++          return FALSE;
++        }
++      else
++        {
++          break;
++        }
++    }
++
++  return TRUE;
++}
++
++gboolean
++cogl_dma_buf_handle_sync_read_start (CoglDmaBufHandle  *dmabuf_handle,
++                                     GError           **error)
++{
++  return sync_read (dmabuf_handle, DMA_BUF_SYNC_START, error);
++}
++
++gboolean
++cogl_dma_buf_handle_sync_read_end (CoglDmaBufHandle  *dmabuf_handle,
++                                   GError           **error)
++{
++  return sync_read (dmabuf_handle, DMA_BUF_SYNC_END, error);
++}
++
+ CoglFramebuffer *
+ cogl_dma_buf_handle_get_framebuffer (CoglDmaBufHandle *dmabuf_handle)
+ {
+diff --git a/cogl/cogl/cogl-dma-buf-handle.h b/cogl/cogl/cogl-dma-buf-handle.h
+index f64a20678d..63c5bab7b7 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.h
++++ b/cogl/cogl/cogl-dma-buf-handle.h
+@@ -63,6 +63,14 @@ cogl_dma_buf_handle_new (CoglFramebuffer *framebuffer,
+ void
+ cogl_dma_buf_handle_free (CoglDmaBufHandle *dmabuf_handle);
+ 
++gboolean
++cogl_dma_buf_handle_sync_read_start (CoglDmaBufHandle  *dmabuf_handle,
++                                     GError           **error);
++
++gboolean
++cogl_dma_buf_handle_sync_read_end (CoglDmaBufHandle  *dmabuf_handle,
++                                   GError           **error);
++
+ /**
+  * cogl_dma_buf_handle_get_framebuffer: (skip)
+  *
+diff --git a/cogl/meson.build b/cogl/meson.build
+index 356d596f56..47e6a3e0da 100644
+--- a/cogl/meson.build
++++ b/cogl/meson.build
+@@ -23,6 +23,7 @@ cogl_mutter_config_h = configure_file(
+ 
+ cogl_pkg_deps = [
+   glib_dep,
++  gio_dep,
+   gobject_dep,
+ ]
+ 
+-- 
+2.28.0
+
+
+From 360a397c19046c6a914ee27e3e5104da3ad0c1c6 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 6 May 2020 22:12:46 +0200
+Subject: [PATCH 18/20] cogl/dma-buf: Add mmap/munmap helpers
+
+Avoids dealing directly with mmap() and munmap().
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit e05a1a6c0b2526146c85ec9c381bb2b49d19b4b2)
+---
+ cogl/cogl/cogl-dma-buf-handle.c | 41 +++++++++++++++++++++++++++++++++
+ cogl/cogl/cogl-dma-buf-handle.h |  9 ++++++++
+ 2 files changed, 50 insertions(+)
+
+diff --git a/cogl/cogl/cogl-dma-buf-handle.c b/cogl/cogl/cogl-dma-buf-handle.c
+index 7e86e2267b..9724ac9c95 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.c
++++ b/cogl/cogl/cogl-dma-buf-handle.c
+@@ -38,6 +38,7 @@
+ #include <gio/gio.h>
+ #include <linux/dma-buf.h>
+ #include <sys/ioctl.h>
++#include <sys/mman.h>
+ #include <unistd.h>
+ 
+ struct _CoglDmaBufHandle
+@@ -147,6 +148,46 @@ cogl_dma_buf_handle_sync_read_end (CoglDmaBufHandle  *dmabuf_handle,
+   return sync_read (dmabuf_handle, DMA_BUF_SYNC_END, error);
+ }
+ 
++gpointer
++cogl_dma_buf_handle_mmap (CoglDmaBufHandle  *dmabuf_handle,
++                          GError           **error)
++{
++  size_t size;
++  gpointer data;
++
++  size = dmabuf_handle->height * dmabuf_handle->stride;
++
++  data = mmap (NULL, size, PROT_READ, MAP_PRIVATE,
++               dmabuf_handle->dmabuf_fd,
++               dmabuf_handle->offset);
++  if (data == MAP_FAILED)
++    {
++      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                   "mmap failed: %s", g_strerror (errno));
++      return NULL;
++    }
++
++  return data;
++}
++
++gboolean
++cogl_dma_buf_handle_munmap (CoglDmaBufHandle  *dmabuf_handle,
++                            gpointer           data,
++                            GError           **error)
++{
++  size_t size;
++
++  size = dmabuf_handle->height * dmabuf_handle->stride;
++  if (munmap (data, size) != 0)
++    {
++      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                   "munmap failed: %s", g_strerror (errno));
++      return FALSE;
++    }
++
++  return TRUE;
++}
++
+ CoglFramebuffer *
+ cogl_dma_buf_handle_get_framebuffer (CoglDmaBufHandle *dmabuf_handle)
+ {
+diff --git a/cogl/cogl/cogl-dma-buf-handle.h b/cogl/cogl/cogl-dma-buf-handle.h
+index 63c5bab7b7..08f307c1db 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.h
++++ b/cogl/cogl/cogl-dma-buf-handle.h
+@@ -71,6 +71,15 @@ gboolean
+ cogl_dma_buf_handle_sync_read_end (CoglDmaBufHandle  *dmabuf_handle,
+                                    GError           **error);
+ 
++gpointer
++cogl_dma_buf_handle_mmap (CoglDmaBufHandle  *dmabuf_handle,
++                          GError           **error);
++
++gboolean
++cogl_dma_buf_handle_munmap (CoglDmaBufHandle  *dmabuf_handle,
++                            gpointer           data,
++                            GError           **error);
++
+ /**
+  * cogl_dma_buf_handle_get_framebuffer: (skip)
+  *
+-- 
+2.28.0
+
+
+From ff8a80137047a91ed27d90467b004d691428bac4 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 6 May 2020 22:14:17 +0200
+Subject: [PATCH 19/20] clutter/stage-view: Add tile based shadow damage
+ detection
+
+Compare, tile by tile, whether actual damage actually changed any
+pixels. While this requires mmap():ing DMA buffers and comparing their
+content, we should only ever use shadow buffers when we're using the
+software renderer, meaning mmap() is cheap as it doesn't involve any
+downloading.
+
+This works by making the shadow framebuffer double buffered, while
+keeping track of damage history. When we're about to swap the onscreen
+buffer, we compare what part of the posted damage actually changed,
+records that into a damage history, then given the onscreen buffer age,
+collect all actual damage for that age. The intersection of these tiles,
+and the actual damage, is then used when blitting the shadow buffer to
+the onscreen framebuffer.
+
+Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/1157
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 068385df3a0cf545e5110378b59db56cbd1bdef3)
+---
+ clutter/clutter/clutter-private.h    |   3 +
+ clutter/clutter/clutter-stage-view.c | 472 +++++++++++++++++++++++++--
+ clutter/clutter/clutter-util.c       |  22 ++
+ 3 files changed, 465 insertions(+), 32 deletions(-)
+
+diff --git a/clutter/clutter/clutter-private.h b/clutter/clutter/clutter-private.h
+index a5cd1fa197..5a0fed85c9 100644
+--- a/clutter/clutter/clutter-private.h
++++ b/clutter/clutter/clutter-private.h
+@@ -265,6 +265,9 @@ gboolean _clutter_util_rectangle_intersection (const cairo_rectangle_int_t *src1
+                                                const cairo_rectangle_int_t *src2,
+                                                cairo_rectangle_int_t       *dest);
+ 
++gboolean _clutter_util_rectangle_contains (const cairo_rectangle_int_t *src1,
++                                           const cairo_rectangle_int_t *src2);
++
+ 
+ struct _ClutterVertex4
+ {
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 21ab02c97b..5e5966d06e 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -17,6 +17,7 @@
+ 
+ #include "clutter-build-config.h"
+ 
++#include "clutter/clutter-damage-history.h"
+ #include "clutter/clutter-stage-view.h"
+ #include "clutter/clutter-stage-view-private.h"
+ #include "clutter/clutter-private.h"
+@@ -53,6 +54,12 @@ typedef struct _ClutterStageViewPrivate
+ 
+   gboolean use_shadowfb;
+   struct {
++    struct {
++      CoglDmaBufHandle *handles[2];
++      int current_idx;
++      ClutterDamageHistory *damage_history;
++    } dma_buf;
++
+     CoglOffscreen *framebuffer;
+   } shadow;
+ 
+@@ -254,6 +261,66 @@ paint_transformed_framebuffer (ClutterStageView            *view,
+   cogl_framebuffer_pop_matrix (dst_framebuffer);
+ }
+ 
++static gboolean
++is_shadowfb_double_buffered (ClutterStageView *view)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++
++  return priv->shadow.dma_buf.handles[0] && priv->shadow.dma_buf.handles[1];
++}
++
++static gboolean
++init_dma_buf_shadowfbs (ClutterStageView  *view,
++                        CoglContext       *cogl_context,
++                        int                width,
++                        int                height,
++                        GError           **error)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context);
++  CoglFramebuffer *initial_shadowfb;
++
++  if (!cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE))
++    {
++      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
++                   "Buffer age not supported");
++      return FALSE;
++    }
++
++  if (!cogl_is_onscreen (priv->framebuffer))
++    {
++      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
++                   "Tried to use shadow buffer without onscreen");
++      return FALSE;
++    }
++
++  priv->shadow.dma_buf.handles[0] = cogl_renderer_create_dma_buf (cogl_renderer,
++                                                                  width, height,
++                                                                  error);
++  if (!priv->shadow.dma_buf.handles[0])
++    return FALSE;
++
++  priv->shadow.dma_buf.handles[1] = cogl_renderer_create_dma_buf (cogl_renderer,
++                                                                  width, height,
++                                                                  error);
++  if (!priv->shadow.dma_buf.handles[1])
++    {
++      g_clear_pointer (&priv->shadow.dma_buf.handles[0],
++                       cogl_dma_buf_handle_free);
++      return FALSE;
++    }
++
++  priv->shadow.dma_buf.damage_history = clutter_damage_history_new ();
++
++  initial_shadowfb =
++    cogl_dma_buf_handle_get_framebuffer (priv->shadow.dma_buf.handles[0]);
++  priv->shadow.framebuffer = cogl_object_ref (initial_shadowfb);
++
++  return TRUE;
++}
++
+ static CoglOffscreen *
+ create_offscreen_framebuffer (CoglContext  *context,
+                               int           width,
+@@ -285,11 +352,11 @@ create_offscreen_framebuffer (CoglContext  *context,
+ }
+ 
+ static gboolean
+-init_offscreen_shadowfb (ClutterStageView  *view,
+-                         CoglContext       *cogl_context,
+-                         int                width,
+-                         int                height,
+-                         GError           **error)
++init_fallback_shadowfb (ClutterStageView  *view,
++                        CoglContext       *cogl_context,
++                        int                width,
++                        int                height,
++                        GError           **error)
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+@@ -317,7 +384,17 @@ init_shadowfb (ClutterStageView *view)
+   height = cogl_framebuffer_get_height (priv->framebuffer);
+   cogl_context = cogl_framebuffer_get_context (priv->framebuffer);
+ 
+-  if (!init_offscreen_shadowfb (view, cogl_context, width, height, &error))
++  if (init_dma_buf_shadowfbs (view, cogl_context, width, height, &error))
++    {
++      g_message ("Initialized double buffered shadow fb for %s", priv->name);
++      return;
++    }
++
++  g_warning ("Failed to initialize double buffered shadow fb for %s: %s",
++             priv->name, error->message);
++  g_clear_error (&error);
++
++  if (!init_fallback_shadowfb (view, cogl_context, width, height, &error))
+     {
+       g_warning ("Failed to initialize single buffered shadow fb for %s: %s",
+                  priv->name, error->message);
+@@ -358,44 +435,298 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+     }
+ }
+ 
+-void
+-clutter_stage_view_before_swap_buffer (ClutterStageView            *view,
+-                                       const cairo_rectangle_int_t *swap_region)
++static gboolean
++is_tile_dirty (cairo_rectangle_int_t *tile,
++               uint8_t               *current_data,
++               uint8_t               *prev_data,
++               int                    bpp,
++               int                    stride)
++{
++  int y;
++
++  for (y = tile->y; y < tile->y + tile->height; y++)
++    {
++      if (memcmp (prev_data + y * stride + tile->x * bpp,
++                  current_data + y * stride + tile->x * bpp,
++                  tile->width * bpp) != 0)
++        return TRUE;
++    }
++
++  return FALSE;
++}
++
++static int
++flip_dma_buf_idx (int idx)
++{
++  return (idx + 1) % 2;
++}
++
++static cairo_region_t *
++find_damaged_tiles (ClutterStageView      *view,
++                    const cairo_region_t  *damage_region,
++                    GError               **error)
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+-  g_autoptr (GError) error = NULL;
++  cairo_region_t *tile_damage_region;
++  cairo_rectangle_int_t damage_extents;
++  cairo_rectangle_int_t fb_rect;
++  int prev_dma_buf_idx;
++  CoglDmaBufHandle *prev_dma_buf_handle;
++  uint8_t *prev_data;
++  int current_dma_buf_idx;
++  CoglDmaBufHandle *current_dma_buf_handle;
++  uint8_t *current_data;
++  int width, height, stride, bpp;
++  int tile_x_min, tile_x_max;
++  int tile_y_min, tile_y_max;
++  int tile_x, tile_y;
++  const int tile_size = 16;
++
++  prev_dma_buf_idx = flip_dma_buf_idx (priv->shadow.dma_buf.current_idx);
++  prev_dma_buf_handle = priv->shadow.dma_buf.handles[prev_dma_buf_idx];
++
++  current_dma_buf_idx = priv->shadow.dma_buf.current_idx;
++  current_dma_buf_handle = priv->shadow.dma_buf.handles[current_dma_buf_idx];
++
++  width = cogl_dma_buf_handle_get_width (current_dma_buf_handle);
++  height = cogl_dma_buf_handle_get_height (current_dma_buf_handle);
++  stride = cogl_dma_buf_handle_get_stride (current_dma_buf_handle);
++  bpp = cogl_dma_buf_handle_get_bpp (current_dma_buf_handle);
++
++  cogl_framebuffer_finish (priv->shadow.framebuffer);
++
++  if (!cogl_dma_buf_handle_sync_read_start (prev_dma_buf_handle, error))
++    return NULL;
++
++  if (!cogl_dma_buf_handle_sync_read_start (current_dma_buf_handle, error))
++    goto err_sync_read_current;
++
++  prev_data = cogl_dma_buf_handle_mmap (prev_dma_buf_handle, error);
++  if (!prev_data)
++    goto err_mmap_prev;
++  current_data = cogl_dma_buf_handle_mmap (current_dma_buf_handle, error);
++  if (!current_data)
++    goto err_mmap_current;
++
++  fb_rect = (cairo_rectangle_int_t) {
++    .width = width,
++    .height = height,
++  };
++
++  cairo_region_get_extents (damage_region, &damage_extents);
++
++  tile_x_min = damage_extents.x / tile_size;
++  tile_x_max = ((damage_extents.x + damage_extents.width + tile_size - 1) /
++                tile_size);
++  tile_y_min = damage_extents.y / tile_size;
++  tile_y_max = ((damage_extents.y + damage_extents.height + tile_size - 1) /
++                tile_size);
++
++  tile_damage_region = cairo_region_create ();
++
++  for (tile_y = tile_y_min; tile_y <= tile_y_max; tile_y++)
++    {
++      for (tile_x = tile_x_min; tile_x <= tile_x_max; tile_x++)
++        {
++          cairo_rectangle_int_t tile = {
++            .x = tile_x * tile_size,
++            .y = tile_y * tile_size,
++            .width = tile_size,
++            .height = tile_size,
++          };
+ 
+-  if (!priv->shadow.framebuffer)
+-    return;
++          if (cairo_region_contains_rectangle (damage_region, &tile) ==
++              CAIRO_REGION_OVERLAP_OUT)
++            continue;
+ 
+-  if (swap_region->width == 0 || swap_region->height == 0)
++          _clutter_util_rectangle_intersection (&tile, &fb_rect, &tile);
++
++          if (is_tile_dirty (&tile, current_data, prev_data, bpp, stride))
++            cairo_region_union_rectangle (tile_damage_region, &tile);
++        }
++    }
++
++  if (!cogl_dma_buf_handle_sync_read_end (prev_dma_buf_handle, error))
+     {
+-      int width, height;
++      g_warning ("Failed to end DMA buffer read synchronization: %s",
++                 (*error)->message);
++      g_clear_error (error);
++    }
+ 
+-      width = cogl_framebuffer_get_width (priv->framebuffer);
+-      height = cogl_framebuffer_get_height (priv->framebuffer);
+-      if (!cogl_blit_framebuffer (priv->shadow.framebuffer,
+-                                  priv->framebuffer,
+-                                  0, 0,
+-                                  0, 0,
+-                                  width, height,
+-                                  &error))
+-        g_warning ("Failed to blit shadow buffer: %s", error->message);
++  if (!cogl_dma_buf_handle_sync_read_end (current_dma_buf_handle, error))
++    {
++      g_warning ("Failed to end DMA buffer read synchronization: %s",
++                 (*error)->message);
++      g_clear_error (error);
++    }
++
++  cogl_dma_buf_handle_munmap (prev_dma_buf_handle, prev_data, NULL);
++  cogl_dma_buf_handle_munmap (current_dma_buf_handle, current_data, NULL);
++
++  cairo_region_intersect (tile_damage_region, damage_region);
++
++  return tile_damage_region;
++
++err_mmap_current:
++  cogl_dma_buf_handle_munmap (prev_dma_buf_handle, prev_data, NULL);
++
++err_mmap_prev:
++  cogl_dma_buf_handle_sync_read_end (current_dma_buf_handle, NULL);
++
++err_sync_read_current:
++  cogl_dma_buf_handle_sync_read_end (prev_dma_buf_handle, NULL);
++
++  return NULL;
++}
++
++static void
++swap_dma_buf_framebuffer (ClutterStageView *view)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  int next_idx;
++  CoglDmaBufHandle *next_dma_buf_handle;
++  CoglOffscreen *next_framebuffer;
++
++  next_idx = ((priv->shadow.dma_buf.current_idx + 1) %
++              G_N_ELEMENTS (priv->shadow.dma_buf.handles));
++  priv->shadow.dma_buf.current_idx = next_idx;
++
++  next_dma_buf_handle = priv->shadow.dma_buf.handles[next_idx];
++  next_framebuffer =
++    cogl_dma_buf_handle_get_framebuffer (next_dma_buf_handle);
++  cogl_clear_object (&priv->shadow.framebuffer);
++  priv->shadow.framebuffer = cogl_object_ref (next_framebuffer);
++}
++
++static void
++copy_shadowfb_to_onscreen (ClutterStageView            *view,
++                           const cairo_rectangle_int_t *swap_region)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  ClutterDamageHistory *damage_history = priv->shadow.dma_buf.damage_history;
++  cairo_region_t *damage_region;
++  int age;
++  int i;
++
++  if (swap_region->width == 0 || swap_region->height == 0)
++    {
++      cairo_rectangle_int_t full_damage = {
++        .width = cogl_framebuffer_get_width (priv->framebuffer),
++        .height = cogl_framebuffer_get_height (priv->framebuffer),
++      };
++      damage_region = cairo_region_create_rectangle (&full_damage);
+     }
+   else
+     {
++      damage_region = cairo_region_create_rectangle (swap_region);
++    }
++
++  if (is_shadowfb_double_buffered (view))
++    {
++      CoglOnscreen *onscreen = COGL_ONSCREEN (priv->framebuffer);
++      cairo_region_t *changed_region;
++
++      if (cogl_onscreen_get_frame_counter (onscreen) >= 1)
++        {
++          g_autoptr (GError) error = NULL;
++
++          changed_region = find_damaged_tiles (view, damage_region, &error);
++          if (!changed_region)
++            {
++              int other_dma_buf_idx;
++
++              g_warning ("Disabling actual damage detection: %s",
++                         error->message);
++
++              other_dma_buf_idx =
++                flip_dma_buf_idx (priv->shadow.dma_buf.current_idx);
++              g_clear_pointer (&priv->shadow.dma_buf.handles[other_dma_buf_idx],
++                               cogl_dma_buf_handle_free);
++            }
++        }
++      else
++        {
++          changed_region = cairo_region_copy (damage_region);
++        }
++
++      if (changed_region)
++        {
++          cairo_rectangle_int_t changed_extents;
++          int buffer_age;
++
++          cairo_region_get_extents (changed_region, &changed_extents);
++          clutter_damage_history_record (damage_history, &changed_extents);
++
++          buffer_age = cogl_onscreen_get_buffer_age (onscreen);
++          if (clutter_damage_history_is_age_valid (damage_history, buffer_age))
++            {
++              for (age = 1; age <= buffer_age; age++)
++                {
++                  const cairo_rectangle_int_t *old_damage;
++
++                  old_damage = clutter_damage_history_lookup (damage_history, age);
++                  cairo_region_union_rectangle (changed_region, old_damage);
++                }
++
++              cairo_region_destroy (damage_region);
++              damage_region = g_steal_pointer (&changed_region);
++            }
++          else
++            {
++              cairo_region_destroy (changed_region);
++            }
++
++          clutter_damage_history_step (damage_history);
++        }
++    }
++
++  if (0)
++    {
++      CoglColor clear_color;
++
++      cogl_color_init_from_4ub (&clear_color,
++                                0, 0, 0, 0);
++      cogl_framebuffer_clear (priv->framebuffer, COGL_BUFFER_BIT_COLOR, &clear_color);
++    }
++
++  for (i = 0; i < cairo_region_num_rectangles (damage_region); i++)
++    {
++      g_autoptr (GError) error = NULL;
++      cairo_rectangle_int_t rect;
++
++      cairo_region_get_rectangle (damage_region, i, &rect);
++
+       if (!cogl_blit_framebuffer (priv->shadow.framebuffer,
+                                   priv->framebuffer,
+-                                  swap_region->x, swap_region->y,
+-                                  swap_region->x, swap_region->y,
+-                                  swap_region->width, swap_region->height,
++                                  rect.x, rect.y,
++                                  rect.x, rect.y,
++                                  rect.width, rect.height,
+                                   &error))
+         {
+           g_warning ("Failed to blit shadow buffer: %s", error->message);
++          cairo_region_destroy (damage_region);
+           return;
+         }
+     }
++
++  cairo_region_destroy (damage_region);
++
++  if (is_shadowfb_double_buffered (view))
++    swap_dma_buf_framebuffer (view);
++}
++
++void
++clutter_stage_view_before_swap_buffer (ClutterStageView            *view,
++                                       const cairo_rectangle_int_t *swap_region)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++
++  if (priv->shadow.framebuffer)
++    copy_shadowfb_to_onscreen (view, swap_region);
+ }
+ 
+ float
+@@ -407,6 +738,47 @@ clutter_stage_view_get_scale (ClutterStageView *view)
+   return priv->scale;
+ }
+ 
++typedef void (*FrontBufferCallback) (CoglFramebuffer *framebuffer,
++                                     gconstpointer    user_data);
++
++static void
++clutter_stage_view_foreach_front_buffer (ClutterStageView    *view,
++                                         FrontBufferCallback  callback,
++                                         gconstpointer        user_data)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++
++  if (priv->offscreen)
++    {
++      callback (priv->offscreen, user_data);
++    }
++  else if (priv->shadow.framebuffer)
++    {
++      if (is_shadowfb_double_buffered (view))
++        {
++          int i;
++
++          for (i = 0; i < G_N_ELEMENTS (priv->shadow.dma_buf.handles); i++)
++            {
++              CoglDmaBufHandle *handle = priv->shadow.dma_buf.handles[i];
++              CoglFramebuffer *framebuffer =
++                cogl_dma_buf_handle_get_framebuffer (handle);
++
++              callback (framebuffer, user_data);
++            }
++        }
++      else
++        {
++          callback (priv->shadow.framebuffer, user_data);
++        }
++    }
++  else
++    {
++      callback (priv->framebuffer, user_data);
++    }
++}
++
+ gboolean
+ clutter_stage_view_is_dirty_viewport (ClutterStageView *view)
+ {
+@@ -425,6 +797,19 @@ clutter_stage_view_invalidate_viewport (ClutterStageView *view)
+   priv->dirty_viewport = TRUE;
+ }
+ 
++static void
++set_framebuffer_viewport (CoglFramebuffer *framebuffer,
++                          gconstpointer    user_data)
++{
++  const ClutterRect *rect = user_data;
++
++  cogl_framebuffer_set_viewport (framebuffer,
++                                 rect->origin.x,
++                                 rect->origin.y,
++                                 rect->size.width,
++                                 rect->size.height);
++}
++
+ void
+ clutter_stage_view_set_viewport (ClutterStageView *view,
+                                  float             x,
+@@ -434,11 +819,17 @@ clutter_stage_view_set_viewport (ClutterStageView *view,
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+-  CoglFramebuffer *framebuffer;
++  ClutterRect rect;
+ 
+   priv->dirty_viewport = FALSE;
+-  framebuffer = clutter_stage_view_get_framebuffer (view);
+-  cogl_framebuffer_set_viewport (framebuffer, x, y, width, height);
++
++  rect = (ClutterRect) {
++    .origin = { .x = x, .y = y },
++    .size = { .width = width, .height = height },
++  };
++  clutter_stage_view_foreach_front_buffer (view,
++                                           set_framebuffer_viewport,
++                                           &rect);
+ }
+ 
+ gboolean
+@@ -450,6 +841,13 @@ clutter_stage_view_is_dirty_projection (ClutterStageView *view)
+   return priv->dirty_projection;
+ }
+ 
++static void
++set_framebuffer_projection_matrix (CoglFramebuffer *framebuffer,
++                                   gconstpointer    user_data)
++{
++  cogl_framebuffer_set_projection_matrix (framebuffer, user_data);
++}
++
+ void
+ clutter_stage_view_invalidate_projection (ClutterStageView *view)
+ {
+@@ -465,11 +863,11 @@ clutter_stage_view_set_projection (ClutterStageView *view,
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+-  CoglFramebuffer *framebuffer;
+ 
+   priv->dirty_projection = FALSE;
+-  framebuffer = clutter_stage_view_get_framebuffer (view);
+-  cogl_framebuffer_set_projection_matrix (framebuffer, matrix);
++  clutter_stage_view_foreach_front_buffer (view,
++                                           set_framebuffer_projection_matrix,
++                                           matrix);
+ }
+ 
+ void
+@@ -593,10 +991,20 @@ clutter_stage_view_dispose (GObject *object)
+   ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
++  int i;
+ 
+   g_clear_pointer (&priv->name, g_free);
+   g_clear_pointer (&priv->framebuffer, cogl_object_unref);
++
+   g_clear_pointer (&priv->shadow.framebuffer, cogl_object_unref);
++  for (i = 0; i < G_N_ELEMENTS (priv->shadow.dma_buf.handles); i++)
++    {
++      g_clear_pointer (&priv->shadow.dma_buf.handles[i],
++                       cogl_dma_buf_handle_free);
++    }
++  g_clear_pointer (&priv->shadow.dma_buf.damage_history,
++                   clutter_damage_history_free);
++
+   g_clear_pointer (&priv->offscreen, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
+ 
+diff --git a/clutter/clutter/clutter-util.c b/clutter/clutter/clutter-util.c
+index ed52b69774..834adae39a 100644
+--- a/clutter/clutter/clutter-util.c
++++ b/clutter/clutter/clutter-util.c
+@@ -210,6 +210,28 @@ _clutter_util_rectangle_intersection (const cairo_rectangle_int_t *src1,
+     }
+ }
+ 
++gboolean
++_clutter_util_rectangle_contains (const cairo_rectangle_int_t *src1,
++                                  const cairo_rectangle_int_t *src2)
++{
++  int x1, y1, x2, y2;
++
++  x1 = MAX (src1->x, src2->x);
++  y1 = MAX (src1->y, src2->y);
++
++  x2 = MIN (src1->x + (int) src1->width,  src2->x + (int) src2->width);
++  y2 = MIN (src1->y + (int) src1->height, src2->y + (int) src2->height);
++
++  if (x1 >= x2 || y1 >= y2)
++    {
++      return FALSE;
++    }
++  else
++    {
++      return TRUE;
++    }
++}
++
+ float
+ _clutter_util_matrix_determinant (const ClutterMatrix *matrix)
+ {
+-- 
+2.28.0
+
+
+From 9968d4aeefc2c47a63e12f977dad031672a63abe Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Sat, 7 Mar 2020 20:29:09 +0100
+Subject: [PATCH 20/20] clutter/stage-cogl: Use view fb instead of onscreen fb
+ for debug-drawing
+
+We need to use the framebuffer of the view instead of the onscreen
+framebuffer when painting the damage region, otherwise the redraw clips
+on rotated monitors won't be shown correctly.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 8e1bd64e05c3098fcce4f916f9e4468decb8f30c)
+---
+ clutter/clutter/cogl/clutter-stage-cogl.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index 11273ec894..3f1f609c4e 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -353,7 +353,7 @@ paint_damage_region (ClutterStageWindow    *stage_window,
+                      ClutterStageView      *view,
+                      cairo_rectangle_int_t *swap_region)
+ {
+-  CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (view);
++  CoglFramebuffer *framebuffer = clutter_stage_view_get_framebuffer (view);
+   CoglContext *ctx = cogl_framebuffer_get_context (framebuffer);
+   static CoglPipeline *overlay_blue = NULL;
+   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
+-- 
+2.28.0
+
diff --git a/SPECS/mutter.spec b/SPECS/mutter.spec
index dd96fb4..940675a 100644
--- a/SPECS/mutter.spec
+++ b/SPECS/mutter.spec
@@ -8,7 +8,7 @@
 
 Name:          mutter
 Version:       3.32.2
-Release:       48%{?dist}
+Release:       57%{?dist}
 Summary:       Window and compositing manager based on Clutter
 
 License:       GPLv2+
@@ -159,6 +159,30 @@ Patch501: 0001-window-actor-Don-t-show-actor-until-meta_window_acto.patch
 Patch502: 0001-monitor-manager-kms-Trigger-hotplug-processing-on-gp.patch
 Patch503: 0002-gpu-kms-Reset-CRTC-mode-and-output-list-if-no-resour.patch
 
+# Add tile based shadow buffer damage tracking (#1670273)
+Patch504: shadow-buffer-tile-damage.patch
+
+# Add PING_TIMEOUT_DELAY to mutter MetaPreferences (#1886034)
+Patch505: 0001-display-Make-check-alive-timeout-configureable.patch
+
+# Polyinstantiation (#1861769)
+Patch506: 0001-xwayland-Don-t-spew-warnings-when-looking-for-X11-di.patch
+Patch507: 0002-xwayland-Make-sure-tmp-.X11-unix-exists.patch
+
+# Mitigate nouveau misidentifying connectors (#1786496)
+Patch508: 0001-monitor-config-manager-Handle-multiple-builtin-panel.patch
+
+# Don't ever enable double buffered shadowfb and fix software rendering
+# detection (#1921151)
+Patch509: 0001-clutter-stage-view-Hide-double-buffered-shadowfb-beh.patch
+Patch510: 0002-cogl-gpu-info-Fix-software-acceleration-detection.patch
+
+# Backport of geometric picking, improving performance and fixing picking
+# 10bpc pixel formats (#1919467)
+Patch511: geometric-picking.patch
+
+Patch520: 0001-clutter-Backport-of-touch-mode.patch
+
 BuildRequires: chrpath
 BuildRequires: pango-devel
 BuildRequires: startup-notification-devel
@@ -300,9 +324,43 @@ desktop-file-validate %{buildroot}/%{_datadir}/applications/%{name}.desktop
 %{_datadir}/mutter-%{mutter_api_version}/tests
 
 %changelog
-* Mon Sep 21 2020 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-48
+* Mon Feb 22 2021 Carlos Garnacho <cgarnach@redhat.com> - 3.32.2-57
+- Backport touch-mode
+  Resolves: #1833787
+
+* Tue Feb 09 2021 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-56
+- Backport geometric picking patches
+  Resolves: #1919467
+
+* Tue Feb 09 2021 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-55
+- Fix slow nouveau with llvmpipe
+  Resolves: #1921151
+
+* Tue Jan 12 2021 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-54
+- Fix polyinstantiation patch backport
+  Resolves: #1861769
+
+* Thu Dec 17 2020 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-53
+- Fix test case backport
+  Related: #1786496
+
+* Thu Dec 17 2020 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-52
+- Support polyinstantiation
+  Resolves: #1861769
+- Mitigate nouveau misidentifying connectors
+  Resolves: #1786496
+
+* Mon Dec 07 2020 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-51
+- Add PING_TIMEOUT_DELAY to mutter MetaPreferences
+  Resolves: #1886034
+
+* Thu Nov 26 2020 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-50
 - Fix GLX stereo buffer rebase error
-  Resolves: #1880339
+  Resolves: #1889528
+
+* Tue Nov 10 2020 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-49
+- Add tile based shadow buffer damage tracking
+  Resolves: #1670273
 
 * Thu Sep 03 2020 Florian Müllner <fmuellner@redhat.com> - 3.32.2-47
 - Fix screen sharing on wayland