[gtk/wip/chergert/for-main] macos: fix cairo renderer with double buffering
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/chergert/for-main] macos: fix cairo renderer with double buffering
- Date: Mon, 28 Feb 2022 09:43:31 +0000 (UTC)
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]