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
This commit is contained in:
Christian Hergert
2022-02-28 01:42:48 -08:00
parent dbede0b115
commit fc4d36e50a
2 changed files with 93 additions and 29 deletions

View File

@@ -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

View File

@@ -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_surface_swap_buffers (GDK_MACOS_SURFACE (surface), painted);
_gdk_macos_buffer_set_damage (buffer, NULL);
_gdk_macos_buffer_unlock (buffer);
_gdk_macos_surface_swap_buffers (surface, painted);
[CATransaction commit];
}