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:
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user