diff --git a/SOURCES/0001-Add-support-for-quad-buffer-stereo.patch b/SOURCES/0001-Add-support-for-quad-buffer-stereo.patch
index 38a1213..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 8d7356fd7439f94f163438d55f2b2d3d918de96d 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
@@ -21,7 +21,7 @@ texture_from_pixmap.
  src/compositor/compositor-private.h          |   9 ++
  src/compositor/compositor.c                  | 125 +++++++++++++++
  src/compositor/meta-shaped-texture-private.h |   5 +-
- src/compositor/meta-shaped-texture.c         |  84 +++++++++-
+ src/compositor/meta-shaped-texture.c         |  85 +++++++++-
  src/compositor/meta-surface-actor-wayland.c  |   2 +-
  src/compositor/meta-surface-actor-x11.c      |  54 ++++++-
  src/compositor/meta-surface-actor-x11.h      |   5 +
@@ -32,7 +32,7 @@ texture_from_pixmap.
  src/core/stereo.h                            |  28 ++++
  src/meson.build                              |   2 +
  src/wayland/meta-wayland-surface.c           |   2 +-
- 14 files changed, 481 insertions(+), 20 deletions(-)
+ 14 files changed, 482 insertions(+), 20 deletions(-)
  create mode 100644 src/core/stereo.c
  create mode 100644 src/core/stereo.h
 
@@ -265,7 +265,7 @@ index a86a2bff0..d0efdd4dc 100644
                                              gboolean           is_y_inverted);
  void meta_shaped_texture_set_snippet (MetaShapedTexture *stex,
 diff --git a/src/compositor/meta-shaped-texture.c b/src/compositor/meta-shaped-texture.c
-index d64e214e5..e77a32109 100644
+index d64e214e5..332b4c814 100644
 --- a/src/compositor/meta-shaped-texture.c
 +++ b/src/compositor/meta-shaped-texture.c
 @@ -88,8 +88,10 @@ struct _MetaShapedTexture
@@ -287,7 +287,7 @@ index d64e214e5..e77a32109 100644
  
    stex->texture = NULL;
    stex->mask_texture = NULL;
-@@ -297,6 +300,9 @@ meta_shaped_texture_dispose (GObject *object)
+@@ -297,7 +300,11 @@ meta_shaped_texture_dispose (GObject *object)
      meta_texture_tower_free (stex->paint_tower);
    stex->paint_tower = NULL;
  
@@ -295,9 +295,11 @@ index d64e214e5..e77a32109 100644
 +  g_clear_pointer (&stex->paint_tower_right, meta_texture_tower_free);
 +
    g_clear_pointer (&stex->texture, cogl_object_unref);
++  g_clear_pointer (&stex->texture_right, cogl_object_unref);
    g_clear_pointer (&stex->opaque_region, cairo_region_destroy);
  
-@@ -507,8 +513,9 @@ paint_clipped_rectangle (MetaShapedTexture     *stex,
+   meta_shaped_texture_set_mask_texture (stex, NULL);
+@@ -507,8 +514,9 @@ paint_clipped_rectangle (MetaShapedTexture     *stex,
  }
  
  static void
@@ -309,7 +311,7 @@ index d64e214e5..e77a32109 100644
  {
    int width, height;
  
-@@ -516,8 +523,11 @@ set_cogl_texture (MetaShapedTexture *stex,
+@@ -516,8 +524,11 @@ set_cogl_texture (MetaShapedTexture *stex,
  
    if (stex->texture)
      cogl_object_unref (stex->texture);
@@ -321,7 +323,7 @@ index d64e214e5..e77a32109 100644
  
    if (cogl_tex != NULL)
      {
-@@ -531,6 +541,9 @@ set_cogl_texture (MetaShapedTexture *stex,
+@@ -531,6 +542,9 @@ set_cogl_texture (MetaShapedTexture *stex,
        height = 0;
      }
  
@@ -331,7 +333,7 @@ index d64e214e5..e77a32109 100644
    if (stex->tex_width != width ||
        stex->tex_height != height)
      {
-@@ -544,8 +557,23 @@ set_cogl_texture (MetaShapedTexture *stex,
+@@ -544,8 +558,23 @@ set_cogl_texture (MetaShapedTexture *stex,
     * previous buffer. We only queue a redraw in response to surface
     * damage. */
  
@@ -356,7 +358,7 @@ index d64e214e5..e77a32109 100644
  }
  
  static gboolean
-@@ -779,7 +807,9 @@ meta_shaped_texture_paint (ClutterActor *actor)
+@@ -779,7 +808,9 @@ meta_shaped_texture_paint (ClutterActor *actor)
  {
    MetaShapedTexture *stex = META_SHAPED_TEXTURE (actor);
    CoglTexture *paint_tex;
@@ -366,7 +368,7 @@ index d64e214e5..e77a32109 100644
  
    if (!stex->texture)
      return;
-@@ -841,7 +871,32 @@ meta_shaped_texture_paint (ClutterActor *actor)
+@@ -841,7 +872,32 @@ meta_shaped_texture_paint (ClutterActor *actor)
      return;
  
    fb = cogl_get_draw_framebuffer ();
@@ -400,7 +402,7 @@ index d64e214e5..e77a32109 100644
  }
  
  static void
-@@ -915,6 +970,12 @@ meta_shaped_texture_set_create_mipmaps (MetaShapedTexture *stex,
+@@ -915,6 +971,12 @@ meta_shaped_texture_set_create_mipmaps (MetaShapedTexture *stex,
        stex->create_mipmaps = create_mipmaps;
        base_texture = create_mipmaps ? stex->texture : NULL;
        meta_texture_tower_set_base_texture (stex->paint_tower, base_texture);
@@ -413,7 +415,7 @@ index d64e214e5..e77a32109 100644
      }
  }
  
-@@ -1046,6 +1107,12 @@ meta_shaped_texture_update_area (MetaShapedTexture *stex,
+@@ -1046,6 +1108,12 @@ meta_shaped_texture_update_area (MetaShapedTexture *stex,
                                    clip.y,
                                    clip.width,
                                    clip.height);
@@ -426,7 +428,7 @@ index d64e214e5..e77a32109 100644
  
    stex->prev_invalidation = stex->last_invalidation;
    stex->last_invalidation = g_get_monotonic_time ();
-@@ -1092,17 +1159,18 @@ meta_shaped_texture_update_area (MetaShapedTexture *stex,
+@@ -1092,17 +1160,18 @@ meta_shaped_texture_update_area (MetaShapedTexture *stex,
  }
  
  /**
@@ -901,5 +903,5 @@ index da0acfcbb..ddad1a45c 100644
                meta_shaped_texture_set_is_y_inverted (stex, is_y_inverted);
                g_clear_pointer (&snippet, cogl_object_unref);
 -- 
-2.25.1
+2.28.0
 
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-monitor-manager-kms-Trigger-hotplug-processing-on-gp.patch b/SOURCES/0001-monitor-manager-kms-Trigger-hotplug-processing-on-gp.patch
new file mode 100644
index 0000000..427bb17
--- /dev/null
+++ b/SOURCES/0001-monitor-manager-kms-Trigger-hotplug-processing-on-gp.patch
@@ -0,0 +1,41 @@
+From 9f8564ce066aeb704341d6f926daec0045243b70 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 25 Jun 2020 10:06:38 +0200
+Subject: [PATCH 1/2] monitor-manager-kms: Trigger hotplug processing on gpu
+ removal
+
+---
+ src/backends/native/meta-monitor-manager-kms.c | 16 +++++++++++-----
+ 1 file changed, 11 insertions(+), 5 deletions(-)
+
+diff --git a/src/backends/native/meta-monitor-manager-kms.c b/src/backends/native/meta-monitor-manager-kms.c
+index 9a0364441a..2819881576 100644
+--- a/src/backends/native/meta-monitor-manager-kms.c
++++ b/src/backends/native/meta-monitor-manager-kms.c
+@@ -470,12 +470,18 @@ on_uevent (GUdevClient *client,
+ 
+       if (!g_strcmp0 (seat_id, device_seat))
+         handle_gpu_hotplug (manager_kms, device);
+-    }
+-
+-  if (!g_udev_device_get_property_as_boolean (device, "HOTPLUG"))
+-    return;
+ 
+-  handle_hotplug_event (manager);
++      handle_hotplug_event (manager);
++    }
++  else if (g_str_equal (action, "remove") &&
++           g_udev_device_get_device_file (device) != NULL)
++    {
++      handle_hotplug_event (manager);
++    }
++  else if (g_udev_device_get_property_as_boolean (device, "HOTPLUG"))
++    {
++      handle_hotplug_event (manager);
++    }
+ }
+ 
+ static void
+-- 
+2.26.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-gpu-kms-Reset-CRTC-mode-and-output-list-if-no-resour.patch b/SOURCES/0002-gpu-kms-Reset-CRTC-mode-and-output-list-if-no-resour.patch
new file mode 100644
index 0000000..5a1c1ff
--- /dev/null
+++ b/SOURCES/0002-gpu-kms-Reset-CRTC-mode-and-output-list-if-no-resour.patch
@@ -0,0 +1,28 @@
+From a192b9abd77aa14ae794850e41d210472f86b9b0 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 25 Jun 2020 10:09:48 +0200
+Subject: [PATCH 2/2] gpu-kms: Reset CRTC, mode and output list if no resources
+
+On device removal, the next resource retrieval will fail; handle this by
+just clearing the CRTC, mode and outputs.
+---
+ src/backends/native/meta-gpu-kms.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/backends/native/meta-gpu-kms.c b/src/backends/native/meta-gpu-kms.c
+index 93e509def5..dc93abb7b1 100644
+--- a/src/backends/native/meta-gpu-kms.c
++++ b/src/backends/native/meta-gpu-kms.c
+@@ -871,6 +871,9 @@ meta_gpu_kms_read_current (MetaGpu  *gpu,
+                      local_error->message);
+           gpu_kms->resources_init_failed_before = TRUE;
+         }
++      meta_gpu_take_outputs (gpu, NULL);
++      meta_gpu_take_modes (gpu, NULL);
++      meta_gpu_take_crtcs (gpu, NULL);
+       return TRUE;
+     }
+ 
+-- 
+2.26.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/cursor-move-only-screen-cast-fixes.patch b/SOURCES/cursor-move-only-screen-cast-fixes.patch
new file mode 100644
index 0000000..03d9814
--- /dev/null
+++ b/SOURCES/cursor-move-only-screen-cast-fixes.patch
@@ -0,0 +1,1115 @@
+From 30caca0cb389dcbbab3d7ba72b92fce8e243b30b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 3 Jul 2020 16:42:45 +0200
+Subject: [PATCH 1/9] screen-cast-src: Make the two record vfuncs more
+ similarly named
+
+Both do more or less the same but with different methods - one puts
+pixels into a buffer using the CPU, the other puts pixels into a buffer
+using the GPU.
+
+However, they are behaving slightly different, which they shouldn't.
+Lets first address the misleading disconnect in naming, and later we'll
+make them behave more similarly.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1351
+(cherry picked from commit d07335cd4ca094d790eac75e75cff01a28fda827)
+---
+ .../meta-screen-cast-monitor-stream-src.c       | 15 ++++++++-------
+ src/backends/meta-screen-cast-stream-src.c      | 17 +++++++++--------
+ src/backends/meta-screen-cast-stream-src.h      |  8 ++++----
+ .../meta-screen-cast-window-stream-src.c        | 15 ++++++++-------
+ 4 files changed, 29 insertions(+), 26 deletions(-)
+
+diff --git a/src/backends/meta-screen-cast-monitor-stream-src.c b/src/backends/meta-screen-cast-monitor-stream-src.c
+index 655b682610..a1a98eb05b 100644
+--- a/src/backends/meta-screen-cast-monitor-stream-src.c
++++ b/src/backends/meta-screen-cast-monitor-stream-src.c
+@@ -347,8 +347,8 @@ meta_screen_cast_monitor_stream_src_disable (MetaScreenCastStreamSrc *src)
+ }
+ 
+ static gboolean
+-meta_screen_cast_monitor_stream_src_record_frame (MetaScreenCastStreamSrc *src,
+-                                                  uint8_t                 *data)
++meta_screen_cast_monitor_stream_src_record_to_buffer (MetaScreenCastStreamSrc *src,
++                                                      uint8_t                 *data)
+ {
+   MetaScreenCastMonitorStreamSrc *monitor_src =
+     META_SCREEN_CAST_MONITOR_STREAM_SRC (src);
+@@ -368,8 +368,8 @@ meta_screen_cast_monitor_stream_src_record_frame (MetaScreenCastStreamSrc *src,
+ }
+ 
+ static gboolean
+-meta_screen_cast_monitor_stream_src_blit_to_framebuffer (MetaScreenCastStreamSrc *src,
+-                                                         CoglFramebuffer         *framebuffer)
++meta_screen_cast_monitor_stream_src_record_to_framebuffer (MetaScreenCastStreamSrc *src,
++                                                           CoglFramebuffer         *framebuffer)
+ {
+   MetaScreenCastMonitorStreamSrc *monitor_src =
+     META_SCREEN_CAST_MONITOR_STREAM_SRC (src);
+@@ -551,9 +551,10 @@ meta_screen_cast_monitor_stream_src_class_init (MetaScreenCastMonitorStreamSrcCl
+   src_class->get_specs = meta_screen_cast_monitor_stream_src_get_specs;
+   src_class->enable = meta_screen_cast_monitor_stream_src_enable;
+   src_class->disable = meta_screen_cast_monitor_stream_src_disable;
+-  src_class->record_frame = meta_screen_cast_monitor_stream_src_record_frame;
+-  src_class->blit_to_framebuffer =
+-    meta_screen_cast_monitor_stream_src_blit_to_framebuffer;
++  src_class->record_to_buffer =
++    meta_screen_cast_monitor_stream_src_record_to_buffer;
++  src_class->record_to_framebuffer =
++    meta_screen_cast_monitor_stream_src_record_to_framebuffer;
+   src_class->set_cursor_metadata =
+     meta_screen_cast_monitor_stream_src_set_cursor_metadata;
+ }
+diff --git a/src/backends/meta-screen-cast-stream-src.c b/src/backends/meta-screen-cast-stream-src.c
+index b77186415f..bafb82388d 100644
+--- a/src/backends/meta-screen-cast-stream-src.c
++++ b/src/backends/meta-screen-cast-stream-src.c
+@@ -133,23 +133,23 @@ meta_screen_cast_stream_src_get_videocrop (MetaScreenCastStreamSrc *src,
+ }
+ 
+ static gboolean
+-meta_screen_cast_stream_src_record_frame (MetaScreenCastStreamSrc *src,
+-                                          uint8_t                 *data)
++meta_screen_cast_stream_src_record_to_buffer (MetaScreenCastStreamSrc *src,
++                                              uint8_t                 *data)
+ {
+   MetaScreenCastStreamSrcClass *klass =
+     META_SCREEN_CAST_STREAM_SRC_GET_CLASS (src);
+ 
+-  return klass->record_frame (src, data);
++  return klass->record_to_buffer (src, data);
+ }
+ 
+ static gboolean
+-meta_screen_cast_stream_src_blit_to_framebuffer (MetaScreenCastStreamSrc *src,
+-                                                 CoglFramebuffer         *framebuffer)
++meta_screen_cast_stream_src_record_to_framebuffer (MetaScreenCastStreamSrc *src,
++                                                   CoglFramebuffer         *framebuffer)
+ {
+   MetaScreenCastStreamSrcClass *klass =
+     META_SCREEN_CAST_STREAM_SRC_GET_CLASS (src);
+ 
+-  return klass->blit_to_framebuffer (src, framebuffer);
++  return klass->record_to_framebuffer (src, framebuffer);
+ }
+ 
+ static void
+@@ -417,7 +417,7 @@ do_record_frame (MetaScreenCastStreamSrc *src,
+   if (spa_buffer->datas[0].data ||
+       spa_buffer->datas[0].type == SPA_DATA_MemFd)
+     {
+-      return meta_screen_cast_stream_src_record_frame (src, data);
++      return meta_screen_cast_stream_src_record_to_buffer (src, data);
+     }
+   else if (spa_buffer->datas[0].type == SPA_DATA_DmaBuf)
+     {
+@@ -427,7 +427,8 @@ do_record_frame (MetaScreenCastStreamSrc *src,
+       CoglFramebuffer *dmabuf_fbo =
+         cogl_dma_buf_handle_get_framebuffer (dmabuf_handle);
+ 
+-      return meta_screen_cast_stream_src_blit_to_framebuffer (src, dmabuf_fbo);
++      return meta_screen_cast_stream_src_record_to_framebuffer (src,
++                                                                dmabuf_fbo);
+     }
+ 
+   return FALSE;
+diff --git a/src/backends/meta-screen-cast-stream-src.h b/src/backends/meta-screen-cast-stream-src.h
+index 3f6a1af2bb..0eda02f717 100644
+--- a/src/backends/meta-screen-cast-stream-src.h
++++ b/src/backends/meta-screen-cast-stream-src.h
+@@ -53,10 +53,10 @@ struct _MetaScreenCastStreamSrcClass
+                       float                   *frame_rate);
+   void (* enable) (MetaScreenCastStreamSrc *src);
+   void (* disable) (MetaScreenCastStreamSrc *src);
+-  gboolean (* record_frame) (MetaScreenCastStreamSrc *src,
+-                             uint8_t                 *data);
+-  gboolean (* blit_to_framebuffer) (MetaScreenCastStreamSrc *src,
+-                                    CoglFramebuffer         *framebuffer);
++  gboolean (* record_to_buffer) (MetaScreenCastStreamSrc *src,
++                                 uint8_t                 *data);
++  gboolean (* record_to_framebuffer) (MetaScreenCastStreamSrc *src,
++                                      CoglFramebuffer         *framebuffer);
+   gboolean (* get_videocrop) (MetaScreenCastStreamSrc *src,
+                               MetaRectangle           *crop_rect);
+   void (* set_cursor_metadata) (MetaScreenCastStreamSrc *src,
+diff --git a/src/backends/meta-screen-cast-window-stream-src.c b/src/backends/meta-screen-cast-window-stream-src.c
+index c252b4356b..281df5e7b2 100644
+--- a/src/backends/meta-screen-cast-window-stream-src.c
++++ b/src/backends/meta-screen-cast-window-stream-src.c
+@@ -462,8 +462,8 @@ meta_screen_cast_window_stream_src_disable (MetaScreenCastStreamSrc *src)
+ }
+ 
+ static gboolean
+-meta_screen_cast_window_stream_src_record_frame (MetaScreenCastStreamSrc *src,
+-                                                 uint8_t                 *data)
++meta_screen_cast_window_stream_src_record_to_buffer (MetaScreenCastStreamSrc *src,
++                                                     uint8_t                 *data)
+ {
+   MetaScreenCastWindowStreamSrc *window_src =
+     META_SCREEN_CAST_WINDOW_STREAM_SRC (src);
+@@ -474,8 +474,8 @@ meta_screen_cast_window_stream_src_record_frame (MetaScreenCastStreamSrc *src,
+ }
+ 
+ static gboolean
+-meta_screen_cast_window_stream_src_blit_to_framebuffer (MetaScreenCastStreamSrc *src,
+-                                                        CoglFramebuffer         *framebuffer)
++meta_screen_cast_window_stream_src_record_to_framebuffer (MetaScreenCastStreamSrc *src,
++                                                          CoglFramebuffer         *framebuffer)
+ {
+   MetaScreenCastWindowStreamSrc *window_src =
+     META_SCREEN_CAST_WINDOW_STREAM_SRC (src);
+@@ -591,9 +591,10 @@ meta_screen_cast_window_stream_src_class_init (MetaScreenCastWindowStreamSrcClas
+   src_class->get_specs = meta_screen_cast_window_stream_src_get_specs;
+   src_class->enable = meta_screen_cast_window_stream_src_enable;
+   src_class->disable = meta_screen_cast_window_stream_src_disable;
+-  src_class->record_frame = meta_screen_cast_window_stream_src_record_frame;
+-  src_class->blit_to_framebuffer =
+-    meta_screen_cast_window_stream_src_blit_to_framebuffer;
++  src_class->record_to_buffer =
++    meta_screen_cast_window_stream_src_record_to_buffer;
++  src_class->record_to_framebuffer =
++    meta_screen_cast_window_stream_src_record_to_framebuffer;
+   src_class->get_videocrop = meta_screen_cast_window_stream_src_get_videocrop;
+   src_class->set_cursor_metadata = meta_screen_cast_window_stream_src_set_cursor_metadata;
+ }
+-- 
+2.26.2
+
+
+From ddc2094222fb55143922364cd4887cb18f856628 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 3 Jul 2020 16:46:44 +0200
+Subject: [PATCH 2/9] screen-cast/window-stream-src: Fix indentation
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1351
+(cherry picked from commit b1d45820efc5c9136f12d8a3b97a573a2eede9e7)
+---
+ src/backends/meta-screen-cast-window-stream-src.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/backends/meta-screen-cast-window-stream-src.c b/src/backends/meta-screen-cast-window-stream-src.c
+index 281df5e7b2..abdc791575 100644
+--- a/src/backends/meta-screen-cast-window-stream-src.c
++++ b/src/backends/meta-screen-cast-window-stream-src.c
+@@ -488,8 +488,8 @@ meta_screen_cast_window_stream_src_record_to_framebuffer (MetaScreenCastStreamSr
+   stream_rect.height = get_stream_height (window_src);
+ 
+   if (!meta_screen_cast_window_blit_to_framebuffer (window_src->screen_cast_window,
+-                                                     &stream_rect,
+-                                                     framebuffer))
++                                                    &stream_rect,
++                                                    framebuffer))
+     return FALSE;
+ 
+   stream = meta_screen_cast_stream_src_get_stream (src);
+-- 
+2.26.2
+
+
+From 59382848840aeb5c6491412742f474a3fb61639e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 3 Jul 2020 16:48:52 +0200
+Subject: [PATCH 3/9] screen-cast/src: Add flag to maybe_record()
+
+Will later be used to make recording avoid recording actual pixel
+content if e.g. only the cursor moved.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1351
+(cherry picked from commit 92db8902d9c3229a13d104bba71dd74f14d6dfac)
+---
+ src/backends/meta-screen-cast-monitor-stream-src.c |  8 ++++++--
+ src/backends/meta-screen-cast-stream-src.c         |  3 ++-
+ src/backends/meta-screen-cast-stream-src.h         |  8 +++++++-
+ src/backends/meta-screen-cast-window-stream-src.c  | 12 +++++++++---
+ 4 files changed, 24 insertions(+), 7 deletions(-)
+
+diff --git a/src/backends/meta-screen-cast-monitor-stream-src.c b/src/backends/meta-screen-cast-monitor-stream-src.c
+index a1a98eb05b..8d57fafc0f 100644
+--- a/src/backends/meta-screen-cast-monitor-stream-src.c
++++ b/src/backends/meta-screen-cast-monitor-stream-src.c
+@@ -120,8 +120,10 @@ stage_painted (MetaStage        *stage,
+                gpointer          user_data)
+ {
+   MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (user_data);
++  MetaScreenCastRecordFlag flags;
+ 
+-  meta_screen_cast_stream_src_maybe_record_frame (src);
++  flags = META_SCREEN_CAST_RECORD_FLAG_NONE;
++  meta_screen_cast_stream_src_maybe_record_frame (src, flags);
+ }
+ 
+ static MetaBackend *
+@@ -180,6 +182,7 @@ sync_cursor_state (MetaScreenCastMonitorStreamSrc *monitor_src)
+ {
+   MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (monitor_src);
+   ClutterStage *stage = get_stage (monitor_src);
++  MetaScreenCastRecordFlag flags;
+ 
+   if (!is_cursor_in_stream (monitor_src))
+     return;
+@@ -187,7 +190,8 @@ sync_cursor_state (MetaScreenCastMonitorStreamSrc *monitor_src)
+   if (clutter_stage_is_redraw_queued (stage))
+     return;
+ 
+-  meta_screen_cast_stream_src_maybe_record_frame (src);
++  flags = META_SCREEN_CAST_RECORD_FLAG_NONE;
++  meta_screen_cast_stream_src_maybe_record_frame (src, flags);
+ }
+ 
+ static void
+diff --git a/src/backends/meta-screen-cast-stream-src.c b/src/backends/meta-screen-cast-stream-src.c
+index bafb82388d..303c030be7 100644
+--- a/src/backends/meta-screen-cast-stream-src.c
++++ b/src/backends/meta-screen-cast-stream-src.c
+@@ -435,7 +435,8 @@ do_record_frame (MetaScreenCastStreamSrc *src,
+ }
+ 
+ void
+-meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc *src)
++meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc  *src,
++                                                MetaScreenCastRecordFlag  flags)
+ {
+   MetaScreenCastStreamSrcPrivate *priv =
+     meta_screen_cast_stream_src_get_instance_private (src);
+diff --git a/src/backends/meta-screen-cast-stream-src.h b/src/backends/meta-screen-cast-stream-src.h
+index 0eda02f717..6c73d05c1d 100644
+--- a/src/backends/meta-screen-cast-stream-src.h
++++ b/src/backends/meta-screen-cast-stream-src.h
+@@ -37,6 +37,11 @@
+ 
+ typedef struct _MetaScreenCastStream MetaScreenCastStream;
+ 
++typedef enum _MetaScreenCastRecordFlag
++{
++  META_SCREEN_CAST_RECORD_FLAG_NONE = 0,
++} MetaScreenCastRecordFlag;
++
+ #define META_TYPE_SCREEN_CAST_STREAM_SRC (meta_screen_cast_stream_src_get_type ())
+ G_DECLARE_DERIVABLE_TYPE (MetaScreenCastStreamSrc,
+                           meta_screen_cast_stream_src,
+@@ -63,7 +68,8 @@ struct _MetaScreenCastStreamSrcClass
+                                 struct spa_meta_cursor  *spa_meta_cursor);
+ };
+ 
+-void meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc *src);
++void meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc  *src,
++                                                     MetaScreenCastRecordFlag  flags);
+ 
+ MetaScreenCastStream * meta_screen_cast_stream_src_get_stream (MetaScreenCastStreamSrc *src);
+ 
+diff --git a/src/backends/meta-screen-cast-window-stream-src.c b/src/backends/meta-screen-cast-window-stream-src.c
+index abdc791575..f64d00860a 100644
+--- a/src/backends/meta-screen-cast-window-stream-src.c
++++ b/src/backends/meta-screen-cast-window-stream-src.c
+@@ -338,8 +338,10 @@ screen_cast_window_damaged (MetaWindowActor               *actor,
+                             MetaScreenCastWindowStreamSrc *window_src)
+ {
+   MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (window_src);
++  MetaScreenCastRecordFlag flags;
+ 
+-  meta_screen_cast_stream_src_maybe_record_frame (src);
++  flags = META_SCREEN_CAST_RECORD_FLAG_NONE;
++  meta_screen_cast_stream_src_maybe_record_frame (src, flags);
+ }
+ 
+ static void
+@@ -376,6 +378,7 @@ static void
+ sync_cursor_state (MetaScreenCastWindowStreamSrc *window_src)
+ {
+   MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (window_src);
++  MetaScreenCastRecordFlag flags;
+ 
+   if (!is_cursor_in_stream (window_src))
+     return;
+@@ -383,7 +386,8 @@ sync_cursor_state (MetaScreenCastWindowStreamSrc *window_src)
+   if (meta_screen_cast_window_has_damage (window_src->screen_cast_window))
+     return;
+ 
+-  meta_screen_cast_stream_src_maybe_record_frame (src);
++  flags = META_SCREEN_CAST_RECORD_FLAG_NONE;
++  meta_screen_cast_stream_src_maybe_record_frame (src, flags);
+ }
+ 
+ static void
+@@ -412,6 +416,7 @@ meta_screen_cast_window_stream_src_enable (MetaScreenCastStreamSrc *src)
+   MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend);
+   MetaWindowActor *window_actor;
+   MetaScreenCastStream *stream;
++  MetaScreenCastRecordFlag flags;
+ 
+   window_actor = meta_window_actor_from_window (get_window (window_src));
+   if (!window_actor)
+@@ -449,7 +454,8 @@ meta_screen_cast_window_stream_src_enable (MetaScreenCastStreamSrc *src)
+       break;
+     }
+ 
+-  meta_screen_cast_stream_src_maybe_record_frame (src);
++  flags = META_SCREEN_CAST_RECORD_FLAG_NONE;
++  meta_screen_cast_stream_src_maybe_record_frame (src, flags);
+ }
+ 
+ static void
+-- 
+2.26.2
+
+
+From b8d76f2ded6a0c8b88403d97d4ea2c84993c0263 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 3 Jul 2020 16:52:43 +0200
+Subject: [PATCH 4/9] screen-cast: Let the reason for recording determine what
+ to record
+
+E.g. we'll have pointer movement that, if no painting is already
+scheduled, should only send new cursor metadata without any new pixel
+buffer. When this happens, tell next step to not record the pixels if
+this was the case, instead of having it rediscover this itself.
+
+Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/1323
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1351
+(cherry picked from commit cf88d648822eb6b7d412c08d4038c657d415bfff)
+---
+ .../meta-screen-cast-monitor-stream-src.c     |  5 +-
+ src/backends/meta-screen-cast-stream-src.c    | 51 +++++++++++--------
+ src/backends/meta-screen-cast-stream-src.h    |  1 +
+ .../meta-screen-cast-window-stream-src.c      |  4 +-
+ 4 files changed, 33 insertions(+), 28 deletions(-)
+
+diff --git a/src/backends/meta-screen-cast-monitor-stream-src.c b/src/backends/meta-screen-cast-monitor-stream-src.c
+index 8d57fafc0f..2352c3b3d8 100644
+--- a/src/backends/meta-screen-cast-monitor-stream-src.c
++++ b/src/backends/meta-screen-cast-monitor-stream-src.c
+@@ -190,7 +190,7 @@ sync_cursor_state (MetaScreenCastMonitorStreamSrc *monitor_src)
+   if (clutter_stage_is_redraw_queued (stage))
+     return;
+ 
+-  flags = META_SCREEN_CAST_RECORD_FLAG_NONE;
++  flags = META_SCREEN_CAST_RECORD_FLAG_CURSOR_ONLY;
+   meta_screen_cast_stream_src_maybe_record_frame (src, flags);
+ }
+ 
+@@ -361,9 +361,6 @@ meta_screen_cast_monitor_stream_src_record_to_buffer (MetaScreenCastStreamSrc *s
+   MetaLogicalMonitor *logical_monitor;
+ 
+   stage = get_stage (monitor_src);
+-  if (!clutter_stage_is_redraw_queued (stage))
+-    return FALSE;
+-
+   monitor = get_monitor (monitor_src);
+   logical_monitor = meta_monitor_get_logical_monitor (monitor);
+   clutter_stage_capture_into (stage, FALSE, &logical_monitor->rect, data);
+diff --git a/src/backends/meta-screen-cast-stream-src.c b/src/backends/meta-screen-cast-stream-src.c
+index 303c030be7..aa4b03b180 100644
+--- a/src/backends/meta-screen-cast-stream-src.c
++++ b/src/backends/meta-screen-cast-stream-src.c
+@@ -470,34 +470,41 @@ meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc  *src,
+       return;
+     }
+ 
+-  if (do_record_frame (src, spa_buffer, data))
++  if (!(flags & META_SCREEN_CAST_RECORD_FLAG_CURSOR_ONLY))
+     {
+-      struct spa_meta_region *spa_meta_video_crop;
++      if (do_record_frame (src, spa_buffer, data))
++        {
++          struct spa_meta_region *spa_meta_video_crop;
+ 
+-      spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize;
+-      spa_buffer->datas[0].chunk->stride = priv->video_stride;
++          spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize;
++          spa_buffer->datas[0].chunk->stride = priv->video_stride;
+ 
+-      /* Update VideoCrop if needed */
+-      spa_meta_video_crop =
+-        spa_buffer_find_meta_data (spa_buffer, SPA_META_VideoCrop,
+-                                   sizeof (*spa_meta_video_crop));
+-      if (spa_meta_video_crop)
+-        {
+-          if (meta_screen_cast_stream_src_get_videocrop (src, &crop_rect))
+-            {
+-              spa_meta_video_crop->region.position.x = crop_rect.x;
+-              spa_meta_video_crop->region.position.y = crop_rect.y;
+-              spa_meta_video_crop->region.size.width = crop_rect.width;
+-              spa_meta_video_crop->region.size.height = crop_rect.height;
+-            }
+-          else
++          /* Update VideoCrop if needed */
++          spa_meta_video_crop =
++            spa_buffer_find_meta_data (spa_buffer, SPA_META_VideoCrop,
++                                       sizeof (*spa_meta_video_crop));
++          if (spa_meta_video_crop)
+             {
+-              spa_meta_video_crop->region.position.x = 0;
+-              spa_meta_video_crop->region.position.y = 0;
+-              spa_meta_video_crop->region.size.width = priv->stream_width;
+-              spa_meta_video_crop->region.size.height = priv->stream_height;
++              if (meta_screen_cast_stream_src_get_videocrop (src, &crop_rect))
++                {
++                  spa_meta_video_crop->region.position.x = crop_rect.x;
++                  spa_meta_video_crop->region.position.y = crop_rect.y;
++                  spa_meta_video_crop->region.size.width = crop_rect.width;
++                  spa_meta_video_crop->region.size.height = crop_rect.height;
++                }
++              else
++                {
++                  spa_meta_video_crop->region.position.x = 0;
++                  spa_meta_video_crop->region.position.y = 0;
++                  spa_meta_video_crop->region.size.width = priv->stream_width;
++                  spa_meta_video_crop->region.size.height = priv->stream_height;
++                }
+             }
+         }
++      else
++        {
++          spa_buffer->datas[0].chunk->size = 0;
++        }
+     }
+   else
+     {
+diff --git a/src/backends/meta-screen-cast-stream-src.h b/src/backends/meta-screen-cast-stream-src.h
+index 6c73d05c1d..87054eedf5 100644
+--- a/src/backends/meta-screen-cast-stream-src.h
++++ b/src/backends/meta-screen-cast-stream-src.h
+@@ -40,6 +40,7 @@ typedef struct _MetaScreenCastStream MetaScreenCastStream;
+ typedef enum _MetaScreenCastRecordFlag
+ {
+   META_SCREEN_CAST_RECORD_FLAG_NONE = 0,
++  META_SCREEN_CAST_RECORD_FLAG_CURSOR_ONLY = 1 << 0,
+ } MetaScreenCastRecordFlag;
+ 
+ #define META_TYPE_SCREEN_CAST_STREAM_SRC (meta_screen_cast_stream_src_get_type ())
+diff --git a/src/backends/meta-screen-cast-window-stream-src.c b/src/backends/meta-screen-cast-window-stream-src.c
+index f64d00860a..63c3429df0 100644
+--- a/src/backends/meta-screen-cast-window-stream-src.c
++++ b/src/backends/meta-screen-cast-window-stream-src.c
+@@ -340,7 +340,7 @@ screen_cast_window_damaged (MetaWindowActor               *actor,
+   MetaScreenCastStreamSrc *src = META_SCREEN_CAST_STREAM_SRC (window_src);
+   MetaScreenCastRecordFlag flags;
+ 
+-  flags = META_SCREEN_CAST_RECORD_FLAG_NONE;
++  flags = META_SCREEN_CAST_RECORD_FLAG_CURSOR_ONLY;
+   meta_screen_cast_stream_src_maybe_record_frame (src, flags);
+ }
+ 
+@@ -386,7 +386,7 @@ sync_cursor_state (MetaScreenCastWindowStreamSrc *window_src)
+   if (meta_screen_cast_window_has_damage (window_src->screen_cast_window))
+     return;
+ 
+-  flags = META_SCREEN_CAST_RECORD_FLAG_NONE;
++  flags = META_SCREEN_CAST_RECORD_FLAG_CURSOR_ONLY;
+   meta_screen_cast_stream_src_maybe_record_frame (src, flags);
+ }
+ 
+-- 
+2.26.2
+
+
+From 5c925cf4de91c9fdd44cb1c13748b2f4d6187dd9 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 3 Jul 2020 16:57:01 +0200
+Subject: [PATCH 5/9] screen-cast/src: Make record functions return an error
+ when failing
+
+Now that we don't use the record function to early out depending on
+implicit state (don't record pixels if only cursor moved for example),
+let it simply report an error when it fails, as we should no longer ever
+return without pixels if nothing failed.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1351
+(cherry picked from commit 2d899596e21d43ab241d0ba37c0a9f90c2e610be)
+---
+ .../meta-screen-cast-monitor-stream-src.c     | 19 +++++------
+ src/backends/meta-screen-cast-stream-src.c    | 32 ++++++++++++-------
+ src/backends/meta-screen-cast-stream-src.h    | 10 +++---
+ .../meta-screen-cast-window-stream-src.c      | 16 +++++++---
+ 4 files changed, 45 insertions(+), 32 deletions(-)
+
+diff --git a/src/backends/meta-screen-cast-monitor-stream-src.c b/src/backends/meta-screen-cast-monitor-stream-src.c
+index 2352c3b3d8..27b3ea37d8 100644
+--- a/src/backends/meta-screen-cast-monitor-stream-src.c
++++ b/src/backends/meta-screen-cast-monitor-stream-src.c
+@@ -351,8 +351,9 @@ meta_screen_cast_monitor_stream_src_disable (MetaScreenCastStreamSrc *src)
+ }
+ 
+ static gboolean
+-meta_screen_cast_monitor_stream_src_record_to_buffer (MetaScreenCastStreamSrc *src,
+-                                                      uint8_t                 *data)
++meta_screen_cast_monitor_stream_src_record_to_buffer (MetaScreenCastStreamSrc  *src,
++                                                      uint8_t                  *data,
++                                                      GError                  **error)
+ {
+   MetaScreenCastMonitorStreamSrc *monitor_src =
+     META_SCREEN_CAST_MONITOR_STREAM_SRC (src);
+@@ -369,8 +370,9 @@ meta_screen_cast_monitor_stream_src_record_to_buffer (MetaScreenCastStreamSrc *s
+ }
+ 
+ static gboolean
+-meta_screen_cast_monitor_stream_src_record_to_framebuffer (MetaScreenCastStreamSrc *src,
+-                                                           CoglFramebuffer         *framebuffer)
++meta_screen_cast_monitor_stream_src_record_to_framebuffer (MetaScreenCastStreamSrc  *src,
++                                                           CoglFramebuffer          *framebuffer,
++                                                           GError                  **error)
+ {
+   MetaScreenCastMonitorStreamSrc *monitor_src =
+     META_SCREEN_CAST_MONITOR_STREAM_SRC (src);
+@@ -394,7 +396,6 @@ meta_screen_cast_monitor_stream_src_record_to_framebuffer (MetaScreenCastStreamS
+   for (l = _clutter_stage_peek_stage_views (stage); l; l = l->next)
+     {
+       ClutterStageView *view = CLUTTER_STAGE_VIEW (l->data);
+-      g_autoptr (GError) error = NULL;
+       CoglFramebuffer *view_framebuffer;
+       MetaRectangle view_layout;
+       int x, y;
+@@ -415,12 +416,8 @@ meta_screen_cast_monitor_stream_src_record_to_framebuffer (MetaScreenCastStreamS
+                                   x, y,
+                                   cogl_framebuffer_get_width (view_framebuffer),
+                                   cogl_framebuffer_get_height (view_framebuffer),
+-                                  &error))
+-        {
+-          g_warning ("Error blitting view into DMABuf framebuffer: %s",
+-                     error->message);
+-          return FALSE;
+-        }
++                                  error))
++        return FALSE;
+     }
+ 
+   cogl_framebuffer_finish (framebuffer);
+diff --git a/src/backends/meta-screen-cast-stream-src.c b/src/backends/meta-screen-cast-stream-src.c
+index aa4b03b180..b930d5e7c0 100644
+--- a/src/backends/meta-screen-cast-stream-src.c
++++ b/src/backends/meta-screen-cast-stream-src.c
+@@ -133,23 +133,25 @@ meta_screen_cast_stream_src_get_videocrop (MetaScreenCastStreamSrc *src,
+ }
+ 
+ static gboolean
+-meta_screen_cast_stream_src_record_to_buffer (MetaScreenCastStreamSrc *src,
+-                                              uint8_t                 *data)
++meta_screen_cast_stream_src_record_to_buffer (MetaScreenCastStreamSrc  *src,
++                                              uint8_t                  *data,
++                                              GError                  **error)
+ {
+   MetaScreenCastStreamSrcClass *klass =
+     META_SCREEN_CAST_STREAM_SRC_GET_CLASS (src);
+ 
+-  return klass->record_to_buffer (src, data);
++  return klass->record_to_buffer (src, data, error);
+ }
+ 
+ static gboolean
+-meta_screen_cast_stream_src_record_to_framebuffer (MetaScreenCastStreamSrc *src,
+-                                                   CoglFramebuffer         *framebuffer)
++meta_screen_cast_stream_src_record_to_framebuffer (MetaScreenCastStreamSrc  *src,
++                                                   CoglFramebuffer          *framebuffer,
++                                                   GError                  **error)
+ {
+   MetaScreenCastStreamSrcClass *klass =
+     META_SCREEN_CAST_STREAM_SRC_GET_CLASS (src);
+ 
+-  return klass->record_to_framebuffer (src, framebuffer);
++  return klass->record_to_framebuffer (src, framebuffer, error);
+ }
+ 
+ static void
+@@ -407,9 +409,10 @@ maybe_record_cursor (MetaScreenCastStreamSrc *src,
+ }
+ 
+ static gboolean
+-do_record_frame (MetaScreenCastStreamSrc *src,
+-                 struct spa_buffer       *spa_buffer,
+-                 uint8_t                 *data)
++do_record_frame (MetaScreenCastStreamSrc  *src,
++                 struct spa_buffer        *spa_buffer,
++                 uint8_t                  *data,
++                 GError                  **error)
+ {
+   MetaScreenCastStreamSrcPrivate *priv =
+     meta_screen_cast_stream_src_get_instance_private (src);
+@@ -417,7 +420,7 @@ do_record_frame (MetaScreenCastStreamSrc *src,
+   if (spa_buffer->datas[0].data ||
+       spa_buffer->datas[0].type == SPA_DATA_MemFd)
+     {
+-      return meta_screen_cast_stream_src_record_to_buffer (src, data);
++      return meta_screen_cast_stream_src_record_to_buffer (src, data, error);
+     }
+   else if (spa_buffer->datas[0].type == SPA_DATA_DmaBuf)
+     {
+@@ -428,9 +431,12 @@ do_record_frame (MetaScreenCastStreamSrc *src,
+         cogl_dma_buf_handle_get_framebuffer (dmabuf_handle);
+ 
+       return meta_screen_cast_stream_src_record_to_framebuffer (src,
+-                                                                dmabuf_fbo);
++                                                                dmabuf_fbo,
++                                                                error);
+     }
+ 
++  g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
++               "Unknown SPA buffer type %u", spa_buffer->datas[0].type);
+   return FALSE;
+ }
+ 
+@@ -445,6 +451,7 @@ meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc  *src,
+   struct spa_buffer *spa_buffer;
+   uint8_t *data = NULL;
+   uint64_t now_us;
++  g_autoptr (GError) error = NULL;
+ 
+   now_us = g_get_monotonic_time ();
+   if (priv->video_format.max_framerate.num > 0 &&
+@@ -472,7 +479,7 @@ meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc  *src,
+ 
+   if (!(flags & META_SCREEN_CAST_RECORD_FLAG_CURSOR_ONLY))
+     {
+-      if (do_record_frame (src, spa_buffer, data))
++      if (do_record_frame (src, spa_buffer, data, &error))
+         {
+           struct spa_meta_region *spa_meta_video_crop;
+ 
+@@ -503,6 +510,7 @@ meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc  *src,
+         }
+       else
+         {
++          g_warning ("Failed to record screen cast frame: %s", error->message);
+           spa_buffer->datas[0].chunk->size = 0;
+         }
+     }
+diff --git a/src/backends/meta-screen-cast-stream-src.h b/src/backends/meta-screen-cast-stream-src.h
+index 87054eedf5..152790ecfb 100644
+--- a/src/backends/meta-screen-cast-stream-src.h
++++ b/src/backends/meta-screen-cast-stream-src.h
+@@ -59,10 +59,12 @@ struct _MetaScreenCastStreamSrcClass
+                       float                   *frame_rate);
+   void (* enable) (MetaScreenCastStreamSrc *src);
+   void (* disable) (MetaScreenCastStreamSrc *src);
+-  gboolean (* record_to_buffer) (MetaScreenCastStreamSrc *src,
+-                                 uint8_t                 *data);
+-  gboolean (* record_to_framebuffer) (MetaScreenCastStreamSrc *src,
+-                                      CoglFramebuffer         *framebuffer);
++  gboolean (* record_to_buffer) (MetaScreenCastStreamSrc  *src,
++                                 uint8_t                  *data,
++                                 GError                  **error);
++  gboolean (* record_to_framebuffer) (MetaScreenCastStreamSrc  *src,
++                                      CoglFramebuffer          *framebuffer,
++                                      GError                  **error);
+   gboolean (* get_videocrop) (MetaScreenCastStreamSrc *src,
+                               MetaRectangle           *crop_rect);
+   void (* set_cursor_metadata) (MetaScreenCastStreamSrc *src,
+diff --git a/src/backends/meta-screen-cast-window-stream-src.c b/src/backends/meta-screen-cast-window-stream-src.c
+index 63c3429df0..70e868997e 100644
+--- a/src/backends/meta-screen-cast-window-stream-src.c
++++ b/src/backends/meta-screen-cast-window-stream-src.c
+@@ -468,8 +468,9 @@ meta_screen_cast_window_stream_src_disable (MetaScreenCastStreamSrc *src)
+ }
+ 
+ static gboolean
+-meta_screen_cast_window_stream_src_record_to_buffer (MetaScreenCastStreamSrc *src,
+-                                                     uint8_t                 *data)
++meta_screen_cast_window_stream_src_record_to_buffer (MetaScreenCastStreamSrc  *src,
++                                                     uint8_t                  *data,
++                                                     GError                  **error)
+ {
+   MetaScreenCastWindowStreamSrc *window_src =
+     META_SCREEN_CAST_WINDOW_STREAM_SRC (src);
+@@ -480,8 +481,9 @@ meta_screen_cast_window_stream_src_record_to_buffer (MetaScreenCastStreamSrc *sr
+ }
+ 
+ static gboolean
+-meta_screen_cast_window_stream_src_record_to_framebuffer (MetaScreenCastStreamSrc *src,
+-                                                          CoglFramebuffer         *framebuffer)
++meta_screen_cast_window_stream_src_record_to_framebuffer (MetaScreenCastStreamSrc  *src,
++                                                          CoglFramebuffer          *framebuffer,
++                                                          GError                  **error)
+ {
+   MetaScreenCastWindowStreamSrc *window_src =
+     META_SCREEN_CAST_WINDOW_STREAM_SRC (src);
+@@ -496,7 +498,11 @@ meta_screen_cast_window_stream_src_record_to_framebuffer (MetaScreenCastStreamSr
+   if (!meta_screen_cast_window_blit_to_framebuffer (window_src->screen_cast_window,
+                                                     &stream_rect,
+                                                     framebuffer))
+-    return FALSE;
++    {
++      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
++                   "Failed to blit window content to framebuffer");
++      return FALSE;
++    }
+ 
+   stream = meta_screen_cast_stream_src_get_stream (src);
+   switch (meta_screen_cast_stream_get_cursor_mode (stream))
+-- 
+2.26.2
+
+
+From 47b03793413aad449f021f64e66c68ca95be9a0f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 3 Jul 2020 23:50:28 +0200
+Subject: [PATCH 6/9] screen-cast/src: Fix signedness of timestamp field
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1351
+(cherry picked from commit 449fa7bf81fe0bee63f497d896cbeffe84dca82d)
+---
+ src/backends/meta-screen-cast-stream-src.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/backends/meta-screen-cast-stream-src.c b/src/backends/meta-screen-cast-stream-src.c
+index b930d5e7c0..f6f66daaa3 100644
+--- a/src/backends/meta-screen-cast-stream-src.c
++++ b/src/backends/meta-screen-cast-stream-src.c
+@@ -89,7 +89,7 @@ typedef struct _MetaScreenCastStreamSrcPrivate
+   struct spa_video_info_raw video_format;
+   int video_stride;
+ 
+-  uint64_t last_frame_timestamp_us;
++  int64_t last_frame_timestamp_us;
+ 
+   GHashTable *dmabuf_handles;
+ 
+-- 
+2.26.2
+
+
+From d8c8ea23e700f85a55a5cc0b79151fbed75ab191 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 3 Jul 2020 23:57:31 +0200
+Subject: [PATCH 7/9] screen-cast/src: Record follow up frame after timeout
+
+During animation or other things that cause multiple frames in a row
+being painted, we might skip recording frames if the max framerate is
+reached.
+
+Doing so means we might end up skipping the last frame in a series,
+ending with the last frame we sent was not the last one, making things
+appear to get stuck sometimes.
+
+Handle this by creating a timeout if we ever throttle, and at the time
+the timeout callback is triggered, make sure we eventually send an up to
+date frame.
+
+This is handle differently depending on the source type. A monitor
+source type reports 1x1 pixel damage on each view its monitor overlaps,
+while a window source type simply records a frame from the surface
+directly, except without recording a timestamp, so that timestamps
+always refer to when damage actually happened.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1351
+(cherry picked from commit e8052f169b957a502bf86ca65071582692039b9c)
+---
+ .../meta-screen-cast-monitor-stream-src.c     | 43 +++++++++++
+ src/backends/meta-screen-cast-stream-src.c    | 77 +++++++++++++++++--
+ src/backends/meta-screen-cast-stream-src.h    |  4 +
+ .../meta-screen-cast-window-stream-src.c      | 11 +++
+ 4 files changed, 130 insertions(+), 5 deletions(-)
+
+diff --git a/src/backends/meta-screen-cast-monitor-stream-src.c b/src/backends/meta-screen-cast-monitor-stream-src.c
+index 27b3ea37d8..3079578d8d 100644
+--- a/src/backends/meta-screen-cast-monitor-stream-src.c
++++ b/src/backends/meta-screen-cast-monitor-stream-src.c
+@@ -190,6 +190,9 @@ sync_cursor_state (MetaScreenCastMonitorStreamSrc *monitor_src)
+   if (clutter_stage_is_redraw_queued (stage))
+     return;
+ 
++  if (meta_screen_cast_stream_src_pending_follow_up_frame (src))
++    return;
++
+   flags = META_SCREEN_CAST_RECORD_FLAG_CURSOR_ONLY;
+   meta_screen_cast_stream_src_maybe_record_frame (src, flags);
+ }
+@@ -425,6 +428,44 @@ meta_screen_cast_monitor_stream_src_record_to_framebuffer (MetaScreenCastStreamS
+   return TRUE;
+ }
+ 
++static void
++meta_screen_cast_monitor_stream_record_follow_up (MetaScreenCastStreamSrc *src)
++{
++  MetaScreenCastMonitorStreamSrc *monitor_src =
++    META_SCREEN_CAST_MONITOR_STREAM_SRC (src);
++  MetaBackend *backend = get_backend (monitor_src);
++  MetaRenderer *renderer = meta_backend_get_renderer (backend);
++  ClutterStage *stage = get_stage (monitor_src);
++  MetaMonitor *monitor;
++  MetaLogicalMonitor *logical_monitor;
++  MetaRectangle logical_monitor_layout;
++  GList *l;
++
++  monitor = get_monitor (monitor_src);
++  logical_monitor = meta_monitor_get_logical_monitor (monitor);
++  logical_monitor_layout = meta_logical_monitor_get_layout (logical_monitor);
++
++  for (l = meta_renderer_get_views (renderer); l; l = l->next)
++    {
++      MetaRendererView *view = l->data;
++      MetaRectangle view_layout;
++      MetaRectangle damage;
++
++      clutter_stage_view_get_layout (CLUTTER_STAGE_VIEW (view), &view_layout);
++
++      if (!meta_rectangle_overlap (&logical_monitor_layout, &view_layout))
++        continue;
++
++      damage = (cairo_rectangle_int_t) {
++        .x = view_layout.x,
++        .y = view_layout.y,
++        .width = 1,
++        .height = 1,
++      };
++      clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (stage), &damage);
++    }
++}
++
+ static void
+ meta_screen_cast_monitor_stream_src_set_cursor_metadata (MetaScreenCastStreamSrc *src,
+                                                          struct spa_meta_cursor  *spa_meta_cursor)
+@@ -553,6 +594,8 @@ meta_screen_cast_monitor_stream_src_class_init (MetaScreenCastMonitorStreamSrcCl
+     meta_screen_cast_monitor_stream_src_record_to_buffer;
+   src_class->record_to_framebuffer =
+     meta_screen_cast_monitor_stream_src_record_to_framebuffer;
++  src_class->record_follow_up =
++    meta_screen_cast_monitor_stream_record_follow_up;
+   src_class->set_cursor_metadata =
+     meta_screen_cast_monitor_stream_src_set_cursor_metadata;
+ }
+diff --git a/src/backends/meta-screen-cast-stream-src.c b/src/backends/meta-screen-cast-stream-src.c
+index f6f66daaa3..55af56f8b9 100644
+--- a/src/backends/meta-screen-cast-stream-src.c
++++ b/src/backends/meta-screen-cast-stream-src.c
+@@ -90,6 +90,7 @@ typedef struct _MetaScreenCastStreamSrcPrivate
+   int video_stride;
+ 
+   int64_t last_frame_timestamp_us;
++  guint follow_up_frame_source_id;
+ 
+   GHashTable *dmabuf_handles;
+ 
+@@ -107,6 +108,12 @@ G_DEFINE_TYPE_WITH_CODE (MetaScreenCastStreamSrc,
+                                                 meta_screen_cast_stream_src_init_initable_iface)
+                          G_ADD_PRIVATE (MetaScreenCastStreamSrc))
+ 
++static inline uint32_t
++us2ms (uint64_t us)
++{
++  return (uint32_t) (us / 1000);
++}
++
+ static void
+ meta_screen_cast_stream_src_get_specs (MetaScreenCastStreamSrc *src,
+                                        int                     *width,
+@@ -154,6 +161,15 @@ meta_screen_cast_stream_src_record_to_framebuffer (MetaScreenCastStreamSrc  *src
+   return klass->record_to_framebuffer (src, framebuffer, error);
+ }
+ 
++static void
++meta_screen_cast_stream_src_record_follow_up (MetaScreenCastStreamSrc *src)
++{
++  MetaScreenCastStreamSrcClass *klass =
++    META_SCREEN_CAST_STREAM_SRC_GET_CLASS (src);
++
++  klass->record_follow_up (src);
++}
++
+ static void
+ meta_screen_cast_stream_src_set_cursor_metadata (MetaScreenCastStreamSrc *src,
+                                                  struct spa_meta_cursor  *spa_meta_cursor)
+@@ -440,6 +456,43 @@ do_record_frame (MetaScreenCastStreamSrc  *src,
+   return FALSE;
+ }
+ 
++gboolean
++meta_screen_cast_stream_src_pending_follow_up_frame (MetaScreenCastStreamSrc *src)
++{
++  MetaScreenCastStreamSrcPrivate *priv =
++    meta_screen_cast_stream_src_get_instance_private (src);
++
++  return priv->follow_up_frame_source_id != 0;
++}
++
++static gboolean
++follow_up_frame_cb (gpointer user_data)
++{
++  MetaScreenCastStreamSrc *src = user_data;
++  MetaScreenCastStreamSrcPrivate *priv =
++    meta_screen_cast_stream_src_get_instance_private (src);
++
++  priv->follow_up_frame_source_id = 0;
++  meta_screen_cast_stream_src_record_follow_up (src);
++
++  return G_SOURCE_REMOVE;
++}
++
++static void
++maybe_schedule_follow_up_frame (MetaScreenCastStreamSrc *src,
++                                int64_t                  timeout_us)
++{
++  MetaScreenCastStreamSrcPrivate *priv =
++    meta_screen_cast_stream_src_get_instance_private (src);
++
++  if (priv->follow_up_frame_source_id)
++    return;
++
++  priv->follow_up_frame_source_id = g_timeout_add (us2ms (timeout_us),
++                                                   follow_up_frame_cb,
++                                                   src);
++}
++
+ void
+ meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc  *src,
+                                                 MetaScreenCastRecordFlag  flags)
+@@ -455,11 +508,24 @@ meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc  *src,
+ 
+   now_us = g_get_monotonic_time ();
+   if (priv->video_format.max_framerate.num > 0 &&
+-      priv->last_frame_timestamp_us != 0 &&
+-      (now_us - priv->last_frame_timestamp_us <
+-       ((1000000 * priv->video_format.max_framerate.denom) /
+-        priv->video_format.max_framerate.num)))
+-    return;
++      priv->last_frame_timestamp_us != 0)
++    {
++      int64_t min_interval_us;
++      int64_t time_since_last_frame_us;
++
++      min_interval_us = ((1000000 * priv->video_format.max_framerate.denom) /
++                         priv->video_format.max_framerate.num);
++
++      time_since_last_frame_us = now_us - priv->last_frame_timestamp_us;
++      if (time_since_last_frame_us < min_interval_us)
++        {
++          int64_t timeout_us;
++
++          timeout_us = min_interval_us - time_since_last_frame_us;
++          maybe_schedule_follow_up_frame (src, timeout_us);
++          return;
++        }
++    }
+ 
+   if (!priv->pipewire_stream)
+     return;
+@@ -479,6 +545,7 @@ meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc  *src,
+ 
+   if (!(flags & META_SCREEN_CAST_RECORD_FLAG_CURSOR_ONLY))
+     {
++      g_clear_handle_id (&priv->follow_up_frame_source_id, g_source_remove);
+       if (do_record_frame (src, spa_buffer, data, &error))
+         {
+           struct spa_meta_region *spa_meta_video_crop;
+diff --git a/src/backends/meta-screen-cast-stream-src.h b/src/backends/meta-screen-cast-stream-src.h
+index 152790ecfb..81ea20b173 100644
+--- a/src/backends/meta-screen-cast-stream-src.h
++++ b/src/backends/meta-screen-cast-stream-src.h
+@@ -65,6 +65,8 @@ struct _MetaScreenCastStreamSrcClass
+   gboolean (* record_to_framebuffer) (MetaScreenCastStreamSrc  *src,
+                                       CoglFramebuffer          *framebuffer,
+                                       GError                  **error);
++  void (* record_follow_up) (MetaScreenCastStreamSrc *src);
++
+   gboolean (* get_videocrop) (MetaScreenCastStreamSrc *src,
+                               MetaRectangle           *crop_rect);
+   void (* set_cursor_metadata) (MetaScreenCastStreamSrc *src,
+@@ -74,6 +76,8 @@ struct _MetaScreenCastStreamSrcClass
+ void meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc  *src,
+                                                      MetaScreenCastRecordFlag  flags);
+ 
++gboolean meta_screen_cast_stream_src_pending_follow_up_frame (MetaScreenCastStreamSrc *src);
++
+ MetaScreenCastStream * meta_screen_cast_stream_src_get_stream (MetaScreenCastStreamSrc *src);
+ 
+ gboolean meta_screen_cast_stream_src_draw_cursor_into (MetaScreenCastStreamSrc  *src,
+diff --git a/src/backends/meta-screen-cast-window-stream-src.c b/src/backends/meta-screen-cast-window-stream-src.c
+index 70e868997e..7026ec3b4f 100644
+--- a/src/backends/meta-screen-cast-window-stream-src.c
++++ b/src/backends/meta-screen-cast-window-stream-src.c
+@@ -520,6 +520,15 @@ meta_screen_cast_window_stream_src_record_to_framebuffer (MetaScreenCastStreamSr
+   return TRUE;
+ }
+ 
++static void
++meta_screen_cast_window_stream_record_follow_up (MetaScreenCastStreamSrc *src)
++{
++  MetaScreenCastRecordFlag flags;
++
++  flags = META_SCREEN_CAST_RECORD_FLAG_NONE;
++  meta_screen_cast_stream_src_maybe_record_frame (src, flags);
++}
++
+ static void
+ meta_screen_cast_window_stream_src_set_cursor_metadata (MetaScreenCastStreamSrc *src,
+                                                         struct spa_meta_cursor  *spa_meta_cursor)
+@@ -607,6 +616,8 @@ meta_screen_cast_window_stream_src_class_init (MetaScreenCastWindowStreamSrcClas
+     meta_screen_cast_window_stream_src_record_to_buffer;
+   src_class->record_to_framebuffer =
+     meta_screen_cast_window_stream_src_record_to_framebuffer;
++  src_class->record_follow_up =
++    meta_screen_cast_window_stream_record_follow_up;
+   src_class->get_videocrop = meta_screen_cast_window_stream_src_get_videocrop;
+   src_class->set_cursor_metadata = meta_screen_cast_window_stream_src_set_cursor_metadata;
+ }
+-- 
+2.26.2
+
+
+From f2babf5129df9e948f471e3d464162888a99201d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 8 Jul 2020 15:08:23 +0200
+Subject: [PATCH 8/9] screen-cast/src: Use G_USEC_PER_SEC instead of 1000000
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1351
+(cherry picked from commit 0c6ac287e6da91ba76bf3958befef4bec6ed28f6)
+---
+ src/backends/meta-screen-cast-stream-src.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/src/backends/meta-screen-cast-stream-src.c b/src/backends/meta-screen-cast-stream-src.c
+index 55af56f8b9..1d6c2b9d08 100644
+--- a/src/backends/meta-screen-cast-stream-src.c
++++ b/src/backends/meta-screen-cast-stream-src.c
+@@ -513,8 +513,9 @@ meta_screen_cast_stream_src_maybe_record_frame (MetaScreenCastStreamSrc  *src,
+       int64_t min_interval_us;
+       int64_t time_since_last_frame_us;
+ 
+-      min_interval_us = ((1000000 * priv->video_format.max_framerate.denom) /
+-                         priv->video_format.max_framerate.num);
++      min_interval_us =
++        ((G_USEC_PER_SEC * priv->video_format.max_framerate.denom) /
++         priv->video_format.max_framerate.num);
+ 
+       time_since_last_frame_us = now_us - priv->last_frame_timestamp_us;
+       if (time_since_last_frame_us < min_interval_us)
+-- 
+2.26.2
+
+
+From 950b3ea51391ffcb434f8f5380459174ba4c4853 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Fri, 10 Jul 2020 07:06:33 +0000
+Subject: [PATCH 9/9] screen-cast/src: Remove follow up timeout source on
+ disable
+
+We failed to remove the timeout source when disabling, meaning that if a
+follow up was scheduled, and shortly after we disabled the source, the
+timeout would be invoked after the source was freed causing
+use-after-free bugs.
+
+Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/1337
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1365
+
+(cherry picked from commit d67ba3ea65717ceab3e0c91267191c6ed2aac2c2)
+(cherry picked from commit 1fd53c480f9bb58bd4ac0efc2bbce17dfda8645b)
+---
+ src/backends/meta-screen-cast-stream-src.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/backends/meta-screen-cast-stream-src.c b/src/backends/meta-screen-cast-stream-src.c
+index 1d6c2b9d08..f39d348baa 100644
+--- a/src/backends/meta-screen-cast-stream-src.c
++++ b/src/backends/meta-screen-cast-stream-src.c
+@@ -622,6 +622,8 @@ meta_screen_cast_stream_src_disable (MetaScreenCastStreamSrc *src)
+ 
+   META_SCREEN_CAST_STREAM_SRC_GET_CLASS (src)->disable (src);
+ 
++  g_clear_handle_id (&priv->follow_up_frame_source_id, g_source_remove);
++
+   priv->is_enabled = FALSE;
+ }
+ 
+-- 
+2.26.2
+
diff --git a/SOURCES/mutter-bump-screencast-api-version.patch b/SOURCES/mutter-bump-screencast-api-version.patch
new file mode 100644
index 0000000..37be67e
--- /dev/null
+++ b/SOURCES/mutter-bump-screencast-api-version.patch
@@ -0,0 +1,13 @@
+diff --git a/src/backends/meta-screen-cast.c b/src/backends/meta-screen-cast.c
+index 268155e..18fc779 100644
+--- a/src/backends/meta-screen-cast.c
++++ b/src/backends/meta-screen-cast.c
+@@ -32,7 +32,7 @@
+ 
+ #define META_SCREEN_CAST_DBUS_SERVICE "org.gnome.Mutter.ScreenCast"
+ #define META_SCREEN_CAST_DBUS_PATH "/org/gnome/Mutter/ScreenCast"
+-#define META_SCREEN_CAST_API_VERSION 2
++#define META_SCREEN_CAST_API_VERSION 3
+ 
+ struct _MetaScreenCast
+ {
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 e168884..45e87b0 100644
--- a/SPECS/mutter.spec
+++ b/SPECS/mutter.spec
@@ -8,7 +8,7 @@
 
 Name:          mutter
 Version:       3.32.2
-Release:       44%{?dist}
+Release:       55%{?dist}
 Summary:       Window and compositing manager based on Clutter
 
 License:       GPLv2+
@@ -144,6 +144,10 @@ Patch404: 0001-backend-Add-getter-for-MetaScreenCast.patch
 Patch405: 0002-renderer-native-Add-API-to-get-primary-GPU.patch
 Patch406: 0003-screen-cast-Move-DMA-buffer-allocation-to-MetaScreen.patch
 Patch407: 0004-screen-cast-Disable-DMA-buffer-based-screen-casting-.patch
+# https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1351
+# https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1365
+Patch408: cursor-move-only-screen-cast-fixes.patch
+Patch409: mutter-bump-screencast-api-version.patch
 
 # Only treat WM_PROTOCOLS messages as WM_PROTOCOL messages (#1847203)
 Patch500: 0001-stage-x11-Check-that-message-is-WM_PROTOCOLS-before-.patch
@@ -151,6 +155,28 @@ Patch500: 0001-stage-x11-Check-that-message-is-WM_PROTOCOLS-before-.patch
 # Don't show widow actor until explictly shown (#1719937)
 Patch501: 0001-window-actor-Don-t-show-actor-until-meta_window_acto.patch
 
+# Handle GPU unplug gracefully (#1846191)
+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
+
 BuildRequires: chrpath
 BuildRequires: pango-devel
 BuildRequires: startup-notification-devel
@@ -292,6 +318,48 @@ desktop-file-validate %{buildroot}/%{_datadir}/applications/%{name}.desktop
 %{_datadir}/mutter-%{mutter_api_version}/tests
 
 %changelog
+* 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: #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
+  Resolves: #1873963
+
+* Wed Jul 15 2020 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-46
+- Handle cursor only screen cast frames better
+  Related: #1837381
+
+* Thu Jul 02 2020 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-45
+- Handle GPU unplug gracefully
+  Resolves: #1846191
+
 * Thu Jun 25 2020 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-44
 - Don't show widow actor until explictly shown
   Resolves: #1719937