diff --git a/SOURCES/0001-Add-support-for-quad-buffer-stereo.patch b/SOURCES/0001-Add-support-for-quad-buffer-stereo.patch
index 7d150d2..38a1213 100644
--- a/SOURCES/0001-Add-support-for-quad-buffer-stereo.patch
+++ b/SOURCES/0001-Add-support-for-quad-buffer-stereo.patch
@@ -1,4 +1,4 @@
-From 6108e0175932f74733c46ebe13db06998f26d4ba Mon Sep 17 00:00:00 2001
+From 8d7356fd7439f94f163438d55f2b2d3d918de96d 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         |  85 +++++++++-
+ src/compositor/meta-shaped-texture.c         |  84 +++++++++-
  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, 482 insertions(+), 20 deletions(-)
+ 14 files changed, 481 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..332b4c814 100644
+index d64e214e5..e77a32109 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..332b4c814 100644
  
    stex->texture = NULL;
    stex->mask_texture = NULL;
-@@ -297,7 +300,11 @@ meta_shaped_texture_dispose (GObject *object)
+@@ -297,6 +300,9 @@ meta_shaped_texture_dispose (GObject *object)
      meta_texture_tower_free (stex->paint_tower);
    stex->paint_tower = NULL;
  
@@ -295,11 +295,9 @@ index d64e214e5..332b4c814 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);
  
-   meta_shaped_texture_set_mask_texture (stex, NULL);
-@@ -507,8 +514,9 @@ paint_clipped_rectangle (MetaShapedTexture     *stex,
+@@ -507,8 +513,9 @@ paint_clipped_rectangle (MetaShapedTexture     *stex,
  }
  
  static void
@@ -311,7 +309,7 @@ index d64e214e5..332b4c814 100644
  {
    int width, height;
  
-@@ -516,8 +524,11 @@ set_cogl_texture (MetaShapedTexture *stex,
+@@ -516,8 +523,11 @@ set_cogl_texture (MetaShapedTexture *stex,
  
    if (stex->texture)
      cogl_object_unref (stex->texture);
@@ -323,7 +321,7 @@ index d64e214e5..332b4c814 100644
  
    if (cogl_tex != NULL)
      {
-@@ -531,6 +542,9 @@ set_cogl_texture (MetaShapedTexture *stex,
+@@ -531,6 +541,9 @@ set_cogl_texture (MetaShapedTexture *stex,
        height = 0;
      }
  
@@ -333,7 +331,7 @@ index d64e214e5..332b4c814 100644
    if (stex->tex_width != width ||
        stex->tex_height != height)
      {
-@@ -544,8 +558,23 @@ set_cogl_texture (MetaShapedTexture *stex,
+@@ -544,8 +557,23 @@ set_cogl_texture (MetaShapedTexture *stex,
     * previous buffer. We only queue a redraw in response to surface
     * damage. */
  
@@ -358,7 +356,7 @@ index d64e214e5..332b4c814 100644
  }
  
  static gboolean
-@@ -779,7 +808,9 @@ meta_shaped_texture_paint (ClutterActor *actor)
+@@ -779,7 +807,9 @@ meta_shaped_texture_paint (ClutterActor *actor)
  {
    MetaShapedTexture *stex = META_SHAPED_TEXTURE (actor);
    CoglTexture *paint_tex;
@@ -368,7 +366,7 @@ index d64e214e5..332b4c814 100644
  
    if (!stex->texture)
      return;
-@@ -841,7 +872,32 @@ meta_shaped_texture_paint (ClutterActor *actor)
+@@ -841,7 +871,32 @@ meta_shaped_texture_paint (ClutterActor *actor)
      return;
  
    fb = cogl_get_draw_framebuffer ();
@@ -402,7 +400,7 @@ index d64e214e5..332b4c814 100644
  }
  
  static void
-@@ -915,6 +971,12 @@ meta_shaped_texture_set_create_mipmaps (MetaShapedTexture *stex,
+@@ -915,6 +970,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);
@@ -415,7 +413,7 @@ index d64e214e5..332b4c814 100644
      }
  }
  
-@@ -1046,6 +1108,12 @@ meta_shaped_texture_update_area (MetaShapedTexture *stex,
+@@ -1046,6 +1107,12 @@ meta_shaped_texture_update_area (MetaShapedTexture *stex,
                                    clip.y,
                                    clip.width,
                                    clip.height);
@@ -428,7 +426,7 @@ index d64e214e5..332b4c814 100644
  
    stex->prev_invalidation = stex->last_invalidation;
    stex->last_invalidation = g_get_monotonic_time ();
-@@ -1092,17 +1160,18 @@ meta_shaped_texture_update_area (MetaShapedTexture *stex,
+@@ -1092,17 +1159,18 @@ meta_shaped_texture_update_area (MetaShapedTexture *stex,
  }
  
  /**
@@ -903,5 +901,5 @@ index da0acfcbb..ddad1a45c 100644
                meta_shaped_texture_set_is_y_inverted (stex, is_y_inverted);
                g_clear_pointer (&snippet, cogl_object_unref);
 -- 
-2.28.0
+2.25.1
 
diff --git a/SOURCES/shadow-buffer-tile-damage.patch b/SOURCES/shadow-buffer-tile-damage.patch
new file mode 100644
index 0000000..ad19f8e
--- /dev/null
+++ b/SOURCES/shadow-buffer-tile-damage.patch
@@ -0,0 +1,3248 @@
+From e42c4e83283787062fb446a2aa698f227fe2db5f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 29 Apr 2020 16:26:52 +0200
+Subject: [PATCH 01/20] cogl/dma-buf-handle: Pass more metadata to handle
+ constructor
+
+Could be useful would one want to mmap the dmabuf and deal with its
+content manually in CPU space.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit e656d0caf01d8b012d2b458676e5658c540525dc)
+---
+ cogl/cogl/cogl-dma-buf-handle.c            | 45 +++++++++++++++++++++
+ cogl/cogl/cogl-dma-buf-handle.h            | 46 +++++++++++++++++++++-
+ src/backends/native/meta-renderer-native.c | 14 +++++--
+ 3 files changed, 101 insertions(+), 4 deletions(-)
+
+diff --git a/cogl/cogl/cogl-dma-buf-handle.c b/cogl/cogl/cogl-dma-buf-handle.c
+index 4a8f709f2c..d8b4e57c55 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.c
++++ b/cogl/cogl/cogl-dma-buf-handle.c
+@@ -40,6 +40,11 @@ struct _CoglDmaBufHandle
+ {
+   CoglFramebuffer *framebuffer;
+   int dmabuf_fd;
++  int width;
++  int height;
++  int stride;
++  int offset;
++  int bpp;
+   gpointer user_data;
+   GDestroyNotify destroy_func;
+ };
+@@ -47,6 +52,11 @@ struct _CoglDmaBufHandle
+ CoglDmaBufHandle *
+ cogl_dma_buf_handle_new (CoglFramebuffer *framebuffer,
+                          int              dmabuf_fd,
++                         int              width,
++                         int              height,
++                         int              stride,
++                         int              offset,
++                         int              bpp,
+                          gpointer         user_data,
+                          GDestroyNotify   destroy_func)
+ {
+@@ -61,6 +71,12 @@ cogl_dma_buf_handle_new (CoglFramebuffer *framebuffer,
+   dmabuf_handle->user_data = user_data;
+   dmabuf_handle->destroy_func = destroy_func;
+ 
++  dmabuf_handle->width = width;
++  dmabuf_handle->height = height;
++  dmabuf_handle->stride = stride;
++  dmabuf_handle->offset = offset;
++  dmabuf_handle->bpp = bpp;
++
+   return dmabuf_handle;
+ }
+ 
+@@ -92,3 +108,32 @@ cogl_dma_buf_handle_get_fd (CoglDmaBufHandle *dmabuf_handle)
+   return dmabuf_handle->dmabuf_fd;
+ }
+ 
++int
++cogl_dma_buf_handle_get_width (CoglDmaBufHandle *dmabuf_handle)
++{
++  return dmabuf_handle->width;
++}
++
++int
++cogl_dma_buf_handle_get_height (CoglDmaBufHandle *dmabuf_handle)
++{
++  return dmabuf_handle->height;
++}
++
++int
++cogl_dma_buf_handle_get_stride (CoglDmaBufHandle *dmabuf_handle)
++{
++  return dmabuf_handle->stride;
++}
++
++int
++cogl_dma_buf_handle_get_offset (CoglDmaBufHandle *dmabuf_handle)
++{
++  return dmabuf_handle->offset;
++}
++
++int
++cogl_dma_buf_handle_get_bpp (CoglDmaBufHandle *dmabuf_handle)
++{
++  return dmabuf_handle->bpp;
++}
+diff --git a/cogl/cogl/cogl-dma-buf-handle.h b/cogl/cogl/cogl-dma-buf-handle.h
+index 25b9b0ccb5..f64a20678d 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.h
++++ b/cogl/cogl/cogl-dma-buf-handle.h
+@@ -46,7 +46,12 @@
+ CoglDmaBufHandle *
+ cogl_dma_buf_handle_new (CoglFramebuffer *framebuffer,
+                          int              dmabuf_fd,
+-                         gpointer         data,
++                         int              width,
++                         int              height,
++                         int              stride,
++                         int              offset,
++                         int              bpp,
++                         gpointer         user_data,
+                          GDestroyNotify   destroy_func);
+ 
+ /**
+@@ -79,5 +84,44 @@ cogl_dma_buf_handle_get_framebuffer (CoglDmaBufHandle *dmabuf_handle);
+ int
+ cogl_dma_buf_handle_get_fd (CoglDmaBufHandle *dmabuf_handle);
+ 
++/**
++ * cogl_dmabuf_handle_get_width: (skip)
++ *
++ * Returns: the buffer width
++ */
++int
++cogl_dma_buf_handle_get_width (CoglDmaBufHandle *dmabuf_handle);
++
++/**
++ * cogl_dmabuf_handle_get_height: (skip)
++ *
++ * Returns: the buffer height
++ */
++int
++cogl_dma_buf_handle_get_height (CoglDmaBufHandle *dmabuf_handle);
++
++/**
++ * cogl_dmabuf_handle_get_stride: (skip)
++ *
++ * Returns: the buffer stride
++ */
++int
++cogl_dma_buf_handle_get_stride (CoglDmaBufHandle *dmabuf_handle);
++
++/**
++ * cogl_dmabuf_handle_get_offset: (skip)
++ *
++ * Returns: the buffer offset
++ */
++int
++cogl_dma_buf_handle_get_offset (CoglDmaBufHandle *dmabuf_handle);
++
++/**
++ * cogl_dmabuf_handle_get_bpp: (skip)
++ *
++ * Returns: the number of bytes per pixel
++ */
++int
++cogl_dma_buf_handle_get_bpp (CoglDmaBufHandle *dmabuf_handle);
+ 
+ #endif /* __COGL_DMA_BUF_HANDLE_H__ */
+diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c
+index 25833b6cf6..c14cb5acda 100644
+--- a/src/backends/native/meta-renderer-native.c
++++ b/src/backends/native/meta-renderer-native.c
+@@ -2641,6 +2641,9 @@ meta_renderer_native_create_dma_buf (CoglRenderer  *cogl_renderer,
+         CoglFramebuffer *dmabuf_fb;
+         CoglDmaBufHandle *dmabuf_handle;
+         struct gbm_bo *new_bo;
++        int stride;
++        int offset;
++        int bpp;
+         int dmabuf_fd = -1;
+ 
+         new_bo = gbm_bo_create (renderer_gpu_data->gbm.device,
+@@ -2664,11 +2667,14 @@ meta_renderer_native_create_dma_buf (CoglRenderer  *cogl_renderer,
+             return NULL;
+           }
+ 
++        stride = gbm_bo_get_stride (new_bo);
++        offset = gbm_bo_get_offset (new_bo, 0);
++        bpp = 4;
+         dmabuf_fb = create_dma_buf_framebuffer (renderer_native,
+                                                 dmabuf_fd,
+                                                 width, height,
+-                                                gbm_bo_get_stride (new_bo),
+-                                                gbm_bo_get_offset (new_bo, 0),
++                                                stride,
++                                                offset,
+                                                 DRM_FORMAT_MOD_LINEAR,
+                                                 DRM_FORMAT_XRGB8888,
+                                                 error);
+@@ -2677,7 +2683,9 @@ meta_renderer_native_create_dma_buf (CoglRenderer  *cogl_renderer,
+           return NULL;
+ 
+         dmabuf_handle =
+-          cogl_dma_buf_handle_new (dmabuf_fb, dmabuf_fd, new_bo,
++          cogl_dma_buf_handle_new (dmabuf_fb, dmabuf_fd,
++                                   width, height, stride, offset, bpp,
++                                   new_bo,
+                                    (GDestroyNotify) gbm_bo_destroy);
+         cogl_object_unref (dmabuf_fb);
+         return dmabuf_handle;
+-- 
+2.28.0
+
+
+From 2270f6dcf7b1e70386f5b4242f92bf5735bb88ba Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 30 Apr 2020 10:42:30 +0200
+Subject: [PATCH 02/20] clutter/stage-view: Add name property
+
+Will be used for logging to identify what view a log entry concerns. For
+the native and nested backend this is the name of the output the CRTC is
+assigned to drive; for X11 it's just "X11 screen", and for the legacy
+"X11 screen" emulation mode of the nested backend it's called "legacy
+nested".
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit c86367199febdee10ecd7ba24c69b6dda52cb896)
+---
+ clutter/clutter/clutter-stage-view.c           | 18 ++++++++++++++++++
+ src/backends/native/meta-renderer-native.c     |  3 +++
+ .../x11/nested/meta-renderer-x11-nested.c      |  4 ++++
+ 3 files changed, 25 insertions(+)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 0fad6fc446..6b543b5d51 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -27,6 +27,7 @@ enum
+ {
+   PROP_0,
+ 
++  PROP_NAME,
+   PROP_LAYOUT,
+   PROP_FRAMEBUFFER,
+   PROP_OFFSCREEN,
+@@ -40,6 +41,8 @@ static GParamSpec *obj_props[PROP_LAST];
+ 
+ typedef struct _ClutterStageViewPrivate
+ {
++  char *name;
++
+   cairo_rectangle_int_t layout;
+   float scale;
+   CoglFramebuffer *framebuffer;
+@@ -339,6 +342,9 @@ clutter_stage_view_get_property (GObject    *object,
+ 
+   switch (prop_id)
+     {
++    case PROP_NAME:
++      g_value_set_string (value, priv->name);
++      break;
+     case PROP_LAYOUT:
+       g_value_set_boxed (value, &priv->layout);
+       break;
+@@ -372,6 +378,9 @@ clutter_stage_view_set_property (GObject      *object,
+ 
+   switch (prop_id)
+     {
++    case PROP_NAME:
++      priv->name = g_value_dup_string (value);
++      break;
+     case PROP_LAYOUT:
+       layout = g_value_get_boxed (value);
+       priv->layout = *layout;
+@@ -414,6 +423,7 @@ clutter_stage_view_dispose (GObject *object)
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+ 
++  g_clear_pointer (&priv->name, g_free);
+   g_clear_pointer (&priv->framebuffer, cogl_object_unref);
+   g_clear_pointer (&priv->shadowfb, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen, cogl_object_unref);
+@@ -446,6 +456,14 @@ clutter_stage_view_class_init (ClutterStageViewClass *klass)
+   object_class->set_property = clutter_stage_view_set_property;
+   object_class->dispose = clutter_stage_view_dispose;
+ 
++  obj_props[PROP_NAME] =
++    g_param_spec_string ("name",
++                         "Name",
++                         "Name of view",
++                         NULL,
++                         G_PARAM_READWRITE |
++                         G_PARAM_CONSTRUCT_ONLY |
++                         G_PARAM_STATIC_STRINGS);
+   obj_props[PROP_LAYOUT] =
+     g_param_spec_boxed ("layout",
+                         "View layout",
+diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c
+index c14cb5acda..d3fb5b3c55 100644
+--- a/src/backends/native/meta-renderer-native.c
++++ b/src/backends/native/meta-renderer-native.c
+@@ -3679,6 +3679,7 @@ meta_renderer_native_create_view (MetaRenderer       *renderer,
+   CoglContext *cogl_context =
+     cogl_context_from_renderer_native (renderer_native);
+   CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
++  MetaMonitor *monitor;
+   CoglDisplayEGL *cogl_display_egl;
+   CoglOnscreenEGL *onscreen_egl;
+   MetaMonitorTransform view_transform;
+@@ -3742,7 +3743,9 @@ meta_renderer_native_create_view (MetaRenderer       *renderer,
+         g_error ("Failed to allocate shadow buffer texture: %s", error->message);
+     }
+ 
++  monitor = meta_logical_monitor_get_monitors (logical_monitor)->data;
+   view = g_object_new (META_TYPE_RENDERER_VIEW,
++                       "name", meta_monitor_get_connector (monitor),
+                        "layout", &logical_monitor->rect,
+                        "scale", scale,
+                        "framebuffer", onscreen,
+diff --git a/src/backends/x11/nested/meta-renderer-x11-nested.c b/src/backends/x11/nested/meta-renderer-x11-nested.c
+index 5000bf3579..f3a5547dbb 100644
+--- a/src/backends/x11/nested/meta-renderer-x11-nested.c
++++ b/src/backends/x11/nested/meta-renderer-x11-nested.c
+@@ -163,6 +163,7 @@ meta_renderer_x11_nested_ensure_legacy_view (MetaRendererX11Nested *renderer_x11
+     .height = height
+   };
+   legacy_view = g_object_new (META_TYPE_RENDERER_VIEW,
++                              "name", "legacy nested",
+                               "layout", &view_layout,
+                               "framebuffer", COGL_FRAMEBUFFER (fake_onscreen),
+                               NULL);
+@@ -179,6 +180,7 @@ meta_renderer_x11_nested_create_view (MetaRenderer       *renderer,
+     meta_backend_get_monitor_manager (backend);
+   ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
+   CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend);
++  MetaMonitor *monitor;
+   MetaMonitorTransform view_transform;
+   float view_scale;
+   int width, height;
+@@ -212,7 +214,9 @@ meta_renderer_x11_nested_create_view (MetaRenderer       *renderer,
+   else
+     offscreen = NULL;
+ 
++  monitor = meta_logical_monitor_get_monitors (logical_monitor)->data;
+   return g_object_new (META_TYPE_RENDERER_VIEW,
++                       "name", meta_monitor_get_connector (monitor),
+                        "layout", &logical_monitor->rect,
+                        "framebuffer", COGL_FRAMEBUFFER (fake_onscreen),
+                        "offscreen", COGL_FRAMEBUFFER (offscreen),
+-- 
+2.28.0
+
+
+From 6716fde14c5b1a00a02a80b46db67d3f236a87c1 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 30 Apr 2020 17:53:30 +0200
+Subject: [PATCH 03/20] renderer-native: Move shadow fb construction to the
+ stage view
+
+The stage view will need a more involved approach to shadow buffers, in
+order to implement things such double buffered shadow buffers and damage
+detection.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 3ab89be574f0e02dc67e1b1f538bb24f94612bcf)
+---
+ clutter/clutter/clutter-stage-view.c       | 115 ++++++++++++++++++---
+ src/backends/native/meta-renderer-native.c |  25 +----
+ 2 files changed, 106 insertions(+), 34 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 6b543b5d51..db0067297c 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -31,7 +31,7 @@ enum
+   PROP_LAYOUT,
+   PROP_FRAMEBUFFER,
+   PROP_OFFSCREEN,
+-  PROP_SHADOWFB,
++  PROP_USE_SHADOWFB,
+   PROP_SCALE,
+ 
+   PROP_LAST
+@@ -50,6 +50,7 @@ typedef struct _ClutterStageViewPrivate
+   CoglOffscreen *offscreen;
+   CoglPipeline *offscreen_pipeline;
+ 
++  gboolean use_shadowfb;
+   CoglOffscreen *shadowfb;
+   CoglPipeline *shadowfb_pipeline;
+ 
+@@ -206,6 +207,80 @@ clutter_stage_view_copy_to_framebuffer (ClutterStageView            *view,
+   cogl_framebuffer_pop_matrix (dst_framebuffer);
+ }
+ 
++static CoglOffscreen *
++create_offscreen_framebuffer (CoglContext  *context,
++                              int           width,
++                              int           height,
++                              GError      **error)
++{
++  CoglOffscreen *framebuffer;
++  CoglTexture2D *texture;
++
++  texture = cogl_texture_2d_new_with_size (context, width, height);
++  cogl_primitive_texture_set_auto_mipmap (COGL_PRIMITIVE_TEXTURE (texture),
++                                          FALSE);
++
++  if (!cogl_texture_allocate (COGL_TEXTURE (texture), error))
++    {
++      cogl_object_unref (texture);
++      return FALSE;
++    }
++
++  framebuffer = cogl_offscreen_new_with_texture (COGL_TEXTURE (texture));
++  cogl_object_unref (texture);
++  if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (framebuffer), error))
++    {
++      cogl_object_unref (framebuffer);
++      return FALSE;
++    }
++
++  return framebuffer;
++}
++
++static gboolean
++init_offscreen_shadowfb (ClutterStageView  *view,
++                         CoglContext       *cogl_context,
++                         int                width,
++                         int                height,
++                         GError           **error)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  CoglOffscreen *offscreen;
++
++  offscreen = create_offscreen_framebuffer (cogl_context, width, height, error);
++  if (!offscreen)
++    return FALSE;
++
++  priv->shadowfb = offscreen;
++  return TRUE;
++}
++
++static void
++init_shadowfb (ClutterStageView *view)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  g_autoptr (GError) error = NULL;
++  int width;
++  int height;
++  CoglContext *cogl_context;
++
++  width = cogl_framebuffer_get_width (priv->framebuffer);
++  height = cogl_framebuffer_get_height (priv->framebuffer);
++  cogl_context = cogl_framebuffer_get_context (priv->framebuffer);
++
++  if (!init_offscreen_shadowfb (view, cogl_context, width, height, &error))
++    {
++      g_warning ("Failed to initialize single buffered shadow fb for %s: %s",
++                 priv->name, error->message);
++    }
++  else
++    {
++      g_message ("Initialized single buffered shadow fb for %s", priv->name);
++    }
++}
++
+ void
+ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+                                    const cairo_rectangle_int_t *rect)
+@@ -354,8 +429,8 @@ clutter_stage_view_get_property (GObject    *object,
+     case PROP_OFFSCREEN:
+       g_value_set_boxed (value, priv->offscreen);
+       break;
+-    case PROP_SHADOWFB:
+-      g_value_set_boxed (value, priv->shadowfb);
++    case PROP_USE_SHADOWFB:
++      g_value_set_boolean (value, priv->use_shadowfb);
+       break;
+     case PROP_SCALE:
+       g_value_set_float (value, priv->scale);
+@@ -405,8 +480,8 @@ clutter_stage_view_set_property (GObject      *object,
+     case PROP_OFFSCREEN:
+       priv->offscreen = g_value_dup_boxed (value);
+       break;
+-    case PROP_SHADOWFB:
+-      priv->shadowfb = g_value_dup_boxed (value);
++    case PROP_USE_SHADOWFB:
++      priv->use_shadowfb = g_value_get_boolean (value);
+       break;
+     case PROP_SCALE:
+       priv->scale = g_value_get_float (value);
+@@ -416,6 +491,19 @@ clutter_stage_view_set_property (GObject      *object,
+     }
+ }
+ 
++static void
++clutter_stage_view_constructed (GObject *object)
++{
++  ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++
++  if (priv->use_shadowfb)
++    init_shadowfb (view);
++
++  G_OBJECT_CLASS (clutter_stage_view_parent_class)->constructed (object);
++}
++
+ static void
+ clutter_stage_view_dispose (GObject *object)
+ {
+@@ -454,6 +542,7 @@ clutter_stage_view_class_init (ClutterStageViewClass *klass)
+ 
+   object_class->get_property = clutter_stage_view_get_property;
+   object_class->set_property = clutter_stage_view_set_property;
++  object_class->constructed = clutter_stage_view_constructed;
+   object_class->dispose = clutter_stage_view_dispose;
+ 
+   obj_props[PROP_NAME] =
+@@ -491,14 +580,14 @@ clutter_stage_view_class_init (ClutterStageViewClass *klass)
+                         G_PARAM_CONSTRUCT_ONLY |
+                         G_PARAM_STATIC_STRINGS);
+ 
+-  obj_props[PROP_SHADOWFB] =
+-    g_param_spec_boxed ("shadowfb",
+-                        "Shadow framebuffer",
+-                        "Framebuffer used as intermediate shadow buffer",
+-                        COGL_TYPE_HANDLE,
+-                        G_PARAM_READWRITE |
+-                        G_PARAM_CONSTRUCT_ONLY |
+-                        G_PARAM_STATIC_STRINGS);
++  obj_props[PROP_USE_SHADOWFB] =
++    g_param_spec_boolean ("use-shadowfb",
++                          "Use shadowfb",
++                          "Whether to use one or more shadow framebuffers",
++                          FALSE,
++                          G_PARAM_READWRITE |
++                          G_PARAM_CONSTRUCT_ONLY |
++                          G_PARAM_STATIC_STRINGS);
+ 
+   obj_props[PROP_SCALE] =
+     g_param_spec_float ("scale",
+diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c
+index d3fb5b3c55..463dddd3a7 100644
+--- a/src/backends/native/meta-renderer-native.c
++++ b/src/backends/native/meta-renderer-native.c
+@@ -3685,7 +3685,7 @@ meta_renderer_native_create_view (MetaRenderer       *renderer,
+   MetaMonitorTransform view_transform;
+   CoglOnscreen *onscreen = NULL;
+   CoglOffscreen *offscreen = NULL;
+-  CoglOffscreen *shadowfb = NULL;
++  gboolean use_shadowfb;
+   float scale;
+   int width, height;
+   MetaRendererView *view;
+@@ -3724,24 +3724,8 @@ meta_renderer_native_create_view (MetaRenderer       *renderer,
+ 
+     }
+ 
+-  if (should_force_shadow_fb (renderer_native,
+-                              renderer_native->primary_gpu_kms))
+-    {
+-      int shadow_width;
+-      int shadow_height;
+-
+-      /* The shadowfb must be the same size as the on-screen framebuffer */
+-      shadow_width = cogl_framebuffer_get_width (COGL_FRAMEBUFFER (onscreen));
+-      shadow_height = cogl_framebuffer_get_height (COGL_FRAMEBUFFER (onscreen));
+-
+-      shadowfb = meta_renderer_native_create_offscreen (renderer_native,
+-                                                        cogl_context,
+-                                                        shadow_width,
+-                                                        shadow_height,
+-                                                        &error);
+-      if (!shadowfb)
+-        g_error ("Failed to allocate shadow buffer texture: %s", error->message);
+-    }
++  use_shadowfb = should_force_shadow_fb (renderer_native,
++                                         renderer_native->primary_gpu_kms);
+ 
+   monitor = meta_logical_monitor_get_monitors (logical_monitor)->data;
+   view = g_object_new (META_TYPE_RENDERER_VIEW,
+@@ -3750,12 +3734,11 @@ meta_renderer_native_create_view (MetaRenderer       *renderer,
+                        "scale", scale,
+                        "framebuffer", onscreen,
+                        "offscreen", offscreen,
+-                       "shadowfb", shadowfb,
++                       "use-shadowfb", use_shadowfb,
+                        "logical-monitor", logical_monitor,
+                        "transform", view_transform,
+                        NULL);
+   g_clear_pointer (&offscreen, cogl_object_unref);
+-  g_clear_pointer (&shadowfb, cogl_object_unref);
+ 
+   meta_onscreen_native_set_view (onscreen, view);
+ 
+-- 
+2.28.0
+
+
+From f79b37583e575d34edb4b7965cb0e48eb2736749 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 30 Apr 2020 18:19:30 +0200
+Subject: [PATCH 04/20] clutter/stage-view: Move shadowfb struct fields into
+ anonymous struct
+
+With the aim to collect shadow buffer related things in one place, place
+them in an anonymous struct.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 310ca695d90b48074a06327e87bd7e924f49cb7f)
+---
+ clutter/clutter/clutter-stage-view.c | 32 +++++++++++++++-------------
+ 1 file changed, 17 insertions(+), 15 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index db0067297c..9bbe158f36 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -51,8 +51,10 @@ typedef struct _ClutterStageViewPrivate
+   CoglPipeline *offscreen_pipeline;
+ 
+   gboolean use_shadowfb;
+-  CoglOffscreen *shadowfb;
+-  CoglPipeline *shadowfb_pipeline;
++  struct {
++    CoglOffscreen *framebuffer;
++    CoglPipeline *pipeline;
++  } shadow;
+ 
+   guint dirty_viewport   : 1;
+   guint dirty_projection : 1;
+@@ -86,8 +88,8 @@ clutter_stage_view_get_framebuffer (ClutterStageView *view)
+ 
+   if (priv->offscreen)
+     return priv->offscreen;
+-  else if (priv->shadowfb)
+-    return priv->shadowfb;
++  else if (priv->shadow.framebuffer)
++    return priv->shadow.framebuffer;
+   else
+     return priv->framebuffer;
+ }
+@@ -153,11 +155,11 @@ clutter_stage_view_ensure_shadowfb_blit_pipeline (ClutterStageView *view)
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+ 
+-  if (priv->shadowfb_pipeline)
++  if (priv->shadow.pipeline)
+     return;
+ 
+-  priv->shadowfb_pipeline =
+-    clutter_stage_view_create_framebuffer_pipeline (priv->shadowfb);
++  priv->shadow.pipeline =
++    clutter_stage_view_create_framebuffer_pipeline (priv->shadow.framebuffer);
+ }
+ 
+ void
+@@ -252,7 +254,7 @@ init_offscreen_shadowfb (ClutterStageView  *view,
+   if (!offscreen)
+     return FALSE;
+ 
+-  priv->shadowfb = offscreen;
++  priv->shadow.framebuffer = offscreen;
+   return TRUE;
+ }
+ 
+@@ -297,13 +299,13 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+       clutter_stage_view_get_offscreen_transformation_matrix (view, &matrix);
+       can_blit = cogl_matrix_is_identity (&matrix);
+ 
+-      if (priv->shadowfb)
++      if (priv->shadow.framebuffer)
+         {
+           clutter_stage_view_copy_to_framebuffer (view,
+                                                   rect,
+                                                   priv->offscreen_pipeline,
+                                                   priv->offscreen,
+-                                                  priv->shadowfb,
++                                                  priv->shadow.framebuffer,
+                                                   can_blit);
+         }
+       else
+@@ -317,13 +319,13 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+         }
+     }
+ 
+-  if (priv->shadowfb)
++  if (priv->shadow.framebuffer)
+     {
+       clutter_stage_view_ensure_shadowfb_blit_pipeline (view);
+       clutter_stage_view_copy_to_framebuffer (view,
+                                               rect,
+-                                              priv->shadowfb_pipeline,
+-                                              priv->shadowfb,
++                                              priv->shadow.pipeline,
++                                              priv->shadow.framebuffer,
+                                               priv->framebuffer,
+                                               TRUE);
+     }
+@@ -513,10 +515,10 @@ clutter_stage_view_dispose (GObject *object)
+ 
+   g_clear_pointer (&priv->name, g_free);
+   g_clear_pointer (&priv->framebuffer, cogl_object_unref);
+-  g_clear_pointer (&priv->shadowfb, cogl_object_unref);
++  g_clear_pointer (&priv->shadow.framebuffer, cogl_object_unref);
++  g_clear_pointer (&priv->shadow.pipeline, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
+-  g_clear_pointer (&priv->shadowfb_pipeline, cogl_object_unref);
+ 
+   G_OBJECT_CLASS (clutter_stage_view_parent_class)->dispose (object);
+ }
+-- 
+2.28.0
+
+
+From 7bf71e7b5f39fcf34c4a636640636f9452b4b06c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 30 Apr 2020 21:51:10 +0200
+Subject: [PATCH 05/20] clutter/stage-view: Move fb viewport and projection
+ setting to here
+
+The stage would fetch the front framebuffer and set the viewport and
+projection matrix, but if we are going to more than one front buffer,
+that won't work, so let the stage just pass the viewport and projection
+matrix to the view and have the view deal with the framebuffer(s).
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit c79bcf0d7e35cf9e85864cf72ea53659a6b8d8a7)
+---
+ clutter/clutter/clutter-stage-view-private.h |  8 ++++++
+ clutter/clutter/clutter-stage-view.c         | 29 ++++++++++++++++++++
+ clutter/clutter/clutter-stage.c              | 16 ++++-------
+ 3 files changed, 42 insertions(+), 11 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view-private.h b/clutter/clutter/clutter-stage-view-private.h
+index 89c42599fc..78aa37c9f4 100644
+--- a/clutter/clutter/clutter-stage-view-private.h
++++ b/clutter/clutter/clutter-stage-view-private.h
+@@ -28,10 +28,18 @@ gboolean clutter_stage_view_is_dirty_viewport (ClutterStageView *view);
+ void clutter_stage_view_set_dirty_viewport (ClutterStageView *view,
+                                             gboolean          dirty);
+ 
++void clutter_stage_view_set_viewport (ClutterStageView *view,
++                                      float             x,
++                                      float             y,
++                                      float             width,
++                                      float             height);
++
+ gboolean clutter_stage_view_is_dirty_projection (ClutterStageView *view);
+ 
+ void clutter_stage_view_set_dirty_projection (ClutterStageView *view,
+                                               gboolean          dirty);
+ 
++void clutter_stage_view_set_projection (ClutterStageView *view,
++                                        const CoglMatrix *matrix);
+ 
+ #endif /* __CLUTTER_STAGE_VIEW_PRIVATE_H__ */
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 9bbe158f36..4d8bbddc9d 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -359,6 +359,22 @@ clutter_stage_view_set_dirty_viewport (ClutterStageView *view,
+   priv->dirty_viewport = dirty;
+ }
+ 
++void
++clutter_stage_view_set_viewport (ClutterStageView *view,
++                                 float             x,
++                                 float             y,
++                                 float             width,
++                                 float             height)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  CoglFramebuffer *framebuffer;
++
++  priv->dirty_viewport = FALSE;
++  framebuffer = clutter_stage_view_get_framebuffer (view);
++  cogl_framebuffer_set_viewport (framebuffer, x, y, width, height);
++}
++
+ gboolean
+ clutter_stage_view_is_dirty_projection (ClutterStageView *view)
+ {
+@@ -378,6 +394,19 @@ clutter_stage_view_set_dirty_projection (ClutterStageView *view,
+   priv->dirty_projection = dirty;
+ }
+ 
++void
++clutter_stage_view_set_projection (ClutterStageView *view,
++                                   const CoglMatrix *matrix)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  CoglFramebuffer *framebuffer;
++
++  priv->dirty_projection = FALSE;
++  framebuffer = clutter_stage_view_get_framebuffer (view);
++  cogl_framebuffer_set_projection_matrix (framebuffer, matrix);
++}
++
+ void
+ clutter_stage_view_get_offscreen_transformation_matrix (ClutterStageView *view,
+                                                         CoglMatrix       *matrix)
+diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
+index 34c4e0119a..4bde234dbf 100644
+--- a/clutter/clutter/clutter-stage.c
++++ b/clutter/clutter/clutter-stage.c
+@@ -3687,7 +3687,6 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+                                      ClutterStageView *view)
+ {
+   ClutterStagePrivate *priv = stage->priv;
+-  CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view);
+ 
+   if (clutter_stage_view_is_dirty_viewport (view))
+     {
+@@ -3716,9 +3715,10 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+       viewport_y = roundf (priv->viewport[1] * fb_scale - viewport_offset_y);
+       viewport_width = roundf (priv->viewport[2] * fb_scale);
+       viewport_height = roundf (priv->viewport[3] * fb_scale);
+-      cogl_framebuffer_set_viewport (fb,
+-                                     viewport_x, viewport_y,
+-                                     viewport_width, viewport_height);
++
++      clutter_stage_view_set_viewport (view,
++                                       viewport_x, viewport_y,
++                                       viewport_width, viewport_height);
+ 
+       perspective = priv->perspective;
+ 
+@@ -3751,16 +3751,10 @@ _clutter_stage_maybe_setup_viewport (ClutterStage     *stage,
+                                           z_2d,
+                                           priv->viewport[2],
+                                           priv->viewport[3]);
+-
+-      clutter_stage_view_set_dirty_viewport (view, FALSE);
+     }
+ 
+   if (clutter_stage_view_is_dirty_projection (view))
+-    {
+-      cogl_framebuffer_set_projection_matrix (fb, &priv->projection);
+-
+-      clutter_stage_view_set_dirty_projection (view, FALSE);
+-    }
++    clutter_stage_view_set_projection (view, &priv->projection);
+ }
+ 
+ #undef _DEG_TO_RAD
+-- 
+2.28.0
+
+
+From 0b345dc3a108f12ebc00e831692b43291c84cd07 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Thu, 30 Apr 2020 21:59:49 +0200
+Subject: [PATCH 06/20] clutter/stage-view: Change set_dirty..() API to
+ invalidate..()
+
+The manual "cleaning" of the viewport and projection state is removed,
+and we only ever try to invalidate the state so that it'll be updated
+next time. Change the API used to reflect this.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 3080ee672a366a3a52d9f43523c40e3afd08e874)
+---
+ clutter/clutter/clutter-stage-view-private.h |  6 ++----
+ clutter/clutter/clutter-stage-view.c         | 10 ++++------
+ clutter/clutter/clutter-stage.c              |  4 ++--
+ 3 files changed, 8 insertions(+), 12 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view-private.h b/clutter/clutter/clutter-stage-view-private.h
+index 78aa37c9f4..e27f140b8a 100644
+--- a/clutter/clutter/clutter-stage-view-private.h
++++ b/clutter/clutter/clutter-stage-view-private.h
+@@ -25,8 +25,7 @@ void clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+ 
+ gboolean clutter_stage_view_is_dirty_viewport (ClutterStageView *view);
+ 
+-void clutter_stage_view_set_dirty_viewport (ClutterStageView *view,
+-                                            gboolean          dirty);
++void clutter_stage_view_invalidate_viewport (ClutterStageView *view);
+ 
+ void clutter_stage_view_set_viewport (ClutterStageView *view,
+                                       float             x,
+@@ -36,8 +35,7 @@ void clutter_stage_view_set_viewport (ClutterStageView *view,
+ 
+ gboolean clutter_stage_view_is_dirty_projection (ClutterStageView *view);
+ 
+-void clutter_stage_view_set_dirty_projection (ClutterStageView *view,
+-                                              gboolean          dirty);
++void clutter_stage_view_invalidate_projection (ClutterStageView *view);
+ 
+ void clutter_stage_view_set_projection (ClutterStageView *view,
+                                         const CoglMatrix *matrix);
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 4d8bbddc9d..40edfad6e1 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -350,13 +350,12 @@ clutter_stage_view_is_dirty_viewport (ClutterStageView *view)
+ }
+ 
+ void
+-clutter_stage_view_set_dirty_viewport (ClutterStageView *view,
+-                                       gboolean          dirty)
++clutter_stage_view_invalidate_viewport (ClutterStageView *view)
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+ 
+-  priv->dirty_viewport = dirty;
++  priv->dirty_viewport = TRUE;
+ }
+ 
+ void
+@@ -385,13 +384,12 @@ clutter_stage_view_is_dirty_projection (ClutterStageView *view)
+ }
+ 
+ void
+-clutter_stage_view_set_dirty_projection (ClutterStageView *view,
+-                                         gboolean          dirty)
++clutter_stage_view_invalidate_projection (ClutterStageView *view)
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+ 
+-  priv->dirty_projection = dirty;
++  priv->dirty_projection = TRUE;
+ }
+ 
+ void
+diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c
+index 4bde234dbf..aaa77d9ede 100644
+--- a/clutter/clutter/clutter-stage.c
++++ b/clutter/clutter/clutter-stage.c
+@@ -2636,7 +2636,7 @@ _clutter_stage_dirty_projection (ClutterStage *stage)
+     {
+       ClutterStageView *view = l->data;
+ 
+-      clutter_stage_view_set_dirty_projection (view, TRUE);
++      clutter_stage_view_invalidate_projection (view);
+     }
+ }
+ 
+@@ -2725,7 +2725,7 @@ _clutter_stage_dirty_viewport (ClutterStage *stage)
+     {
+       ClutterStageView *view = l->data;
+ 
+-      clutter_stage_view_set_dirty_viewport (view, TRUE);
++      clutter_stage_view_invalidate_viewport (view);
+     }
+ }
+ 
+-- 
+2.28.0
+
+
+From 32da7b5c31277c56089e4b3b8ccf43bc552e8974 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 17:05:36 +0200
+Subject: [PATCH 07/20] cogl: Make private BLIT_FRAMEBUFFER feature public
+
+Will be a requirement for enabling shadow buffers.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit b3153760bf81af07f5328ba07b0ff3009bd8305b)
+---
+ cogl/cogl/cogl-blit.c                       | 2 +-
+ cogl/cogl/cogl-context.h                    | 3 +++
+ cogl/cogl/cogl-framebuffer.c                | 2 +-
+ cogl/cogl/cogl-framebuffer.h                | 2 +-
+ cogl/cogl/cogl-private.h                    | 1 -
+ cogl/cogl/driver/gl/cogl-framebuffer-gl.c   | 4 ++--
+ cogl/cogl/driver/gl/gl/cogl-driver-gl.c     | 4 ++--
+ cogl/cogl/driver/gl/gles/cogl-driver-gles.c | 4 ++--
+ 8 files changed, 12 insertions(+), 10 deletions(-)
+
+diff --git a/cogl/cogl/cogl-blit.c b/cogl/cogl/cogl-blit.c
+index ae5a8a345d..dd5fffff37 100644
+--- a/cogl/cogl/cogl-blit.c
++++ b/cogl/cogl/cogl-blit.c
+@@ -158,7 +158,7 @@ _cogl_blit_framebuffer_begin (CoglBlitData *data)
+      supported. */
+   if ((_cogl_texture_get_format (data->src_tex) & COGL_PREMULT_BIT) !=
+       (_cogl_texture_get_format (data->dst_tex) & COGL_PREMULT_BIT) ||
+-      !_cogl_has_private_feature (ctx, COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER))
++      !cogl_has_feature (ctx, COGL_FEATURE_ID_BLIT_FRAMEBUFFER))
+     return FALSE;
+ 
+   dst_offscreen = _cogl_offscreen_new_with_texture_full
+diff --git a/cogl/cogl/cogl-context.h b/cogl/cogl/cogl-context.h
+index d4104625e6..ec90491e94 100644
+--- a/cogl/cogl/cogl-context.h
++++ b/cogl/cogl/cogl-context.h
+@@ -227,6 +227,8 @@ cogl_is_context (void *object);
+  *     the depth buffer to a texture.
+  * @COGL_FEATURE_ID_PRESENTATION_TIME: Whether frame presentation
+  *    time stamps will be recorded in #CoglFrameInfo objects.
++ * @COGL_FEATURE_ID_BLIT_FRAMEBUFFER: Whether blitting using
++ *    cogl_blit_framebuffer() is supported.
+  *
+  * All the capabilities that can vary between different GPUs supported
+  * by Cogl. Applications that depend on any of these features should explicitly
+@@ -261,6 +263,7 @@ typedef enum _CoglFeatureID
+   COGL_FEATURE_ID_TEXTURE_RG,
+   COGL_FEATURE_ID_BUFFER_AGE,
+   COGL_FEATURE_ID_TEXTURE_EGL_IMAGE_EXTERNAL,
++  COGL_FEATURE_ID_BLIT_FRAMEBUFFER,
+ 
+   /*< private >*/
+   _COGL_N_FEATURE_IDS   /*< skip >*/
+diff --git a/cogl/cogl/cogl-framebuffer.c b/cogl/cogl/cogl-framebuffer.c
+index d64fc89fb6..fffac3f685 100644
+--- a/cogl/cogl/cogl-framebuffer.c
++++ b/cogl/cogl/cogl-framebuffer.c
+@@ -1464,7 +1464,7 @@ cogl_blit_framebuffer (CoglFramebuffer *src,
+   int src_x1, src_y1, src_x2, src_y2;
+   int dst_x1, dst_y1, dst_x2, dst_y2;
+ 
+-  if (!_cogl_has_private_feature (ctx, COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER))
++  if (!cogl_has_feature (ctx, COGL_FEATURE_ID_BLIT_FRAMEBUFFER))
+     {
+       g_set_error_literal (error, COGL_SYSTEM_ERROR,
+                            COGL_SYSTEM_ERROR_UNSUPPORTED,
+diff --git a/cogl/cogl/cogl-framebuffer.h b/cogl/cogl/cogl-framebuffer.h
+index 38ada9feb7..c347076919 100644
+--- a/cogl/cogl/cogl-framebuffer.h
++++ b/cogl/cogl/cogl-framebuffer.h
+@@ -1863,7 +1863,7 @@ cogl_is_framebuffer (void *object);
+  *
+  * This blits a region of the color buffer of the source buffer
+  * to the destination buffer. This function should only be
+- * called if the COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER feature is
++ * called if the COGL_FEATURE_ID_BLIT_FRAMEBUFFER feature is
+  * advertised.
+  *
+  * The source and destination rectangles are defined in offscreen
+diff --git a/cogl/cogl/cogl-private.h b/cogl/cogl/cogl-private.h
+index d9fbe68c76..07ac7eb2d8 100644
+--- a/cogl/cogl/cogl-private.h
++++ b/cogl/cogl/cogl-private.h
+@@ -42,7 +42,6 @@ typedef enum
+ {
+   COGL_PRIVATE_FEATURE_TEXTURE_2D_FROM_EGL_IMAGE,
+   COGL_PRIVATE_FEATURE_MESA_PACK_INVERT,
+-  COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER,
+   COGL_PRIVATE_FEATURE_FOUR_CLIP_PLANES,
+   COGL_PRIVATE_FEATURE_PBOS,
+   COGL_PRIVATE_FEATURE_VBOS,
+diff --git a/cogl/cogl/driver/gl/cogl-framebuffer-gl.c b/cogl/cogl/driver/gl/cogl-framebuffer-gl.c
+index 6466fd6bcf..2c0613462f 100644
+--- a/cogl/cogl/driver/gl/cogl-framebuffer-gl.c
++++ b/cogl/cogl/driver/gl/cogl-framebuffer-gl.c
+@@ -401,8 +401,8 @@ _cogl_framebuffer_gl_flush_state (CoglFramebuffer *draw_buffer,
+         {
+           /* NB: Currently we only take advantage of binding separate
+            * read/write buffers for framebuffer blit purposes. */
+-          _COGL_RETURN_IF_FAIL (_cogl_has_private_feature
+-                                (ctx, COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER));
++          _COGL_RETURN_IF_FAIL (cogl_has_feature
++                                (ctx, COGL_FEATURE_ID_BLIT_FRAMEBUFFER));
+ 
+           _cogl_framebuffer_gl_bind (draw_buffer, GL_DRAW_FRAMEBUFFER);
+           _cogl_framebuffer_gl_bind (read_buffer, GL_READ_FRAMEBUFFER);
+diff --git a/cogl/cogl/driver/gl/gl/cogl-driver-gl.c b/cogl/cogl/driver/gl/gl/cogl-driver-gl.c
+index 716617b54b..f905267c53 100644
+--- a/cogl/cogl/driver/gl/gl/cogl-driver-gl.c
++++ b/cogl/cogl/driver/gl/gl/cogl-driver-gl.c
+@@ -466,8 +466,8 @@ _cogl_driver_update_features (CoglContext *ctx,
+     }
+ 
+   if (ctx->glBlitFramebuffer)
+-    COGL_FLAGS_SET (private_features,
+-                    COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER, TRUE);
++    COGL_FLAGS_SET (ctx->features,
++                    COGL_FEATURE_ID_BLIT_FRAMEBUFFER, TRUE);
+ 
+   if (ctx->glRenderbufferStorageMultisampleIMG)
+     {
+diff --git a/cogl/cogl/driver/gl/gles/cogl-driver-gles.c b/cogl/cogl/driver/gl/gles/cogl-driver-gles.c
+index 902bd0bd3a..e55bb302c4 100644
+--- a/cogl/cogl/driver/gl/gles/cogl-driver-gles.c
++++ b/cogl/cogl/driver/gl/gles/cogl-driver-gles.c
+@@ -325,8 +325,8 @@ _cogl_driver_update_features (CoglContext *context,
+     }
+ 
+   if (context->glBlitFramebuffer)
+-    COGL_FLAGS_SET (private_features,
+-                    COGL_PRIVATE_FEATURE_BLIT_FRAMEBUFFER, TRUE);
++    COGL_FLAGS_SET (context->features,
++                    COGL_FEATURE_ID_BLIT_FRAMEBUFFER, TRUE);
+ 
+   if (_cogl_check_extension ("GL_OES_element_index_uint", gl_extensions))
+     {
+-- 
+2.28.0
+
+
+From 32aa92e50a12a5fd9652866937750a3c86c4845f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 17:06:35 +0200
+Subject: [PATCH 08/20] renderer/native: Only enable shadowfbs if we can blit
+
+There is no point in enabling shadow buffers if we can't as that'd be
+even slower than not having them at all.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit f191c3b74f572547707fcb6522db76a88689eae2)
+---
+ src/backends/native/meta-renderer-native.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c
+index 463dddd3a7..62ca4bcbd4 100644
+--- a/src/backends/native/meta-renderer-native.c
++++ b/src/backends/native/meta-renderer-native.c
+@@ -3649,6 +3649,9 @@ should_force_shadow_fb (MetaRendererNative *renderer_native,
+       break;
+     }
+ 
++  if (!cogl_has_feature (cogl_context, COGL_FEATURE_ID_BLIT_FRAMEBUFFER))
++    return FALSE;
++
+   kms_fd = meta_gpu_kms_get_fd (primary_gpu);
+   if (drmGetCap (kms_fd, DRM_CAP_DUMB_PREFER_SHADOW, &prefer_shadow) == 0)
+     {
+-- 
+2.28.0
+
+
+From 5f247503e261f5bbb6baedc40c737c96b8144218 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 18:55:03 +0200
+Subject: [PATCH 09/20] clutter/stage-view: Always use cogl_blit_framebuffer()
+ from shadowfb
+
+It should only be used when direct blitting is supported, so there is no
+reason we should have to deal with pipelines etc when blitting from the
+shadow buffer to the onscreen.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 130f696f303a01d6d666ac967c53b4b5dc372f08)
+---
+ clutter/clutter/clutter-stage-view.c | 37 +++++++++++-----------------
+ 1 file changed, 15 insertions(+), 22 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 40edfad6e1..e7e33963a6 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -53,7 +53,6 @@ typedef struct _ClutterStageViewPrivate
+   gboolean use_shadowfb;
+   struct {
+     CoglOffscreen *framebuffer;
+-    CoglPipeline *pipeline;
+   } shadow;
+ 
+   guint dirty_viewport   : 1;
+@@ -149,19 +148,6 @@ clutter_stage_view_ensure_offscreen_blit_pipeline (ClutterStageView *view)
+     view_class->setup_offscreen_blit_pipeline (view, priv->offscreen_pipeline);
+ }
+ 
+-static void
+-clutter_stage_view_ensure_shadowfb_blit_pipeline (ClutterStageView *view)
+-{
+-  ClutterStageViewPrivate *priv =
+-    clutter_stage_view_get_instance_private (view);
+-
+-  if (priv->shadow.pipeline)
+-    return;
+-
+-  priv->shadow.pipeline =
+-    clutter_stage_view_create_framebuffer_pipeline (priv->shadow.framebuffer);
+-}
+-
+ void
+ clutter_stage_view_invalidate_offscreen_blit_pipeline (ClutterStageView *view)
+ {
+@@ -321,13 +307,21 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+ 
+   if (priv->shadow.framebuffer)
+     {
+-      clutter_stage_view_ensure_shadowfb_blit_pipeline (view);
+-      clutter_stage_view_copy_to_framebuffer (view,
+-                                              rect,
+-                                              priv->shadow.pipeline,
+-                                              priv->shadow.framebuffer,
+-                                              priv->framebuffer,
+-                                              TRUE);
++      int width, height;
++      g_autoptr (GError) error = NULL;
++
++      width = cogl_framebuffer_get_width (priv->framebuffer);
++      height = cogl_framebuffer_get_height (priv->framebuffer);
++      if (!cogl_blit_framebuffer (priv->shadow.framebuffer,
++                                  priv->framebuffer,
++                                  0, 0,
++                                  0, 0,
++                                  width, height,
++                                  &error))
++        {
++          g_warning ("Failed to blit shadow buffer: %s", error->message);
++          return;
++        }
+     }
+ }
+ 
+@@ -543,7 +537,6 @@ clutter_stage_view_dispose (GObject *object)
+   g_clear_pointer (&priv->name, g_free);
+   g_clear_pointer (&priv->framebuffer, cogl_object_unref);
+   g_clear_pointer (&priv->shadow.framebuffer, cogl_object_unref);
+-  g_clear_pointer (&priv->shadow.pipeline, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
+ 
+-- 
+2.28.0
+
+
+From d20008aa8630c87d8607e64ff77188fc67b3d22a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 18:59:32 +0200
+Subject: [PATCH 10/20] clutter/stage-view: Simplify painting of offscreen
+ slightly
+
+We will only ever have an "offscreen" if we're painting transformed in
+some way, so the 'can_blit' checking is unnecessary. Remove it.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 32d5e7d3d77c7ba29b8a7da45731aa31bd486056)
+---
+ clutter/clutter/clutter-stage-view.c | 49 +++++++---------------------
+ 1 file changed, 12 insertions(+), 37 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index e7e33963a6..64fb20cb00 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -158,29 +158,13 @@ clutter_stage_view_invalidate_offscreen_blit_pipeline (ClutterStageView *view)
+ }
+ 
+ static void
+-clutter_stage_view_copy_to_framebuffer (ClutterStageView            *view,
+-                                        const cairo_rectangle_int_t *rect,
+-                                        CoglPipeline                *pipeline,
+-                                        CoglFramebuffer             *src_framebuffer,
+-                                        CoglFramebuffer             *dst_framebuffer,
+-                                        gboolean                     can_blit)
++paint_transformed_framebuffer (ClutterStageView *view,
++                               CoglPipeline     *pipeline,
++                               CoglFramebuffer  *src_framebuffer,
++                               CoglFramebuffer  *dst_framebuffer)
+ {
+   CoglMatrix matrix;
+ 
+-  /* First, try with blit */
+-  if (can_blit)
+-    {
+-      if (cogl_blit_framebuffer (src_framebuffer,
+-                                 dst_framebuffer,
+-                                 0, 0,
+-                                 0, 0,
+-                                 cogl_framebuffer_get_width (dst_framebuffer),
+-                                 cogl_framebuffer_get_height (dst_framebuffer),
+-                                 NULL))
+-        return;
+-    }
+-
+-  /* If blit fails, fallback to the slower painting method */
+   cogl_framebuffer_push_matrix (dst_framebuffer);
+ 
+   cogl_matrix_init_identity (&matrix);
+@@ -278,30 +262,21 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+ 
+   if (priv->offscreen)
+     {
+-      gboolean can_blit;
+-      CoglMatrix matrix;
+-
+       clutter_stage_view_ensure_offscreen_blit_pipeline (view);
+-      clutter_stage_view_get_offscreen_transformation_matrix (view, &matrix);
+-      can_blit = cogl_matrix_is_identity (&matrix);
+ 
+       if (priv->shadow.framebuffer)
+         {
+-          clutter_stage_view_copy_to_framebuffer (view,
+-                                                  rect,
+-                                                  priv->offscreen_pipeline,
+-                                                  priv->offscreen,
+-                                                  priv->shadow.framebuffer,
+-                                                  can_blit);
++          paint_transformed_framebuffer (view,
++                                         priv->offscreen_pipeline,
++                                         priv->offscreen,
++                                         priv->shadow.framebuffer);
+         }
+       else
+         {
+-          clutter_stage_view_copy_to_framebuffer (view,
+-                                                  rect,
+-                                                  priv->offscreen_pipeline,
+-                                                  priv->offscreen,
+-                                                  priv->framebuffer,
+-                                                  can_blit);
++          paint_transformed_framebuffer (view,
++                                         priv->offscreen_pipeline,
++                                         priv->offscreen,
++                                         priv->framebuffer);
+         }
+     }
+ 
+-- 
+2.28.0
+
+
+From 8fca65cc3ff989529bf08a47f20b80691f91f95f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 19:08:03 +0200
+Subject: [PATCH 11/20] region-utils: Make transform util const correct
+
+The input should be const, as it will not be altered.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 761bc64cdd4746389625173454b8861cf211cd79)
+---
+ src/compositor/region-utils.c | 2 +-
+ src/compositor/region-utils.h | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/compositor/region-utils.c b/src/compositor/region-utils.c
+index 752af85c5c..8edb89322c 100644
+--- a/src/compositor/region-utils.c
++++ b/src/compositor/region-utils.c
+@@ -376,7 +376,7 @@ meta_make_border_region (cairo_region_t *region,
+ }
+ 
+ cairo_region_t *
+-meta_region_transform (cairo_region_t       *region,
++meta_region_transform (const cairo_region_t *region,
+                        MetaMonitorTransform  transform,
+                        int                   width,
+                        int                   height)
+diff --git a/src/compositor/region-utils.h b/src/compositor/region-utils.h
+index 84e4d83bc2..ca1b8b7b45 100644
+--- a/src/compositor/region-utils.h
++++ b/src/compositor/region-utils.h
+@@ -106,7 +106,7 @@ cairo_region_t * meta_make_border_region (cairo_region_t *region,
+                                           int             y_amount,
+                                           gboolean        flip);
+ 
+-cairo_region_t * meta_region_transform (cairo_region_t       *region,
++cairo_region_t * meta_region_transform (const cairo_region_t *region,
+                                         MetaMonitorTransform  transform,
+                                         int                   width,
+                                         int                   height);
+-- 
+2.28.0
+
+
+From 58331ff2f10aad87f537e3ebdaa5707c13c9e41b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 19:05:36 +0200
+Subject: [PATCH 12/20] clutter/stage-cogl: Use buffer age when view monitor is
+ rotated
+
+We failed to use the buffer age when monitors were rotated, as when they
+are, we first composite to an offscreen framebuffer, then later again to
+the onscreen. The buffer age checking happened on the offscreen, and an
+offscreen being single buffered, they can't possible support buffer
+ages.
+
+Instead, move the buffer age check to check the actual onscreen
+framebuffer. The offscreen to onscreen painting is still always full
+frame, but that will be fixed in a later commit.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 41c2c2c7d72a0bc8ac1970d35183345424642cf1)
+---
+ clutter/clutter/clutter-stage-view-private.h |  6 +++
+ clutter/clutter/clutter-stage-view.c         | 29 ++++++-----
+ clutter/clutter/clutter-stage-view.h         | 11 ++--
+ clutter/clutter/cogl/clutter-stage-cogl.c    | 54 +++++++-------------
+ src/backends/meta-renderer-view.c            | 22 ++++++++
+ 5 files changed, 68 insertions(+), 54 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view-private.h b/clutter/clutter/clutter-stage-view-private.h
+index e27f140b8a..10f9847b70 100644
+--- a/clutter/clutter/clutter-stage-view-private.h
++++ b/clutter/clutter/clutter-stage-view-private.h
+@@ -40,4 +40,10 @@ void clutter_stage_view_invalidate_projection (ClutterStageView *view);
+ void clutter_stage_view_set_projection (ClutterStageView *view,
+                                         const CoglMatrix *matrix);
+ 
++void clutter_stage_view_transform_rect_to_onscreen (ClutterStageView            *view,
++                                                    const cairo_rectangle_int_t *src_rect,
++                                                    int                          dst_width,
++                                                    int                          dst_height,
++                                                    cairo_rectangle_int_t       *dst_rect);
++
+ #endif /* __CLUTTER_STAGE_VIEW_PRIVATE_H__ */
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 64fb20cb00..080bfd6669 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -157,6 +157,22 @@ clutter_stage_view_invalidate_offscreen_blit_pipeline (ClutterStageView *view)
+   g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
+ }
+ 
++void
++clutter_stage_view_transform_rect_to_onscreen (ClutterStageView            *view,
++                                               const cairo_rectangle_int_t *src_rect,
++                                               int                          dst_width,
++                                               int                          dst_height,
++                                               cairo_rectangle_int_t       *dst_rect)
++{
++  ClutterStageViewClass *view_class = CLUTTER_STAGE_VIEW_GET_CLASS (view);
++
++  return view_class->transform_rect_to_onscreen (view,
++                                                 src_rect,
++                                                 dst_width,
++                                                 dst_height,
++                                                 dst_rect);
++}
++
+ static void
+ paint_transformed_framebuffer (ClutterStageView *view,
+                                CoglPipeline     *pipeline,
+@@ -383,19 +399,6 @@ clutter_stage_view_get_offscreen_transformation_matrix (ClutterStageView *view,
+   view_class->get_offscreen_transformation_matrix (view, matrix);
+ }
+ 
+-void
+-clutter_stage_view_transform_to_onscreen (ClutterStageView *view,
+-                                          gfloat           *x,
+-                                          gfloat           *y)
+-{
+-  gfloat z = 0, w = 1;
+-  CoglMatrix matrix;
+-
+-  clutter_stage_view_get_offscreen_transformation_matrix (view, &matrix);
+-  cogl_matrix_get_inverse (&matrix, &matrix);
+-  cogl_matrix_transform_point (&matrix, x, y, &z, &w);
+-}
+-
+ static void
+ clutter_stage_default_get_offscreen_transformation_matrix (ClutterStageView *view,
+                                                            CoglMatrix       *matrix)
+diff --git a/clutter/clutter/clutter-stage-view.h b/clutter/clutter/clutter-stage-view.h
+index 26bf10e798..eb0184e9ab 100644
+--- a/clutter/clutter/clutter-stage-view.h
++++ b/clutter/clutter/clutter-stage-view.h
+@@ -43,6 +43,12 @@ struct _ClutterStageViewClass
+ 
+   void (* get_offscreen_transformation_matrix) (ClutterStageView *view,
+                                                 CoglMatrix       *matrix);
++
++  void (* transform_rect_to_onscreen) (ClutterStageView            *view,
++                                       const cairo_rectangle_int_t *src_rect,
++                                       int                          dst_width,
++                                       int                          dst_height,
++                                       cairo_rectangle_int_t       *dst_rect);
+ };
+ 
+ CLUTTER_EXPORT
+@@ -56,11 +62,6 @@ CoglFramebuffer *clutter_stage_view_get_onscreen (ClutterStageView *view);
+ CLUTTER_EXPORT
+ void             clutter_stage_view_invalidate_offscreen_blit_pipeline (ClutterStageView *view);
+ 
+-CLUTTER_EXPORT
+-void             clutter_stage_view_transform_to_onscreen (ClutterStageView *view,
+-                                                           gfloat           *x,
+-                                                           gfloat           *y);
+-
+ CLUTTER_EXPORT
+ float clutter_stage_view_get_scale (ClutterStageView *view);
+ 
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index 005c6f6922..821f78ee7c 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -509,36 +509,17 @@ static void
+ transform_swap_region_to_onscreen (ClutterStageView      *view,
+                                    cairo_rectangle_int_t *swap_region)
+ {
+-  CoglFramebuffer *framebuffer;
+-  cairo_rectangle_int_t layout;
+-  gfloat x1, y1, x2, y2;
+-  gint width, height;
+-
+-  framebuffer = clutter_stage_view_get_onscreen (view);
+-  clutter_stage_view_get_layout (view, &layout);
+-
+-  x1 = (float) swap_region->x / layout.width;
+-  y1 = (float) swap_region->y / layout.height;
+-  x2 = (float) (swap_region->x + swap_region->width) / layout.width;
+-  y2 = (float) (swap_region->y + swap_region->height) / layout.height;
+-
+-  clutter_stage_view_transform_to_onscreen (view, &x1, &y1);
+-  clutter_stage_view_transform_to_onscreen (view, &x2, &y2);
+-
+-  width = cogl_framebuffer_get_width (framebuffer);
+-  height = cogl_framebuffer_get_height (framebuffer);
+-
+-  x1 = floor (x1 * width);
+-  y1 = floor (height - (y1 * height));
+-  x2 = ceil (x2 * width);
+-  y2 = ceil (height - (y2 * height));
+-
+-  *swap_region = (cairo_rectangle_int_t) {
+-    .x = x1,
+-    .y = y1,
+-    .width = x2 - x1,
+-    .height = y2 - y1
+-  };
++  CoglFramebuffer *onscreen = clutter_stage_view_get_onscreen (view);
++  int width, height;
++
++  width = cogl_framebuffer_get_width (onscreen);
++  height = cogl_framebuffer_get_height (onscreen);
++
++  clutter_stage_view_transform_rect_to_onscreen (view,
++                                                 swap_region,
++                                                 width,
++                                                 height,
++                                                 swap_region);
+ }
+ 
+ static void
+@@ -593,6 +574,7 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+   ClutterStageViewCoglPrivate *view_priv =
+     clutter_stage_view_cogl_get_instance_private (view_cogl);
+   CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view);
++  CoglFramebuffer *onscreen = clutter_stage_view_get_onscreen (view);
+   cairo_rectangle_int_t view_rect;
+   gboolean have_clip;
+   gboolean may_use_clipped_redraw;
+@@ -618,10 +600,10 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+   fb_height = cogl_framebuffer_get_height (fb);
+ 
+   can_blit_sub_buffer =
+-    cogl_is_onscreen (fb) &&
++    cogl_is_onscreen (onscreen) &&
+     cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_REGION);
+ 
+-  has_buffer_age = cogl_is_onscreen (fb) && is_buffer_age_enabled ();
++  has_buffer_age = cogl_is_onscreen (onscreen) && is_buffer_age_enabled ();
+ 
+   /* NB: a zero width redraw clip == full stage redraw */
+   if (stage_cogl->bounding_redraw_clip.width == 0)
+@@ -645,7 +627,7 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+       have_clip &&
+       /* some drivers struggle to get going and produce some junk
+        * frames when starting up... */
+-      cogl_onscreen_get_frame_counter (COGL_ONSCREEN (fb)) > 3)
++      cogl_onscreen_get_frame_counter (COGL_ONSCREEN (onscreen)) > 3)
+     {
+       ClutterRect rect;
+ 
+@@ -686,7 +668,7 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+           cairo_rectangle_int_t *current_fb_damage =
+             &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index++)];
+ 
+-          age = cogl_onscreen_get_buffer_age (COGL_ONSCREEN (fb));
++          age = cogl_onscreen_get_buffer_age (COGL_ONSCREEN (onscreen));
+ 
+           if (valid_buffer_age (view_cogl, age))
+             {
+@@ -961,9 +943,9 @@ clutter_stage_cogl_get_dirty_pixel (ClutterStageWindow *stage_window,
+                                     int                *x,
+                                     int                *y)
+ {
+-  CoglFramebuffer *framebuffer = clutter_stage_view_get_framebuffer (view);
++  CoglFramebuffer *onscreen = clutter_stage_view_get_onscreen (view);
+   gboolean has_buffer_age =
+-    cogl_is_onscreen (framebuffer) &&
++    cogl_is_onscreen (onscreen) &&
+     is_buffer_age_enabled ();
+   float fb_scale;
+   gboolean scale_is_fractional;
+diff --git a/src/backends/meta-renderer-view.c b/src/backends/meta-renderer-view.c
+index cab1f5f483..4e45f2ef02 100644
+--- a/src/backends/meta-renderer-view.c
++++ b/src/backends/meta-renderer-view.c
+@@ -34,6 +34,7 @@
+ 
+ #include "backends/meta-renderer.h"
+ #include "clutter/clutter-mutter.h"
++#include "compositor/region-utils.h"
+ 
+ enum
+ {
+@@ -125,6 +126,25 @@ meta_renderer_view_setup_offscreen_blit_pipeline (ClutterStageView *view,
+   cogl_pipeline_set_layer_matrix (pipeline, 0, &matrix);
+ }
+ 
++static void
++meta_renderer_view_transform_rect_to_onscreen (ClutterStageView            *view,
++                                               const cairo_rectangle_int_t *src_rect,
++                                               int                          dst_width,
++                                               int                          dst_height,
++                                               cairo_rectangle_int_t       *dst_rect)
++{
++  MetaRendererView *renderer_view = META_RENDERER_VIEW (view);
++  MetaMonitorTransform inverted_transform;
++
++  inverted_transform =
++    meta_monitor_transform_invert (renderer_view->transform);
++  return meta_rectangle_transform (src_rect,
++                                   inverted_transform,
++                                   dst_width,
++                                   dst_height,
++                                   dst_rect);
++}
++
+ static void
+ meta_renderer_view_set_transform (MetaRendererView     *view,
+                                   MetaMonitorTransform  transform)
+@@ -195,6 +215,8 @@ meta_renderer_view_class_init (MetaRendererViewClass *klass)
+     meta_renderer_view_setup_offscreen_blit_pipeline;
+   view_class->get_offscreen_transformation_matrix =
+     meta_renderer_view_get_offscreen_transformation_matrix;
++  view_class->transform_rect_to_onscreen =
++    meta_renderer_view_transform_rect_to_onscreen;
+ 
+   object_class->get_property = meta_renderer_view_get_property;
+   object_class->set_property = meta_renderer_view_set_property;
+-- 
+2.28.0
+
+
+From 6fc1da9dd3ac2753771bb68adb780d1d55494cba Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 19:22:10 +0200
+Subject: [PATCH 13/20] clutter/stage-view: Only paint redraw clip from
+ offscreen
+
+The rest didn't change, so only actually paint the part of the offscreen
+that was composited as part of the stage painting. In practice, this
+means that, unless a shadow buffer is used, we now only paint the
+damaged part of the stage, and copy the damage part of the offscreen to
+the onscreen.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit acf6b79e3a5b9d8d285886c471961e8c0bec48ce)
+---
+ clutter/clutter/clutter-stage-view.c | 85 ++++++++++++++++++++++++----
+ 1 file changed, 73 insertions(+), 12 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 080bfd6669..b686272db0 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -19,6 +19,7 @@
+ 
+ #include "clutter/clutter-stage-view.h"
+ #include "clutter/clutter-stage-view-private.h"
++#include "clutter/clutter-private.h"
+ 
+ #include <cairo-gobject.h>
+ #include <math.h>
+@@ -174,23 +175,81 @@ clutter_stage_view_transform_rect_to_onscreen (ClutterStageView            *view
+ }
+ 
+ static void
+-paint_transformed_framebuffer (ClutterStageView *view,
+-                               CoglPipeline     *pipeline,
+-                               CoglFramebuffer  *src_framebuffer,
+-                               CoglFramebuffer  *dst_framebuffer)
++paint_transformed_framebuffer (ClutterStageView            *view,
++                               CoglPipeline                *pipeline,
++                               CoglFramebuffer             *src_framebuffer,
++                               CoglFramebuffer             *dst_framebuffer,
++                               const cairo_rectangle_int_t *redraw_clip)
+ {
+   CoglMatrix matrix;
++  int dst_width, dst_height;
++  cairo_rectangle_int_t view_layout;
++  cairo_rectangle_int_t onscreen_layout;
++  float view_scale;
++  float *coordinates;
++  cairo_rectangle_int_t src_rect;
++  cairo_rectangle_int_t dst_rect;
++
++  dst_width = cogl_framebuffer_get_width (dst_framebuffer);
++  dst_height = cogl_framebuffer_get_height (dst_framebuffer);
++  clutter_stage_view_get_layout (view, &view_layout);
++  clutter_stage_view_transform_rect_to_onscreen (view,
++                                                 &(cairo_rectangle_int_t) {
++                                                   .width = view_layout.width,
++                                                   .height = view_layout.height,
++                                                 },
++                                                 view_layout.width,
++                                                 view_layout.height,
++                                                 &onscreen_layout);
++  view_scale = clutter_stage_view_get_scale (view);
+ 
+   cogl_framebuffer_push_matrix (dst_framebuffer);
+ 
+   cogl_matrix_init_identity (&matrix);
+-  cogl_matrix_translate (&matrix, -1, 1, 0);
+-  cogl_matrix_scale (&matrix, 2, -2, 0);
++  cogl_matrix_scale (&matrix,
++                     1.0 / (dst_width / 2.0),
++                     -1.0 / (dst_height / 2.0), 0);
++  cogl_matrix_translate (&matrix,
++                         -(dst_width / 2.0),
++                         -(dst_height / 2.0), 0);
+   cogl_framebuffer_set_projection_matrix (dst_framebuffer, &matrix);
+-
+-  cogl_framebuffer_draw_rectangle (dst_framebuffer,
+-                                   pipeline,
+-                                   0, 0, 1, 1);
++  cogl_framebuffer_set_viewport (dst_framebuffer,
++                                 0, 0, dst_width, dst_height);
++
++  coordinates = g_newa (float, 2 * 4);
++
++  src_rect = *redraw_clip;
++  _clutter_util_rectangle_offset (&src_rect,
++                                  -view_layout.x,
++                                  -view_layout.y,
++                                  &src_rect);
++
++  clutter_stage_view_transform_rect_to_onscreen (view,
++                                                 &src_rect,
++                                                 onscreen_layout.width,
++                                                 onscreen_layout.height,
++                                                 &dst_rect);
++
++  coordinates[0] = (float) dst_rect.x * view_scale;
++  coordinates[1] = (float) dst_rect.y * view_scale;
++  coordinates[2] = ((float) (dst_rect.x + dst_rect.width) *
++                    view_scale);
++  coordinates[3] = ((float) (dst_rect.y + dst_rect.height) *
++                    view_scale);
++
++  coordinates[4] = (((float) dst_rect.x / (float) dst_width) *
++                    view_scale);
++  coordinates[5] = (((float) dst_rect.y / (float) dst_height) *
++                    view_scale);
++  coordinates[6] = ((float) (dst_rect.x + dst_rect.width) /
++                    (float) dst_width) * view_scale;
++  coordinates[7] = ((float) (dst_rect.y + dst_rect.height) /
++                    (float) dst_height) * view_scale;
++
++  cogl_framebuffer_draw_textured_rectangles (dst_framebuffer,
++                                             pipeline,
++                                             coordinates,
++                                             1);
+ 
+   cogl_framebuffer_pop_matrix (dst_framebuffer);
+ }
+@@ -285,14 +344,16 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+           paint_transformed_framebuffer (view,
+                                          priv->offscreen_pipeline,
+                                          priv->offscreen,
+-                                         priv->shadow.framebuffer);
++                                         priv->shadow.framebuffer,
++                                         rect);
+         }
+       else
+         {
+           paint_transformed_framebuffer (view,
+                                          priv->offscreen_pipeline,
+                                          priv->offscreen,
+-                                         priv->framebuffer);
++                                         priv->framebuffer,
++                                         rect);
+         }
+     }
+ 
+-- 
+2.28.0
+
+
+From ff3164440e6bbb3e845a1d4a23843a5792afc16f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 13 May 2020 17:18:50 +0200
+Subject: [PATCH 14/20] clutter/stage-cogl: Only construct damage array if
+ it'll be used
+
+It's only used when we actually swap buffers, which we only do if the
+target framebuffer is an onscreen.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 95a80c442b6300ce5b41b4b3975a372f1eabd166)
+---
+ clutter/clutter/cogl/clutter-stage-cogl.c | 22 +++++++++++-----------
+ 1 file changed, 11 insertions(+), 11 deletions(-)
+
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index 821f78ee7c..fc6d0d031d 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -413,17 +413,6 @@ swap_framebuffer (ClutterStageWindow    *stage_window,
+                   gboolean               swap_with_damage)
+ {
+   CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (view);
+-  int damage[4], ndamage;
+-
+-  damage[0] = swap_region->x;
+-  damage[1] = swap_region->y;
+-  damage[2] = swap_region->width;
+-  damage[3] = swap_region->height;
+-
+-  if (swap_region->width != 0)
+-    ndamage = 1;
+-  else
+-    ndamage = 0;
+ 
+   if (G_UNLIKELY ((clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_DAMAGE_REGION)))
+     paint_damage_region (stage_window, view, swap_region);
+@@ -431,6 +420,17 @@ swap_framebuffer (ClutterStageWindow    *stage_window,
+   if (cogl_is_onscreen (framebuffer))
+     {
+       CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
++      int damage[4], ndamage;
++
++      damage[0] = swap_region->x;
++      damage[1] = swap_region->y;
++      damage[2] = swap_region->width;
++      damage[3] = swap_region->height;
++
++      if (swap_region->width != 0)
++        ndamage = 1;
++      else
++        ndamage = 0;
+ 
+       /* push on the screen */
+       if (ndamage == 1 && !swap_with_damage)
+-- 
+2.28.0
+
+
+From f946746f5938e7d6c48b688827fb991f22dc1364 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Tue, 5 May 2020 19:25:23 +0200
+Subject: [PATCH 15/20] clutter/stage-view: Only blit the damage part of the
+ shadow buffer
+
+This fixes the last "copy everything" paths when clutter doesn't
+directly paint onto the onscreen framebuffer. It adds a new hook into
+the stage view called before the swap buffer, as at this point, we have
+the swap buffer damag regions ready, which corresponds to the regions we
+must blit according to the damage reported to clutter.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 851e7727ec6f3719139ab562ac2524cdc1bd64ae)
+---
+ clutter/clutter/clutter-stage-view-private.h |  3 +++
+ clutter/clutter/clutter-stage-view.c         | 25 ++++++++++++++++++--
+ clutter/clutter/cogl/clutter-stage-cogl.c    |  2 ++
+ 3 files changed, 28 insertions(+), 2 deletions(-)
+
+diff --git a/clutter/clutter/clutter-stage-view-private.h b/clutter/clutter/clutter-stage-view-private.h
+index 10f9847b70..bddc38ded6 100644
+--- a/clutter/clutter/clutter-stage-view-private.h
++++ b/clutter/clutter/clutter-stage-view-private.h
+@@ -23,6 +23,9 @@
+ void clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+                                         const cairo_rectangle_int_t *clip);
+ 
++void clutter_stage_view_before_swap_buffer (ClutterStageView            *view,
++                                            const cairo_rectangle_int_t *swap_region);
++
+ gboolean clutter_stage_view_is_dirty_viewport (ClutterStageView *view);
+ 
+ void clutter_stage_view_invalidate_viewport (ClutterStageView *view);
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index b686272db0..21ab02c97b 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -356,11 +356,22 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+                                          rect);
+         }
+     }
++}
++
++void
++clutter_stage_view_before_swap_buffer (ClutterStageView            *view,
++                                       const cairo_rectangle_int_t *swap_region)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  g_autoptr (GError) error = NULL;
+ 
+-  if (priv->shadow.framebuffer)
++  if (!priv->shadow.framebuffer)
++    return;
++
++  if (swap_region->width == 0 || swap_region->height == 0)
+     {
+       int width, height;
+-      g_autoptr (GError) error = NULL;
+ 
+       width = cogl_framebuffer_get_width (priv->framebuffer);
+       height = cogl_framebuffer_get_height (priv->framebuffer);
+@@ -370,6 +381,16 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+                                   0, 0,
+                                   width, height,
+                                   &error))
++        g_warning ("Failed to blit shadow buffer: %s", error->message);
++    }
++  else
++    {
++      if (!cogl_blit_framebuffer (priv->shadow.framebuffer,
++                                  priv->framebuffer,
++                                  swap_region->x, swap_region->y,
++                                  swap_region->x, swap_region->y,
++                                  swap_region->width, swap_region->height,
++                                  &error))
+         {
+           g_warning ("Failed to blit shadow buffer: %s", error->message);
+           return;
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index fc6d0d031d..884819ebd3 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -417,6 +417,8 @@ swap_framebuffer (ClutterStageWindow    *stage_window,
+   if (G_UNLIKELY ((clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_DAMAGE_REGION)))
+     paint_damage_region (stage_window, view, swap_region);
+ 
++  clutter_stage_view_before_swap_buffer (view, swap_region);
++
+   if (cogl_is_onscreen (framebuffer))
+     {
+       CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
+-- 
+2.28.0
+
+
+From 757dd09dc9b76a7654f087679db1c7f005b7653c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 6 May 2020 09:11:34 +0200
+Subject: [PATCH 16/20] clutter/stage-cogl: Extract damage history logic
+
+Move the damage history tracking to a new ClutterDamageHistory helper
+type. The aim is to be able to track damage history elsewhere without
+reimplementing the data structure and tracking logic.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 09271bcfef8889022f15a3b2949843e55f3df9da)
+---
+ clutter/clutter/clutter-damage-history.c  |  89 +++++++++++++++++
+ clutter/clutter/clutter-damage-history.h  |  42 ++++++++
+ clutter/clutter/cogl/clutter-stage-cogl.c | 116 ++++++++++++----------
+ clutter/clutter/meson.build               |   2 +
+ 4 files changed, 195 insertions(+), 54 deletions(-)
+ create mode 100644 clutter/clutter/clutter-damage-history.c
+ create mode 100644 clutter/clutter/clutter-damage-history.h
+
+diff --git a/clutter/clutter/clutter-damage-history.c b/clutter/clutter/clutter-damage-history.c
+new file mode 100644
+index 0000000000..78ab0f7b5e
+--- /dev/null
++++ b/clutter/clutter/clutter-damage-history.c
+@@ -0,0 +1,89 @@
++/*
++ * Copyright (C) 2007,2008,2009,2010,2011  Intel Corporation.
++ * Copyright (C) 2020 Red Hat Inc
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "clutter-build-config.h"
++
++#include "clutter-damage-history.h"
++
++#define DAMAGE_HISTORY_LENGTH 0x10
++
++struct _ClutterDamageHistory
++{
++  cairo_rectangle_int_t damages[DAMAGE_HISTORY_LENGTH];
++  int index;
++};
++
++ClutterDamageHistory *
++clutter_damage_history_new (void)
++{
++  ClutterDamageHistory *history;
++
++  history = g_new0 (ClutterDamageHistory, 1);
++
++  return history;
++}
++
++void
++clutter_damage_history_free (ClutterDamageHistory *history)
++{
++  g_free (history);
++}
++
++gboolean
++clutter_damage_history_is_age_valid (ClutterDamageHistory *history,
++                                     int                   age)
++{
++  const cairo_rectangle_int_t *damage;
++
++  if (age >= DAMAGE_HISTORY_LENGTH ||
++      age < 1)
++    return FALSE;
++
++  damage = clutter_damage_history_lookup (history, age);
++  if (damage->width == 0 || damage->height == 0)
++    return FALSE;
++
++  return TRUE;
++}
++
++void
++clutter_damage_history_record (ClutterDamageHistory        *history,
++                               const cairo_rectangle_int_t *damage)
++{
++  history->damages[history->index] = *damage;
++}
++
++static inline int
++step_damage_index (int current,
++                   int diff)
++{
++  return (current + diff) & (DAMAGE_HISTORY_LENGTH - 1);
++}
++
++void
++clutter_damage_history_step (ClutterDamageHistory *history)
++{
++  history->index = step_damage_index (history->index, 1);
++}
++
++const cairo_rectangle_int_t *
++clutter_damage_history_lookup (ClutterDamageHistory *history,
++                               int                   age)
++{
++  return &history->damages[step_damage_index (history->index, -age)];
++}
+diff --git a/clutter/clutter/clutter-damage-history.h b/clutter/clutter/clutter-damage-history.h
+new file mode 100644
+index 0000000000..6c483acab7
+--- /dev/null
++++ b/clutter/clutter/clutter-damage-history.h
+@@ -0,0 +1,42 @@
++/*
++ * Copyright (C) 2007,2008,2009,2010,2011  Intel Corporation.
++ * Copyright (C) 2020 Red Hat Inc
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef CLUTTER_DAMAGE_HISTORY_H
++#define CLUTTER_DAMAGE_HISTORY_H
++
++#include <cairo.h>
++#include <glib.h>
++
++typedef struct _ClutterDamageHistory ClutterDamageHistory;
++
++ClutterDamageHistory * clutter_damage_history_new (void);
++
++void clutter_damage_history_free (ClutterDamageHistory *history);
++
++gboolean clutter_damage_history_is_age_valid (ClutterDamageHistory *history,
++                                              int                   age);
++
++void clutter_damage_history_record (ClutterDamageHistory        *history,
++                                    const cairo_rectangle_int_t *damage);
++
++void clutter_damage_history_step (ClutterDamageHistory *history);
++
++const cairo_rectangle_int_t * clutter_damage_history_lookup (ClutterDamageHistory *history,
++                                                             int                   age);
++
++#endif /* CLUTTER_DAMAGE_HISTORY_H */
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index 884819ebd3..11273ec894 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -38,6 +38,7 @@
+ 
+ #include "clutter-actor-private.h"
+ #include "clutter-backend-private.h"
++#include "clutter-damage-history.h"
+ #include "clutter-debug.h"
+ #include "clutter-event.h"
+ #include "clutter-enum-types.h"
+@@ -49,13 +50,9 @@
+ 
+ typedef struct _ClutterStageViewCoglPrivate
+ {
+-  /*
+-   * List of previous damaged areas in stage view framebuffer coordinate space.
++  /* Damage history, in stage view render target framebuffer coordinate space.
+    */
+-#define DAMAGE_HISTORY_MAX 16
+-#define DAMAGE_HISTORY(x) ((x) & (DAMAGE_HISTORY_MAX - 1))
+-  cairo_rectangle_int_t damage_history[DAMAGE_HISTORY_MAX];
+-  unsigned int damage_index;
++  ClutterDamageHistory *damage_history;
+ } ClutterStageViewCoglPrivate;
+ 
+ G_DEFINE_TYPE_WITH_PRIVATE (ClutterStageViewCogl, clutter_stage_view_cogl,
+@@ -348,10 +345,7 @@ valid_buffer_age (ClutterStageViewCogl *view_cogl,
+   ClutterStageViewCoglPrivate *view_priv =
+     clutter_stage_view_cogl_get_instance_private (view_cogl);
+ 
+-  if (age <= 0)
+-    return FALSE;
+-
+-  return age < MIN (view_priv->damage_index, DAMAGE_HISTORY_MAX);
++  return clutter_damage_history_is_age_valid (view_priv->damage_history, age);
+ }
+ 
+ static void
+@@ -483,30 +477,6 @@ paint_stage (ClutterStageCogl            *stage_cogl,
+   clutter_stage_view_blit_offscreen (view, clip);
+ }
+ 
+-static void
+-fill_current_damage_history_and_step (ClutterStageView *view)
+-{
+-  ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
+-  ClutterStageViewCoglPrivate *view_priv =
+-    clutter_stage_view_cogl_get_instance_private (view_cogl);
+-  cairo_rectangle_int_t view_rect;
+-  float fb_scale;
+-  cairo_rectangle_int_t *current_fb_damage;
+-
+-  current_fb_damage =
+-    &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index)];
+-  clutter_stage_view_get_layout (view, &view_rect);
+-  fb_scale = clutter_stage_view_get_scale (view);
+-
+-  *current_fb_damage = (cairo_rectangle_int_t) {
+-    .x = 0,
+-    .y = 0,
+-    .width = view_rect.width * fb_scale,
+-    .height = view_rect.height * fb_scale
+-  };
+-  view_priv->damage_index++;
+-}
+-
+ static void
+ transform_swap_region_to_onscreen (ClutterStageView      *view,
+                                    cairo_rectangle_int_t *swap_region)
+@@ -567,6 +537,24 @@ scale_and_clamp_rect (const ClutterRect     *rect,
+   _clutter_util_rectangle_int_extents (&tmp, dest);
+ }
+ 
++static void
++record_full_damage (ClutterStageView *view)
++{
++  ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
++  ClutterStageViewCoglPrivate *view_priv =
++    clutter_stage_view_cogl_get_instance_private (view_cogl);
++  CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view);
++  int fb_width, fb_height;
++
++  fb_width = cogl_framebuffer_get_width (fb);
++  fb_height = cogl_framebuffer_get_height (fb);
++  clutter_damage_history_record (view_priv->damage_history,
++                                 &(cairo_rectangle_int_t) {
++                                   .width = fb_width,
++                                   .height = fb_height
++                                 });
++}
++
+ static gboolean
+ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+                                 ClutterStageView   *view)
+@@ -666,9 +654,7 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+     {
+       if (use_clipped_redraw && !clip_region_empty)
+         {
+-          int age, i;
+-          cairo_rectangle_int_t *current_fb_damage =
+-            &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index++)];
++          int age;
+ 
+           age = cogl_onscreen_get_buffer_age (COGL_ONSCREEN (onscreen));
+ 
+@@ -676,16 +662,20 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+             {
+               ClutterRect rect;
+               cairo_rectangle_int_t damage_region;
++              int i;
+ 
+-              *current_fb_damage = fb_clip_region;
++              clutter_damage_history_record (view_priv->damage_history,
++                                             &fb_clip_region);
+ 
+               for (i = 1; i <= age; i++)
+                 {
+-                  cairo_rectangle_int_t *fb_damage =
+-                    &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index - i - 1)];
++                  const cairo_rectangle_int_t *old_damage;
++
++                  old_damage =
++                    clutter_damage_history_lookup (view_priv->damage_history, i);
+ 
+                   _clutter_util_rectangle_union (&fb_clip_region,
+-                                                 fb_damage,
++                                                 old_damage,
+                                                  &fb_clip_region);
+                 }
+ 
+@@ -713,18 +703,15 @@ clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
+             {
+               CLUTTER_NOTE (CLIPPING, "Invalid back buffer(age=%d): forcing full redraw\n", age);
+               use_clipped_redraw = FALSE;
+-              *current_fb_damage = (cairo_rectangle_int_t) {
+-                .x = 0,
+-                .y = 0,
+-                .width = view_rect.width * fb_scale,
+-                .height = view_rect.height * fb_scale
+-              };
++              record_full_damage (view);
+             }
+         }
+       else if (!use_clipped_redraw)
+         {
+-          fill_current_damage_history_and_step (view);
++          record_full_damage (view);
+         }
++
++      clutter_damage_history_step (view_priv->damage_history);
+     }
+ 
+   cogl_push_framebuffer (fb);
+@@ -946,6 +933,9 @@ clutter_stage_cogl_get_dirty_pixel (ClutterStageWindow *stage_window,
+                                     int                *y)
+ {
+   CoglFramebuffer *onscreen = clutter_stage_view_get_onscreen (view);
++  ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
++  ClutterStageViewCoglPrivate *view_priv =
++    clutter_stage_view_cogl_get_instance_private (view_cogl);
+   gboolean has_buffer_age =
+     cogl_is_onscreen (onscreen) &&
+     is_buffer_age_enabled ();
+@@ -967,22 +957,21 @@ clutter_stage_cogl_get_dirty_pixel (ClutterStageWindow *stage_window,
+    * For now, always use the (0, 0) pixel for picking when using fractional
+    * framebuffer scaling.
+    */
+-  if (!has_buffer_age || scale_is_fractional)
++  if (!has_buffer_age ||
++      scale_is_fractional ||
++      !clutter_damage_history_is_age_valid (view_priv->damage_history, 0))
+     {
+       *x = 0;
+       *y = 0;
+     }
+   else
+     {
+-      ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
+-      ClutterStageViewCoglPrivate *view_priv =
+-        clutter_stage_view_cogl_get_instance_private (view_cogl);
+       cairo_rectangle_int_t view_layout;
+-      cairo_rectangle_int_t *fb_damage;
++      const cairo_rectangle_int_t *fb_damage;
+ 
+       clutter_stage_view_get_layout (view, &view_layout);
+ 
+-      fb_damage = &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index - 1)];
++      fb_damage = clutter_damage_history_lookup (view_priv->damage_history, 0);
+       *x = fb_damage->x / fb_scale;
+       *y = fb_damage->y / fb_scale;
+     }
+@@ -1052,12 +1041,31 @@ _clutter_stage_cogl_init (ClutterStageCogl *stage)
+   stage->update_time = -1;
+ }
+ 
++static void
++clutter_stage_view_cogl_finalize (GObject *object)
++{
++  ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (object);
++  ClutterStageViewCoglPrivate *view_priv =
++    clutter_stage_view_cogl_get_instance_private (view_cogl);
++
++  clutter_damage_history_free (view_priv->damage_history);
++
++  G_OBJECT_CLASS (clutter_stage_view_cogl_parent_class)->finalize (object);
++}
++
+ static void
+ clutter_stage_view_cogl_init (ClutterStageViewCogl *view_cogl)
+ {
++  ClutterStageViewCoglPrivate *view_priv =
++    clutter_stage_view_cogl_get_instance_private (view_cogl);
++
++  view_priv->damage_history = clutter_damage_history_new ();
+ }
+ 
+ static void
+ clutter_stage_view_cogl_class_init (ClutterStageViewCoglClass *klass)
+ {
++  GObjectClass *object_class = G_OBJECT_CLASS (klass);
++
++  object_class->finalize = clutter_stage_view_cogl_finalize;
+ }
+diff --git a/clutter/clutter/meson.build b/clutter/clutter/meson.build
+index 8e0484453d..c9eab96d29 100644
+--- a/clutter/clutter/meson.build
++++ b/clutter/clutter/meson.build
+@@ -116,6 +116,7 @@ clutter_sources = [
+   'clutter-constraint.c',
+   'clutter-container.c',
+   'clutter-content.c',
++  'clutter-damage-history.c',
+   'clutter-deform-effect.c',
+   'clutter-desaturate-effect.c',
+   'clutter-device-manager.c',
+@@ -186,6 +187,7 @@ clutter_private_headers = [
+   'clutter-bezier.h',
+   'clutter-constraint-private.h',
+   'clutter-content-private.h',
++  'clutter-damage-history.h',
+   'clutter-debug.h',
+   'clutter-device-manager-private.h',
+   'clutter-easing.h',
+-- 
+2.28.0
+
+
+From 5da1c8083784a351a7763a0c9a9ce4c8359522a4 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 6 May 2020 21:40:40 +0200
+Subject: [PATCH 17/20] cogl/dma-buf: Add API to synchronize reading
+
+Used before and after accessing DMA buffer content using mmap().
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 2d972fc761b9e39f78e66dd84eab57309cdc8658)
+---
+ cogl/cogl/cogl-dma-buf-handle.c | 51 +++++++++++++++++++++++++++++++++
+ cogl/cogl/cogl-dma-buf-handle.h |  8 ++++++
+ cogl/meson.build                |  1 +
+ 3 files changed, 60 insertions(+)
+
+diff --git a/cogl/cogl/cogl-dma-buf-handle.c b/cogl/cogl/cogl-dma-buf-handle.c
+index d8b4e57c55..7e86e2267b 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.c
++++ b/cogl/cogl/cogl-dma-buf-handle.c
+@@ -34,6 +34,10 @@
+ #include "cogl-dma-buf-handle.h"
+ #include "cogl-object.h"
+ 
++#include <errno.h>
++#include <gio/gio.h>
++#include <linux/dma-buf.h>
++#include <sys/ioctl.h>
+ #include <unistd.h>
+ 
+ struct _CoglDmaBufHandle
+@@ -96,6 +100,53 @@ cogl_dma_buf_handle_free (CoglDmaBufHandle *dmabuf_handle)
+   g_free (dmabuf_handle);
+ }
+ 
++static gboolean
++sync_read (CoglDmaBufHandle  *dmabuf_handle,
++           uint64_t           start_or_end,
++           GError           **error)
++{
++  struct dma_buf_sync sync = { 0 };
++
++  sync.flags = start_or_end | DMA_BUF_SYNC_READ;
++
++  while (TRUE)
++    {
++      int ret;
++
++      ret = ioctl (dmabuf_handle->dmabuf_fd, DMA_BUF_IOCTL_SYNC, &sync);
++      if (ret == -1 && errno == EINTR)
++        {
++          continue;
++        }
++      else if (ret == -1)
++        {
++          g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                       "ioctl: %s", g_strerror (errno));
++          return FALSE;
++        }
++      else
++        {
++          break;
++        }
++    }
++
++  return TRUE;
++}
++
++gboolean
++cogl_dma_buf_handle_sync_read_start (CoglDmaBufHandle  *dmabuf_handle,
++                                     GError           **error)
++{
++  return sync_read (dmabuf_handle, DMA_BUF_SYNC_START, error);
++}
++
++gboolean
++cogl_dma_buf_handle_sync_read_end (CoglDmaBufHandle  *dmabuf_handle,
++                                   GError           **error)
++{
++  return sync_read (dmabuf_handle, DMA_BUF_SYNC_END, error);
++}
++
+ CoglFramebuffer *
+ cogl_dma_buf_handle_get_framebuffer (CoglDmaBufHandle *dmabuf_handle)
+ {
+diff --git a/cogl/cogl/cogl-dma-buf-handle.h b/cogl/cogl/cogl-dma-buf-handle.h
+index f64a20678d..63c5bab7b7 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.h
++++ b/cogl/cogl/cogl-dma-buf-handle.h
+@@ -63,6 +63,14 @@ cogl_dma_buf_handle_new (CoglFramebuffer *framebuffer,
+ void
+ cogl_dma_buf_handle_free (CoglDmaBufHandle *dmabuf_handle);
+ 
++gboolean
++cogl_dma_buf_handle_sync_read_start (CoglDmaBufHandle  *dmabuf_handle,
++                                     GError           **error);
++
++gboolean
++cogl_dma_buf_handle_sync_read_end (CoglDmaBufHandle  *dmabuf_handle,
++                                   GError           **error);
++
+ /**
+  * cogl_dma_buf_handle_get_framebuffer: (skip)
+  *
+diff --git a/cogl/meson.build b/cogl/meson.build
+index 356d596f56..47e6a3e0da 100644
+--- a/cogl/meson.build
++++ b/cogl/meson.build
+@@ -23,6 +23,7 @@ cogl_mutter_config_h = configure_file(
+ 
+ cogl_pkg_deps = [
+   glib_dep,
++  gio_dep,
+   gobject_dep,
+ ]
+ 
+-- 
+2.28.0
+
+
+From 360a397c19046c6a914ee27e3e5104da3ad0c1c6 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 6 May 2020 22:12:46 +0200
+Subject: [PATCH 18/20] cogl/dma-buf: Add mmap/munmap helpers
+
+Avoids dealing directly with mmap() and munmap().
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit e05a1a6c0b2526146c85ec9c381bb2b49d19b4b2)
+---
+ cogl/cogl/cogl-dma-buf-handle.c | 41 +++++++++++++++++++++++++++++++++
+ cogl/cogl/cogl-dma-buf-handle.h |  9 ++++++++
+ 2 files changed, 50 insertions(+)
+
+diff --git a/cogl/cogl/cogl-dma-buf-handle.c b/cogl/cogl/cogl-dma-buf-handle.c
+index 7e86e2267b..9724ac9c95 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.c
++++ b/cogl/cogl/cogl-dma-buf-handle.c
+@@ -38,6 +38,7 @@
+ #include <gio/gio.h>
+ #include <linux/dma-buf.h>
+ #include <sys/ioctl.h>
++#include <sys/mman.h>
+ #include <unistd.h>
+ 
+ struct _CoglDmaBufHandle
+@@ -147,6 +148,46 @@ cogl_dma_buf_handle_sync_read_end (CoglDmaBufHandle  *dmabuf_handle,
+   return sync_read (dmabuf_handle, DMA_BUF_SYNC_END, error);
+ }
+ 
++gpointer
++cogl_dma_buf_handle_mmap (CoglDmaBufHandle  *dmabuf_handle,
++                          GError           **error)
++{
++  size_t size;
++  gpointer data;
++
++  size = dmabuf_handle->height * dmabuf_handle->stride;
++
++  data = mmap (NULL, size, PROT_READ, MAP_PRIVATE,
++               dmabuf_handle->dmabuf_fd,
++               dmabuf_handle->offset);
++  if (data == MAP_FAILED)
++    {
++      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                   "mmap failed: %s", g_strerror (errno));
++      return NULL;
++    }
++
++  return data;
++}
++
++gboolean
++cogl_dma_buf_handle_munmap (CoglDmaBufHandle  *dmabuf_handle,
++                            gpointer           data,
++                            GError           **error)
++{
++  size_t size;
++
++  size = dmabuf_handle->height * dmabuf_handle->stride;
++  if (munmap (data, size) != 0)
++    {
++      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
++                   "munmap failed: %s", g_strerror (errno));
++      return FALSE;
++    }
++
++  return TRUE;
++}
++
+ CoglFramebuffer *
+ cogl_dma_buf_handle_get_framebuffer (CoglDmaBufHandle *dmabuf_handle)
+ {
+diff --git a/cogl/cogl/cogl-dma-buf-handle.h b/cogl/cogl/cogl-dma-buf-handle.h
+index 63c5bab7b7..08f307c1db 100644
+--- a/cogl/cogl/cogl-dma-buf-handle.h
++++ b/cogl/cogl/cogl-dma-buf-handle.h
+@@ -71,6 +71,15 @@ gboolean
+ cogl_dma_buf_handle_sync_read_end (CoglDmaBufHandle  *dmabuf_handle,
+                                    GError           **error);
+ 
++gpointer
++cogl_dma_buf_handle_mmap (CoglDmaBufHandle  *dmabuf_handle,
++                          GError           **error);
++
++gboolean
++cogl_dma_buf_handle_munmap (CoglDmaBufHandle  *dmabuf_handle,
++                            gpointer           data,
++                            GError           **error);
++
+ /**
+  * cogl_dma_buf_handle_get_framebuffer: (skip)
+  *
+-- 
+2.28.0
+
+
+From ff8a80137047a91ed27d90467b004d691428bac4 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
+Date: Wed, 6 May 2020 22:14:17 +0200
+Subject: [PATCH 19/20] clutter/stage-view: Add tile based shadow damage
+ detection
+
+Compare, tile by tile, whether actual damage actually changed any
+pixels. While this requires mmap():ing DMA buffers and comparing their
+content, we should only ever use shadow buffers when we're using the
+software renderer, meaning mmap() is cheap as it doesn't involve any
+downloading.
+
+This works by making the shadow framebuffer double buffered, while
+keeping track of damage history. When we're about to swap the onscreen
+buffer, we compare what part of the posted damage actually changed,
+records that into a damage history, then given the onscreen buffer age,
+collect all actual damage for that age. The intersection of these tiles,
+and the actual damage, is then used when blitting the shadow buffer to
+the onscreen framebuffer.
+
+Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/1157
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 068385df3a0cf545e5110378b59db56cbd1bdef3)
+---
+ clutter/clutter/clutter-private.h    |   3 +
+ clutter/clutter/clutter-stage-view.c | 472 +++++++++++++++++++++++++--
+ clutter/clutter/clutter-util.c       |  22 ++
+ 3 files changed, 465 insertions(+), 32 deletions(-)
+
+diff --git a/clutter/clutter/clutter-private.h b/clutter/clutter/clutter-private.h
+index a5cd1fa197..5a0fed85c9 100644
+--- a/clutter/clutter/clutter-private.h
++++ b/clutter/clutter/clutter-private.h
+@@ -265,6 +265,9 @@ gboolean _clutter_util_rectangle_intersection (const cairo_rectangle_int_t *src1
+                                                const cairo_rectangle_int_t *src2,
+                                                cairo_rectangle_int_t       *dest);
+ 
++gboolean _clutter_util_rectangle_contains (const cairo_rectangle_int_t *src1,
++                                           const cairo_rectangle_int_t *src2);
++
+ 
+ struct _ClutterVertex4
+ {
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index 21ab02c97b..5e5966d06e 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -17,6 +17,7 @@
+ 
+ #include "clutter-build-config.h"
+ 
++#include "clutter/clutter-damage-history.h"
+ #include "clutter/clutter-stage-view.h"
+ #include "clutter/clutter-stage-view-private.h"
+ #include "clutter/clutter-private.h"
+@@ -53,6 +54,12 @@ typedef struct _ClutterStageViewPrivate
+ 
+   gboolean use_shadowfb;
+   struct {
++    struct {
++      CoglDmaBufHandle *handles[2];
++      int current_idx;
++      ClutterDamageHistory *damage_history;
++    } dma_buf;
++
+     CoglOffscreen *framebuffer;
+   } shadow;
+ 
+@@ -254,6 +261,66 @@ paint_transformed_framebuffer (ClutterStageView            *view,
+   cogl_framebuffer_pop_matrix (dst_framebuffer);
+ }
+ 
++static gboolean
++is_shadowfb_double_buffered (ClutterStageView *view)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++
++  return priv->shadow.dma_buf.handles[0] && priv->shadow.dma_buf.handles[1];
++}
++
++static gboolean
++init_dma_buf_shadowfbs (ClutterStageView  *view,
++                        CoglContext       *cogl_context,
++                        int                width,
++                        int                height,
++                        GError           **error)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context);
++  CoglFramebuffer *initial_shadowfb;
++
++  if (!cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE))
++    {
++      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
++                   "Buffer age not supported");
++      return FALSE;
++    }
++
++  if (!cogl_is_onscreen (priv->framebuffer))
++    {
++      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
++                   "Tried to use shadow buffer without onscreen");
++      return FALSE;
++    }
++
++  priv->shadow.dma_buf.handles[0] = cogl_renderer_create_dma_buf (cogl_renderer,
++                                                                  width, height,
++                                                                  error);
++  if (!priv->shadow.dma_buf.handles[0])
++    return FALSE;
++
++  priv->shadow.dma_buf.handles[1] = cogl_renderer_create_dma_buf (cogl_renderer,
++                                                                  width, height,
++                                                                  error);
++  if (!priv->shadow.dma_buf.handles[1])
++    {
++      g_clear_pointer (&priv->shadow.dma_buf.handles[0],
++                       cogl_dma_buf_handle_free);
++      return FALSE;
++    }
++
++  priv->shadow.dma_buf.damage_history = clutter_damage_history_new ();
++
++  initial_shadowfb =
++    cogl_dma_buf_handle_get_framebuffer (priv->shadow.dma_buf.handles[0]);
++  priv->shadow.framebuffer = cogl_object_ref (initial_shadowfb);
++
++  return TRUE;
++}
++
+ static CoglOffscreen *
+ create_offscreen_framebuffer (CoglContext  *context,
+                               int           width,
+@@ -285,11 +352,11 @@ create_offscreen_framebuffer (CoglContext  *context,
+ }
+ 
+ static gboolean
+-init_offscreen_shadowfb (ClutterStageView  *view,
+-                         CoglContext       *cogl_context,
+-                         int                width,
+-                         int                height,
+-                         GError           **error)
++init_fallback_shadowfb (ClutterStageView  *view,
++                        CoglContext       *cogl_context,
++                        int                width,
++                        int                height,
++                        GError           **error)
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+@@ -317,7 +384,17 @@ init_shadowfb (ClutterStageView *view)
+   height = cogl_framebuffer_get_height (priv->framebuffer);
+   cogl_context = cogl_framebuffer_get_context (priv->framebuffer);
+ 
+-  if (!init_offscreen_shadowfb (view, cogl_context, width, height, &error))
++  if (init_dma_buf_shadowfbs (view, cogl_context, width, height, &error))
++    {
++      g_message ("Initialized double buffered shadow fb for %s", priv->name);
++      return;
++    }
++
++  g_warning ("Failed to initialize double buffered shadow fb for %s: %s",
++             priv->name, error->message);
++  g_clear_error (&error);
++
++  if (!init_fallback_shadowfb (view, cogl_context, width, height, &error))
+     {
+       g_warning ("Failed to initialize single buffered shadow fb for %s: %s",
+                  priv->name, error->message);
+@@ -358,44 +435,298 @@ clutter_stage_view_blit_offscreen (ClutterStageView            *view,
+     }
+ }
+ 
+-void
+-clutter_stage_view_before_swap_buffer (ClutterStageView            *view,
+-                                       const cairo_rectangle_int_t *swap_region)
++static gboolean
++is_tile_dirty (cairo_rectangle_int_t *tile,
++               uint8_t               *current_data,
++               uint8_t               *prev_data,
++               int                    bpp,
++               int                    stride)
++{
++  int y;
++
++  for (y = tile->y; y < tile->y + tile->height; y++)
++    {
++      if (memcmp (prev_data + y * stride + tile->x * bpp,
++                  current_data + y * stride + tile->x * bpp,
++                  tile->width * bpp) != 0)
++        return TRUE;
++    }
++
++  return FALSE;
++}
++
++static int
++flip_dma_buf_idx (int idx)
++{
++  return (idx + 1) % 2;
++}
++
++static cairo_region_t *
++find_damaged_tiles (ClutterStageView      *view,
++                    const cairo_region_t  *damage_region,
++                    GError               **error)
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+-  g_autoptr (GError) error = NULL;
++  cairo_region_t *tile_damage_region;
++  cairo_rectangle_int_t damage_extents;
++  cairo_rectangle_int_t fb_rect;
++  int prev_dma_buf_idx;
++  CoglDmaBufHandle *prev_dma_buf_handle;
++  uint8_t *prev_data;
++  int current_dma_buf_idx;
++  CoglDmaBufHandle *current_dma_buf_handle;
++  uint8_t *current_data;
++  int width, height, stride, bpp;
++  int tile_x_min, tile_x_max;
++  int tile_y_min, tile_y_max;
++  int tile_x, tile_y;
++  const int tile_size = 16;
++
++  prev_dma_buf_idx = flip_dma_buf_idx (priv->shadow.dma_buf.current_idx);
++  prev_dma_buf_handle = priv->shadow.dma_buf.handles[prev_dma_buf_idx];
++
++  current_dma_buf_idx = priv->shadow.dma_buf.current_idx;
++  current_dma_buf_handle = priv->shadow.dma_buf.handles[current_dma_buf_idx];
++
++  width = cogl_dma_buf_handle_get_width (current_dma_buf_handle);
++  height = cogl_dma_buf_handle_get_height (current_dma_buf_handle);
++  stride = cogl_dma_buf_handle_get_stride (current_dma_buf_handle);
++  bpp = cogl_dma_buf_handle_get_bpp (current_dma_buf_handle);
++
++  cogl_framebuffer_finish (priv->shadow.framebuffer);
++
++  if (!cogl_dma_buf_handle_sync_read_start (prev_dma_buf_handle, error))
++    return NULL;
++
++  if (!cogl_dma_buf_handle_sync_read_start (current_dma_buf_handle, error))
++    goto err_sync_read_current;
++
++  prev_data = cogl_dma_buf_handle_mmap (prev_dma_buf_handle, error);
++  if (!prev_data)
++    goto err_mmap_prev;
++  current_data = cogl_dma_buf_handle_mmap (current_dma_buf_handle, error);
++  if (!current_data)
++    goto err_mmap_current;
++
++  fb_rect = (cairo_rectangle_int_t) {
++    .width = width,
++    .height = height,
++  };
++
++  cairo_region_get_extents (damage_region, &damage_extents);
++
++  tile_x_min = damage_extents.x / tile_size;
++  tile_x_max = ((damage_extents.x + damage_extents.width + tile_size - 1) /
++                tile_size);
++  tile_y_min = damage_extents.y / tile_size;
++  tile_y_max = ((damage_extents.y + damage_extents.height + tile_size - 1) /
++                tile_size);
++
++  tile_damage_region = cairo_region_create ();
++
++  for (tile_y = tile_y_min; tile_y <= tile_y_max; tile_y++)
++    {
++      for (tile_x = tile_x_min; tile_x <= tile_x_max; tile_x++)
++        {
++          cairo_rectangle_int_t tile = {
++            .x = tile_x * tile_size,
++            .y = tile_y * tile_size,
++            .width = tile_size,
++            .height = tile_size,
++          };
+ 
+-  if (!priv->shadow.framebuffer)
+-    return;
++          if (cairo_region_contains_rectangle (damage_region, &tile) ==
++              CAIRO_REGION_OVERLAP_OUT)
++            continue;
+ 
+-  if (swap_region->width == 0 || swap_region->height == 0)
++          _clutter_util_rectangle_intersection (&tile, &fb_rect, &tile);
++
++          if (is_tile_dirty (&tile, current_data, prev_data, bpp, stride))
++            cairo_region_union_rectangle (tile_damage_region, &tile);
++        }
++    }
++
++  if (!cogl_dma_buf_handle_sync_read_end (prev_dma_buf_handle, error))
+     {
+-      int width, height;
++      g_warning ("Failed to end DMA buffer read synchronization: %s",
++                 (*error)->message);
++      g_clear_error (error);
++    }
+ 
+-      width = cogl_framebuffer_get_width (priv->framebuffer);
+-      height = cogl_framebuffer_get_height (priv->framebuffer);
+-      if (!cogl_blit_framebuffer (priv->shadow.framebuffer,
+-                                  priv->framebuffer,
+-                                  0, 0,
+-                                  0, 0,
+-                                  width, height,
+-                                  &error))
+-        g_warning ("Failed to blit shadow buffer: %s", error->message);
++  if (!cogl_dma_buf_handle_sync_read_end (current_dma_buf_handle, error))
++    {
++      g_warning ("Failed to end DMA buffer read synchronization: %s",
++                 (*error)->message);
++      g_clear_error (error);
++    }
++
++  cogl_dma_buf_handle_munmap (prev_dma_buf_handle, prev_data, NULL);
++  cogl_dma_buf_handle_munmap (current_dma_buf_handle, current_data, NULL);
++
++  cairo_region_intersect (tile_damage_region, damage_region);
++
++  return tile_damage_region;
++
++err_mmap_current:
++  cogl_dma_buf_handle_munmap (prev_dma_buf_handle, prev_data, NULL);
++
++err_mmap_prev:
++  cogl_dma_buf_handle_sync_read_end (current_dma_buf_handle, NULL);
++
++err_sync_read_current:
++  cogl_dma_buf_handle_sync_read_end (prev_dma_buf_handle, NULL);
++
++  return NULL;
++}
++
++static void
++swap_dma_buf_framebuffer (ClutterStageView *view)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  int next_idx;
++  CoglDmaBufHandle *next_dma_buf_handle;
++  CoglOffscreen *next_framebuffer;
++
++  next_idx = ((priv->shadow.dma_buf.current_idx + 1) %
++              G_N_ELEMENTS (priv->shadow.dma_buf.handles));
++  priv->shadow.dma_buf.current_idx = next_idx;
++
++  next_dma_buf_handle = priv->shadow.dma_buf.handles[next_idx];
++  next_framebuffer =
++    cogl_dma_buf_handle_get_framebuffer (next_dma_buf_handle);
++  cogl_clear_object (&priv->shadow.framebuffer);
++  priv->shadow.framebuffer = cogl_object_ref (next_framebuffer);
++}
++
++static void
++copy_shadowfb_to_onscreen (ClutterStageView            *view,
++                           const cairo_rectangle_int_t *swap_region)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++  ClutterDamageHistory *damage_history = priv->shadow.dma_buf.damage_history;
++  cairo_region_t *damage_region;
++  int age;
++  int i;
++
++  if (swap_region->width == 0 || swap_region->height == 0)
++    {
++      cairo_rectangle_int_t full_damage = {
++        .width = cogl_framebuffer_get_width (priv->framebuffer),
++        .height = cogl_framebuffer_get_height (priv->framebuffer),
++      };
++      damage_region = cairo_region_create_rectangle (&full_damage);
+     }
+   else
+     {
++      damage_region = cairo_region_create_rectangle (swap_region);
++    }
++
++  if (is_shadowfb_double_buffered (view))
++    {
++      CoglOnscreen *onscreen = COGL_ONSCREEN (priv->framebuffer);
++      cairo_region_t *changed_region;
++
++      if (cogl_onscreen_get_frame_counter (onscreen) >= 1)
++        {
++          g_autoptr (GError) error = NULL;
++
++          changed_region = find_damaged_tiles (view, damage_region, &error);
++          if (!changed_region)
++            {
++              int other_dma_buf_idx;
++
++              g_warning ("Disabling actual damage detection: %s",
++                         error->message);
++
++              other_dma_buf_idx =
++                flip_dma_buf_idx (priv->shadow.dma_buf.current_idx);
++              g_clear_pointer (&priv->shadow.dma_buf.handles[other_dma_buf_idx],
++                               cogl_dma_buf_handle_free);
++            }
++        }
++      else
++        {
++          changed_region = cairo_region_copy (damage_region);
++        }
++
++      if (changed_region)
++        {
++          cairo_rectangle_int_t changed_extents;
++          int buffer_age;
++
++          cairo_region_get_extents (changed_region, &changed_extents);
++          clutter_damage_history_record (damage_history, &changed_extents);
++
++          buffer_age = cogl_onscreen_get_buffer_age (onscreen);
++          if (clutter_damage_history_is_age_valid (damage_history, buffer_age))
++            {
++              for (age = 1; age <= buffer_age; age++)
++                {
++                  const cairo_rectangle_int_t *old_damage;
++
++                  old_damage = clutter_damage_history_lookup (damage_history, age);
++                  cairo_region_union_rectangle (changed_region, old_damage);
++                }
++
++              cairo_region_destroy (damage_region);
++              damage_region = g_steal_pointer (&changed_region);
++            }
++          else
++            {
++              cairo_region_destroy (changed_region);
++            }
++
++          clutter_damage_history_step (damage_history);
++        }
++    }
++
++  if (0)
++    {
++      CoglColor clear_color;
++
++      cogl_color_init_from_4ub (&clear_color,
++                                0, 0, 0, 0);
++      cogl_framebuffer_clear (priv->framebuffer, COGL_BUFFER_BIT_COLOR, &clear_color);
++    }
++
++  for (i = 0; i < cairo_region_num_rectangles (damage_region); i++)
++    {
++      g_autoptr (GError) error = NULL;
++      cairo_rectangle_int_t rect;
++
++      cairo_region_get_rectangle (damage_region, i, &rect);
++
+       if (!cogl_blit_framebuffer (priv->shadow.framebuffer,
+                                   priv->framebuffer,
+-                                  swap_region->x, swap_region->y,
+-                                  swap_region->x, swap_region->y,
+-                                  swap_region->width, swap_region->height,
++                                  rect.x, rect.y,
++                                  rect.x, rect.y,
++                                  rect.width, rect.height,
+                                   &error))
+         {
+           g_warning ("Failed to blit shadow buffer: %s", error->message);
++          cairo_region_destroy (damage_region);
+           return;
+         }
+     }
++
++  cairo_region_destroy (damage_region);
++
++  if (is_shadowfb_double_buffered (view))
++    swap_dma_buf_framebuffer (view);
++}
++
++void
++clutter_stage_view_before_swap_buffer (ClutterStageView            *view,
++                                       const cairo_rectangle_int_t *swap_region)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++
++  if (priv->shadow.framebuffer)
++    copy_shadowfb_to_onscreen (view, swap_region);
+ }
+ 
+ float
+@@ -407,6 +738,47 @@ clutter_stage_view_get_scale (ClutterStageView *view)
+   return priv->scale;
+ }
+ 
++typedef void (*FrontBufferCallback) (CoglFramebuffer *framebuffer,
++                                     gconstpointer    user_data);
++
++static void
++clutter_stage_view_foreach_front_buffer (ClutterStageView    *view,
++                                         FrontBufferCallback  callback,
++                                         gconstpointer        user_data)
++{
++  ClutterStageViewPrivate *priv =
++    clutter_stage_view_get_instance_private (view);
++
++  if (priv->offscreen)
++    {
++      callback (priv->offscreen, user_data);
++    }
++  else if (priv->shadow.framebuffer)
++    {
++      if (is_shadowfb_double_buffered (view))
++        {
++          int i;
++
++          for (i = 0; i < G_N_ELEMENTS (priv->shadow.dma_buf.handles); i++)
++            {
++              CoglDmaBufHandle *handle = priv->shadow.dma_buf.handles[i];
++              CoglFramebuffer *framebuffer =
++                cogl_dma_buf_handle_get_framebuffer (handle);
++
++              callback (framebuffer, user_data);
++            }
++        }
++      else
++        {
++          callback (priv->shadow.framebuffer, user_data);
++        }
++    }
++  else
++    {
++      callback (priv->framebuffer, user_data);
++    }
++}
++
+ gboolean
+ clutter_stage_view_is_dirty_viewport (ClutterStageView *view)
+ {
+@@ -425,6 +797,19 @@ clutter_stage_view_invalidate_viewport (ClutterStageView *view)
+   priv->dirty_viewport = TRUE;
+ }
+ 
++static void
++set_framebuffer_viewport (CoglFramebuffer *framebuffer,
++                          gconstpointer    user_data)
++{
++  const ClutterRect *rect = user_data;
++
++  cogl_framebuffer_set_viewport (framebuffer,
++                                 rect->origin.x,
++                                 rect->origin.y,
++                                 rect->size.width,
++                                 rect->size.height);
++}
++
+ void
+ clutter_stage_view_set_viewport (ClutterStageView *view,
+                                  float             x,
+@@ -434,11 +819,17 @@ clutter_stage_view_set_viewport (ClutterStageView *view,
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+-  CoglFramebuffer *framebuffer;
++  ClutterRect rect;
+ 
+   priv->dirty_viewport = FALSE;
+-  framebuffer = clutter_stage_view_get_framebuffer (view);
+-  cogl_framebuffer_set_viewport (framebuffer, x, y, width, height);
++
++  rect = (ClutterRect) {
++    .origin = { .x = x, .y = y },
++    .size = { .width = width, .height = height },
++  };
++  clutter_stage_view_foreach_front_buffer (view,
++                                           set_framebuffer_viewport,
++                                           &rect);
+ }
+ 
+ gboolean
+@@ -450,6 +841,13 @@ clutter_stage_view_is_dirty_projection (ClutterStageView *view)
+   return priv->dirty_projection;
+ }
+ 
++static void
++set_framebuffer_projection_matrix (CoglFramebuffer *framebuffer,
++                                   gconstpointer    user_data)
++{
++  cogl_framebuffer_set_projection_matrix (framebuffer, user_data);
++}
++
+ void
+ clutter_stage_view_invalidate_projection (ClutterStageView *view)
+ {
+@@ -465,11 +863,11 @@ clutter_stage_view_set_projection (ClutterStageView *view,
+ {
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
+-  CoglFramebuffer *framebuffer;
+ 
+   priv->dirty_projection = FALSE;
+-  framebuffer = clutter_stage_view_get_framebuffer (view);
+-  cogl_framebuffer_set_projection_matrix (framebuffer, matrix);
++  clutter_stage_view_foreach_front_buffer (view,
++                                           set_framebuffer_projection_matrix,
++                                           matrix);
+ }
+ 
+ void
+@@ -593,10 +991,20 @@ clutter_stage_view_dispose (GObject *object)
+   ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
+   ClutterStageViewPrivate *priv =
+     clutter_stage_view_get_instance_private (view);
++  int i;
+ 
+   g_clear_pointer (&priv->name, g_free);
+   g_clear_pointer (&priv->framebuffer, cogl_object_unref);
++
+   g_clear_pointer (&priv->shadow.framebuffer, cogl_object_unref);
++  for (i = 0; i < G_N_ELEMENTS (priv->shadow.dma_buf.handles); i++)
++    {
++      g_clear_pointer (&priv->shadow.dma_buf.handles[i],
++                       cogl_dma_buf_handle_free);
++    }
++  g_clear_pointer (&priv->shadow.dma_buf.damage_history,
++                   clutter_damage_history_free);
++
+   g_clear_pointer (&priv->offscreen, cogl_object_unref);
+   g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
+ 
+diff --git a/clutter/clutter/clutter-util.c b/clutter/clutter/clutter-util.c
+index ed52b69774..834adae39a 100644
+--- a/clutter/clutter/clutter-util.c
++++ b/clutter/clutter/clutter-util.c
+@@ -210,6 +210,28 @@ _clutter_util_rectangle_intersection (const cairo_rectangle_int_t *src1,
+     }
+ }
+ 
++gboolean
++_clutter_util_rectangle_contains (const cairo_rectangle_int_t *src1,
++                                  const cairo_rectangle_int_t *src2)
++{
++  int x1, y1, x2, y2;
++
++  x1 = MAX (src1->x, src2->x);
++  y1 = MAX (src1->y, src2->y);
++
++  x2 = MIN (src1->x + (int) src1->width,  src2->x + (int) src2->width);
++  y2 = MIN (src1->y + (int) src1->height, src2->y + (int) src2->height);
++
++  if (x1 >= x2 || y1 >= y2)
++    {
++      return FALSE;
++    }
++  else
++    {
++      return TRUE;
++    }
++}
++
+ float
+ _clutter_util_matrix_determinant (const ClutterMatrix *matrix)
+ {
+-- 
+2.28.0
+
+
+From 9968d4aeefc2c47a63e12f977dad031672a63abe Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Sat, 7 Mar 2020 20:29:09 +0100
+Subject: [PATCH 20/20] clutter/stage-cogl: Use view fb instead of onscreen fb
+ for debug-drawing
+
+We need to use the framebuffer of the view instead of the onscreen
+framebuffer when painting the damage region, otherwise the redraw clips
+on rotated monitors won't be shown correctly.
+
+https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1237
+(cherry picked from commit 8e1bd64e05c3098fcce4f916f9e4468decb8f30c)
+---
+ clutter/clutter/cogl/clutter-stage-cogl.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c
+index 11273ec894..3f1f609c4e 100644
+--- a/clutter/clutter/cogl/clutter-stage-cogl.c
++++ b/clutter/clutter/cogl/clutter-stage-cogl.c
+@@ -353,7 +353,7 @@ paint_damage_region (ClutterStageWindow    *stage_window,
+                      ClutterStageView      *view,
+                      cairo_rectangle_int_t *swap_region)
+ {
+-  CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (view);
++  CoglFramebuffer *framebuffer = clutter_stage_view_get_framebuffer (view);
+   CoglContext *ctx = cogl_framebuffer_get_context (framebuffer);
+   static CoglPipeline *overlay_blue = NULL;
+   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
+-- 
+2.28.0
+
diff --git a/SPECS/mutter.spec b/SPECS/mutter.spec
index dd96fb4..9d1f1fd 100644
--- a/SPECS/mutter.spec
+++ b/SPECS/mutter.spec
@@ -8,7 +8,7 @@
 
 Name:          mutter
 Version:       3.32.2
-Release:       48%{?dist}
+Release:       49%{?dist}
 Summary:       Window and compositing manager based on Clutter
 
 License:       GPLv2+
@@ -159,6 +159,9 @@ Patch501: 0001-window-actor-Don-t-show-actor-until-meta_window_acto.patch
 Patch502: 0001-monitor-manager-kms-Trigger-hotplug-processing-on-gp.patch
 Patch503: 0002-gpu-kms-Reset-CRTC-mode-and-output-list-if-no-resour.patch
 
+# Add tile based shadow buffer damage tracking (#1670273)
+Patch504: shadow-buffer-tile-damage.patch
+
 BuildRequires: chrpath
 BuildRequires: pango-devel
 BuildRequires: startup-notification-devel
@@ -300,9 +303,9 @@ desktop-file-validate %{buildroot}/%{_datadir}/applications/%{name}.desktop
 %{_datadir}/mutter-%{mutter_api_version}/tests
 
 %changelog
-* Mon Sep 21 2020 Jonas Ådahl <jadahl@redhat.com> - 3.32.2-48
-- Fix GLX stereo buffer rebase error
-  Resolves: #1880339
+* 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