[gtk/wip/chergert/for-main] macos: fix cairo renderer with double buffering



commit 4302e39b398c60f13e3461cab690a01046ddcfbd
Author: Christian Hergert <christian hergert me>
Date:   Mon Feb 28 01:42:48 2022 -0800

    macos: fix cairo renderer with double buffering
    
    If we are double buffering surfaces with IOSurface then we need to copy
    the area that was damaged in the previous frame to the back buffer. This
    can be done with IOSurface but we need to hold the read-only lock so that
    we don't cause the underlying IOSurface contents to be invalidated.
    
    Additionally, since this is only used in the context of rendering to a
    GdkMacosSurface, we know the life-time of the cairo_surface_t and can
    simply lock/unlock the IOSurface buffer from begin_frame/end_frame to have
    the buffer flushing semantics we want.
    
    To ensure that we don't over damage, we store the damage in begin_frame
    (and copy it) and then subtract it from the next frames damage to determine
    the smallest amount we need to copy (taking scale factor into account).
    
    We don't care to modify the damage region to swapBuffers because they
    already have the right contents and could potentially fall into another
    tile anyway and we'd like to avoid damaging that.
    
    Fixes #4735

 gdk/macos/gdkmacosbuffer.c       |   2 +-
 gdk/macos/gdkmacoscairocontext.c | 120 ++++++++++++++++++++++++++++++---------
 2 files changed, 93 insertions(+), 29 deletions(-)
---
diff --git a/gdk/macos/gdkmacosbuffer.c b/gdk/macos/gdkmacosbuffer.c
index 46a0504461..eb8a719dbc 100644
--- a/gdk/macos/gdkmacosbuffer.c
+++ b/gdk/macos/gdkmacosbuffer.c
@@ -281,7 +281,7 @@ _gdk_macos_buffer_set_damage (GdkMacosBuffer *self,
     return;
 
   g_clear_pointer (&self->damage, cairo_region_destroy);
-  self->damage = cairo_region_reference (damage);
+  self->damage = cairo_region_copy (damage);
 }
 
 gpointer
diff --git a/gdk/macos/gdkmacoscairocontext.c b/gdk/macos/gdkmacoscairocontext.c
index 041f1193e6..31eecd5df6 100644
--- a/gdk/macos/gdkmacoscairocontext.c
+++ b/gdk/macos/gdkmacoscairocontext.c
@@ -42,19 +42,6 @@ struct _GdkMacosCairoContextClass
 
 G_DEFINE_TYPE (GdkMacosCairoContext, _gdk_macos_cairo_context, GDK_TYPE_CAIRO_CONTEXT)
 
-static const cairo_user_data_key_t buffer_key;
-
-static void
-unlock_buffer (gpointer data)
-{
-  GdkMacosBuffer *buffer = data;
-
-  g_assert (GDK_IS_MACOS_BUFFER (buffer));
-
-  _gdk_macos_buffer_unlock (buffer);
-  g_clear_object (&buffer);
-}
-
 static cairo_t *
 _gdk_macos_cairo_context_cairo_create (GdkCairoContext *cairo_context)
 {
@@ -106,12 +93,9 @@ _gdk_macos_cairo_context_cairo_create (GdkCairoContext *cairo_context)
                                                        stride);
   cairo_surface_set_device_scale (image_surface, scale, scale);
 
-  /* Lock the buffer so we can modify it safely */
-  _gdk_macos_buffer_lock (buffer);
-  cairo_surface_set_user_data (image_surface,
-                               &buffer_key,
-                               g_object_ref (buffer),
-                               unlock_buffer);
+  /* The buffer should already be locked at this point, and will
+   * be unlocked as part of end_frame.
+   */
 
   if (!(cr = cairo_create (image_surface)))
     goto failure;
@@ -158,6 +142,52 @@ failure:
   return cr;
 }
 
+static void
+copy_surface_data (GdkMacosBuffer       *from,
+                   GdkMacosBuffer       *to,
+                   const cairo_region_t *region,
+                   int                   scale)
+{
+  const guint8 *from_base;
+  guint8 *to_base;
+  guint from_stride;
+  guint to_stride;
+  guint n_rects;
+
+  g_assert (GDK_IS_MACOS_BUFFER (from));
+  g_assert (GDK_IS_MACOS_BUFFER (to));
+  g_assert (region != NULL);
+  g_assert (!cairo_region_is_empty (region));
+
+  from_base = _gdk_macos_buffer_get_data (from);
+  from_stride = _gdk_macos_buffer_get_stride (from);
+
+  to_base = _gdk_macos_buffer_get_data (to);
+  to_stride = _gdk_macos_buffer_get_stride (to);
+
+  n_rects = cairo_region_num_rectangles (region);
+
+  for (guint i = 0; i < n_rects; i++)
+    {
+      cairo_rectangle_int_t rect;
+      int y2;
+
+      cairo_region_get_rectangle (region, i, &rect);
+
+      rect.y *= scale;
+      rect.height *= scale;
+      rect.x *= scale;
+      rect.width *= scale;
+
+      y2 = rect.y + rect.height;
+
+      for (int y = rect.y; y < y2; y++)
+        memcpy (&to_base[y * to_stride + rect.x * 4],
+                &from_base[y * from_stride + rect.x * 4],
+                rect.width * 4);
+    }
+}
+
 static void
 _gdk_macos_cairo_context_begin_frame (GdkDrawContext *draw_context,
                                       gboolean        prefers_high_depth,
@@ -165,34 +195,68 @@ _gdk_macos_cairo_context_begin_frame (GdkDrawContext *draw_context,
 {
   GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
   GdkMacosBuffer *buffer;
-  GdkSurface *surface;
+  GdkMacosSurface *surface;
 
   g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
 
   [CATransaction begin];
   [CATransaction setDisableActions:YES];
 
-  surface = gdk_draw_context_get_surface (draw_context);
-  buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface));
+  surface = GDK_MACOS_SURFACE (gdk_draw_context_get_surface (draw_context));
+  buffer = _gdk_macos_surface_get_buffer (surface);
 
   _gdk_macos_buffer_set_damage (buffer, region);
   _gdk_macos_buffer_set_flipped (buffer, FALSE);
+
+  _gdk_macos_buffer_lock (buffer);
+
+  /* If there is damage that was on the previous frame that is not on
+   * this frame, we need to copy that rendered region over to the back
+   * buffer so that when swapping buffers, we still have that content.
+   * This is done with a read-only lock on the IOSurface to avoid
+   * invalidating the buffer contents.
+   */
+  if (surface->front != NULL)
+    {
+      const cairo_region_t *previous = _gdk_macos_buffer_get_damage (surface->front);
+
+      if (previous != NULL)
+        {
+          cairo_region_t *copy;
+
+          copy = cairo_region_copy (previous);
+          cairo_region_subtract (copy, region);
+
+          if (!cairo_region_is_empty (copy))
+            {
+              int scale = gdk_surface_get_scale_factor (GDK_SURFACE (surface));
+
+              _gdk_macos_buffer_read_lock (surface->front);
+              copy_surface_data (surface->front, buffer, copy, scale);
+              _gdk_macos_buffer_read_unlock (surface->front);
+            }
+
+          cairo_region_destroy (copy);
+        }
+    }
 }
 
 static void
 _gdk_macos_cairo_context_end_frame (GdkDrawContext *draw_context,
                                     cairo_region_t *painted)
 {
+  GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context;
+  GdkMacosSurface *surface;
   GdkMacosBuffer *buffer;
-  GdkSurface *surface;
 
-  g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (draw_context));
+  g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self));
 
-  surface = gdk_draw_context_get_surface (draw_context);
-  buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface));
+  surface = GDK_MACOS_SURFACE (gdk_draw_context_get_surface (draw_context));
+  buffer = _gdk_macos_surface_get_buffer (surface);
+
+  _gdk_macos_buffer_unlock (buffer);
 
-  _gdk_macos_surface_swap_buffers (GDK_MACOS_SURFACE (surface), painted);
-  _gdk_macos_buffer_set_damage (buffer, NULL);
+  _gdk_macos_surface_swap_buffers (surface, painted);
 
   [CATransaction commit];
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]