mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-16 21:50:34 +00:00
wayland: stage uncommitted changes to dedicated buffer
Right now we use one buffer for both staged changes (freshly painted changes waiting for the frame clock to send to the compositor) and committed changes (changes actively being read by the compositor process). This creates a problem in the event we need to stage updates at the same time the compositor is processing committed updates: we can't change what the compositor is actively processing. The current solution for handling this contention is to allocate a temporary buffer on the spot at the time the updates are staged, and to copy that buffer back to the shared buffer later. The problem, though, is that the copy to the shared buffer currently happens as soon as the updates are finished being staged, not when the shared buffer is done being processed by the compositor. In order to address that problem, this commit changes the code to always stage changes to a dedicated staging buffer. The staging buffer is used exclusively by the client until the client is done with it, and then once that staging buffer is committed, the client never writes to that buffer again. If the client needs to stage new updates, it allocates a brand new staging buffer, draws to it, and back fills the undrawn parts of the buffer from a copy of the contents of the committed buffer. As an optimization, the compositor has the option of releasing the committed buffer back to the client. If it does so before the client needs to stage new updates, then the client will reuse the buffer for staging future updates. This optimization prevents having to allocate a new staging buffer and the associated cost of back filling that new buffer with a readback of the committed buffer. https://bugzilla.gnome.org/show_bug.cgi?id=761312
This commit is contained in:
parent
40e91195ad
commit
c80dd54924
@ -119,7 +119,10 @@ struct _GdkWindowImplWayland
|
||||
GdkWindowTypeHint hint;
|
||||
GdkWindow *transient_for;
|
||||
|
||||
cairo_surface_t *cairo_surface;
|
||||
cairo_surface_t *staging_cairo_surface;
|
||||
cairo_surface_t *committed_cairo_surface;
|
||||
cairo_surface_t *backfill_cairo_surface;
|
||||
|
||||
int pending_buffer_offset_x;
|
||||
int pending_buffer_offset_y;
|
||||
|
||||
@ -153,6 +156,7 @@ struct _GdkWindowImplWayland
|
||||
|
||||
cairo_region_t *opaque_region;
|
||||
cairo_region_t *input_region;
|
||||
cairo_region_t *staged_updates_region;
|
||||
};
|
||||
|
||||
struct _GdkWindowImplWaylandClass
|
||||
@ -191,6 +195,20 @@ _gdk_wayland_screen_add_orphan_dialog (GdkWindow *window)
|
||||
orphan_dialogs = g_list_prepend (orphan_dialogs, window);
|
||||
}
|
||||
|
||||
static void
|
||||
drop_cairo_surfaces (GdkWindow *window)
|
||||
{
|
||||
GdkWindowImplWayland *impl = GDK_WINDOW_IMPL_WAYLAND (window->impl);
|
||||
|
||||
g_clear_pointer (&impl->staging_cairo_surface, cairo_surface_destroy);
|
||||
g_clear_pointer (&impl->backfill_cairo_surface, cairo_surface_destroy);
|
||||
|
||||
/* We nullify this so if a buffer release comes in later, we won't
|
||||
* try to reuse that buffer since it's no longer suitable
|
||||
*/
|
||||
impl->committed_cairo_surface = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* gdk_wayland_window_update_size:
|
||||
* @drawable: a #GdkDrawableImplWayland.
|
||||
@ -208,7 +226,7 @@ gdk_wayland_window_update_size (GdkWindow *window,
|
||||
GdkRectangle area;
|
||||
cairo_region_t *region;
|
||||
|
||||
g_clear_pointer (&impl->cairo_surface, cairo_surface_destroy);
|
||||
drop_cairo_surfaces (window);
|
||||
|
||||
window->width = width;
|
||||
window->height = height;
|
||||
@ -256,11 +274,11 @@ _gdk_wayland_screen_create_root_window (GdkScreen *screen,
|
||||
impl->scale = gdk_screen_get_monitor_scale_factor (screen, 0);
|
||||
|
||||
/* logical 1x1 fake buffer */
|
||||
impl->cairo_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
||||
impl->scale,
|
||||
impl->scale);
|
||||
impl->staging_cairo_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
||||
impl->scale,
|
||||
impl->scale);
|
||||
|
||||
cairo_surface_set_device_scale (impl->cairo_surface, impl->scale, impl->scale);
|
||||
cairo_surface_set_device_scale (impl->staging_cairo_surface, impl->scale, impl->scale);
|
||||
|
||||
window->window_type = GDK_WINDOW_ROOT;
|
||||
window->depth = 32;
|
||||
@ -337,6 +355,37 @@ fill_presentation_time_from_frame_time (GdkFrameTimings *timings,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
read_back_cairo_surface (GdkWindow *window)
|
||||
{
|
||||
GdkWindowImplWayland *impl = GDK_WINDOW_IMPL_WAYLAND (window->impl);
|
||||
cairo_t *cr;
|
||||
cairo_region_t *paint_region = NULL;
|
||||
|
||||
if (!impl->backfill_cairo_surface)
|
||||
goto out;
|
||||
|
||||
paint_region = cairo_region_copy (window->clip_region);
|
||||
cairo_region_subtract (paint_region, impl->staged_updates_region);
|
||||
|
||||
if (cairo_region_is_empty (paint_region))
|
||||
goto out;
|
||||
|
||||
cr = cairo_create (impl->staging_cairo_surface);
|
||||
cairo_set_source_surface (cr, impl->backfill_cairo_surface, 0, 0);
|
||||
gdk_cairo_region (cr, paint_region);
|
||||
cairo_clip (cr);
|
||||
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
|
||||
cairo_paint (cr);
|
||||
cairo_destroy (cr);
|
||||
cairo_surface_flush (impl->staging_cairo_surface);
|
||||
|
||||
out:
|
||||
g_clear_pointer (&paint_region, cairo_region_destroy);
|
||||
g_clear_pointer (&impl->staged_updates_region, cairo_region_destroy);
|
||||
g_clear_pointer (&impl->backfill_cairo_surface, cairo_surface_destroy);
|
||||
}
|
||||
|
||||
static void
|
||||
frame_callback (void *data,
|
||||
struct wl_callback *callback,
|
||||
@ -441,7 +490,21 @@ on_frame_clock_after_paint (GdkFrameClock *clock,
|
||||
wl_callback_add_listener (callback, &frame_listener, window);
|
||||
_gdk_frame_clock_freeze (clock);
|
||||
|
||||
/* Before we commit a new buffer, make sure we've backfilled
|
||||
* undrawn parts from any old committed buffer
|
||||
*/
|
||||
read_back_cairo_surface (window);
|
||||
|
||||
/* From this commit forward, we can't write to the buffer,
|
||||
* it's "live". In the future, if we need to stage more changes
|
||||
* we have to allocate a new staging buffer and draw to it instead.
|
||||
*
|
||||
* Our one saving grace is if the compositor releases the buffer
|
||||
* before we need to stage any changes, then we can take it back and
|
||||
* use it again.
|
||||
*/
|
||||
wl_surface_commit (impl->display_server.wl_surface);
|
||||
impl->committed_cairo_surface = g_steal_pointer (&impl->staging_cairo_surface);
|
||||
|
||||
g_signal_emit (impl, signals[COMMITTED], 0);
|
||||
}
|
||||
@ -562,11 +625,11 @@ gdk_wayland_window_attach_image (GdkWindow *window)
|
||||
if (GDK_WINDOW_DESTROYED (window))
|
||||
return;
|
||||
|
||||
g_assert (_gdk_wayland_is_shm_surface (impl->cairo_surface));
|
||||
g_assert (_gdk_wayland_is_shm_surface (impl->staging_cairo_surface));
|
||||
|
||||
/* Attach this new buffer to the surface */
|
||||
wl_surface_attach (impl->display_server.wl_surface,
|
||||
_gdk_wayland_shm_surface_get_wl_buffer (impl->cairo_surface),
|
||||
_gdk_wayland_shm_surface_get_wl_buffer (impl->staging_cairo_surface),
|
||||
impl->pending_buffer_offset_x,
|
||||
impl->pending_buffer_offset_y);
|
||||
impl->pending_buffer_offset_x = 0;
|
||||
@ -580,13 +643,50 @@ gdk_wayland_window_attach_image (GdkWindow *window)
|
||||
impl->pending_commit = TRUE;
|
||||
}
|
||||
|
||||
static const cairo_user_data_key_t gdk_wayland_window_cairo_key;
|
||||
|
||||
static void
|
||||
buffer_release_callback (void *_data,
|
||||
struct wl_buffer *wl_buffer)
|
||||
{
|
||||
cairo_surface_t *cairo_surface = _data;
|
||||
GdkWindowImplWayland *impl = cairo_surface_get_user_data (cairo_surface, &gdk_wayland_window_cairo_key);
|
||||
|
||||
cairo_surface_destroy (cairo_surface);
|
||||
g_return_if_fail (GDK_IS_WINDOW_IMPL_WAYLAND (impl));
|
||||
|
||||
/* The released buffer isn't the latest committed one, we have no further
|
||||
* use for it, so clean it up.
|
||||
*/
|
||||
if (impl->committed_cairo_surface != cairo_surface)
|
||||
{
|
||||
/* If this fails, then the surface buffer got reused before it was
|
||||
* released from the compositor
|
||||
*/
|
||||
g_warn_if_fail (impl->staging_cairo_surface != cairo_surface);
|
||||
|
||||
cairo_surface_destroy (cairo_surface);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we've staged updates into a new buffer before the release for this
|
||||
* buffer came in, then we can't reuse this buffer, so unref it. It may still
|
||||
* be alive as a readback buffer though.
|
||||
*/
|
||||
if (impl->staging_cairo_surface != NULL)
|
||||
{
|
||||
/* If this fails, then we've allocated a staging buffer prematurely.
|
||||
* We didn't have any screen updates.
|
||||
*/
|
||||
g_warn_if_fail (!cairo_region_is_empty (impl->staged_updates_region));
|
||||
|
||||
g_clear_pointer (&impl->committed_cairo_surface, cairo_surface_destroy);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Release came in, we haven't done any interim updates, so we can just use
|
||||
* the old committed buffer again.
|
||||
*/
|
||||
impl->staging_cairo_surface = g_steal_pointer (&impl->committed_cairo_surface);
|
||||
}
|
||||
|
||||
static const struct wl_buffer_listener buffer_listener = {
|
||||
@ -601,32 +701,41 @@ gdk_wayland_window_ensure_cairo_surface (GdkWindow *window)
|
||||
/* If we are drawing using OpenGL then we only need a logical 1x1 surface. */
|
||||
if (impl->display_server.egl_window)
|
||||
{
|
||||
if (impl->cairo_surface &&
|
||||
_gdk_wayland_is_shm_surface (impl->cairo_surface))
|
||||
cairo_surface_destroy (impl->cairo_surface);
|
||||
if (impl->staging_cairo_surface &&
|
||||
_gdk_wayland_is_shm_surface (impl->staging_cairo_surface))
|
||||
cairo_surface_destroy (impl->staging_cairo_surface);
|
||||
|
||||
impl->cairo_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
||||
impl->scale,
|
||||
impl->scale);
|
||||
cairo_surface_set_device_scale (impl->cairo_surface,
|
||||
impl->staging_cairo_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
||||
impl->scale,
|
||||
impl->scale);
|
||||
cairo_surface_set_device_scale (impl->staging_cairo_surface,
|
||||
impl->scale, impl->scale);
|
||||
}
|
||||
else if (!impl->cairo_surface)
|
||||
else if (!impl->staging_cairo_surface)
|
||||
{
|
||||
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (gdk_window_get_display (impl->wrapper));
|
||||
struct wl_buffer *buffer;
|
||||
|
||||
impl->cairo_surface = _gdk_wayland_display_create_shm_surface (display_wayland,
|
||||
impl->wrapper->width,
|
||||
impl->wrapper->height,
|
||||
impl->scale);
|
||||
buffer = _gdk_wayland_shm_surface_get_wl_buffer (impl->cairo_surface);
|
||||
wl_buffer_add_listener (buffer, &buffer_listener, impl->cairo_surface);
|
||||
impl->staging_cairo_surface = _gdk_wayland_display_create_shm_surface (display_wayland,
|
||||
impl->wrapper->width,
|
||||
impl->wrapper->height,
|
||||
impl->scale);
|
||||
cairo_surface_set_user_data (impl->staging_cairo_surface,
|
||||
&gdk_wayland_window_cairo_key,
|
||||
g_object_ref (impl),
|
||||
(cairo_destroy_func_t)
|
||||
g_object_unref);
|
||||
buffer = _gdk_wayland_shm_surface_get_wl_buffer (impl->staging_cairo_surface);
|
||||
wl_buffer_add_listener (buffer, &buffer_listener, impl->staging_cairo_surface);
|
||||
}
|
||||
}
|
||||
|
||||
/* Unlike other backends the Cairo surface is not just a cheap wrapper
|
||||
* around some other backing. It is the buffer itself.
|
||||
/* The cairo surface returned here uses a memory segment that's shared
|
||||
* with the display server. This is not a temporary buffer that gets
|
||||
* copied to the display server, but the actual buffer the display server
|
||||
* will ultimately end up sending to the GPU. At the time this happens
|
||||
* impl->committed_cairo_surface gets set to impl->staging_cairo_surface, and
|
||||
* impl->staging_cairo_surface gets nullified.
|
||||
*/
|
||||
static cairo_surface_t *
|
||||
gdk_wayland_window_ref_cairo_surface (GdkWindow *window)
|
||||
@ -638,9 +747,9 @@ gdk_wayland_window_ref_cairo_surface (GdkWindow *window)
|
||||
|
||||
gdk_wayland_window_ensure_cairo_surface (window);
|
||||
|
||||
cairo_surface_reference (impl->cairo_surface);
|
||||
cairo_surface_reference (impl->staging_cairo_surface);
|
||||
|
||||
return impl->cairo_surface;
|
||||
return impl->staging_cairo_surface;
|
||||
}
|
||||
|
||||
static cairo_surface_t *
|
||||
@ -671,6 +780,24 @@ gdk_window_impl_wayland_end_paint (GdkWindow *window)
|
||||
{
|
||||
gdk_wayland_window_attach_image (window);
|
||||
|
||||
/* If there's a committed buffer pending, then track which
|
||||
* updates are staged until the next frame, so we can back
|
||||
* fill the unstaged parts of the staging buffer with the
|
||||
* last frame.
|
||||
*/
|
||||
if (impl->committed_cairo_surface != NULL)
|
||||
{
|
||||
if (impl->staged_updates_region == NULL)
|
||||
{
|
||||
impl->staged_updates_region = cairo_region_copy (window->current_paint.region);
|
||||
impl->backfill_cairo_surface = cairo_surface_reference (impl->committed_cairo_surface);
|
||||
}
|
||||
else
|
||||
{
|
||||
cairo_region_union (impl->staged_updates_region, window->current_paint.region);
|
||||
}
|
||||
}
|
||||
|
||||
n = cairo_region_num_rectangles (window->current_paint.region);
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
@ -701,6 +828,7 @@ gdk_window_impl_wayland_finalize (GObject *object)
|
||||
|
||||
g_clear_pointer (&impl->opaque_region, cairo_region_destroy);
|
||||
g_clear_pointer (&impl->input_region, cairo_region_destroy);
|
||||
g_clear_pointer (&impl->staged_updates_region, cairo_region_destroy);
|
||||
|
||||
G_OBJECT_CLASS (_gdk_window_impl_wayland_parent_class)->finalize (object);
|
||||
}
|
||||
@ -1498,8 +1626,8 @@ gdk_wayland_window_show (GdkWindow *window,
|
||||
|
||||
_gdk_make_event (window, GDK_MAP, NULL, FALSE);
|
||||
|
||||
if (impl->cairo_surface &&
|
||||
_gdk_wayland_is_shm_surface (impl->cairo_surface))
|
||||
if (impl->staging_cairo_surface &&
|
||||
_gdk_wayland_is_shm_surface (impl->staging_cairo_surface))
|
||||
gdk_wayland_window_attach_image (window);
|
||||
}
|
||||
|
||||
@ -1843,8 +1971,6 @@ gdk_wayland_window_destroy (GdkWindow *window,
|
||||
gboolean recursing,
|
||||
gboolean foreign_destroy)
|
||||
{
|
||||
GdkWindowImplWayland *impl = GDK_WINDOW_IMPL_WAYLAND (window->impl);
|
||||
|
||||
g_return_if_fail (GDK_IS_WINDOW (window));
|
||||
|
||||
/* Wayland windows can't be externally destroyed; we may possibly
|
||||
@ -1853,8 +1979,7 @@ gdk_wayland_window_destroy (GdkWindow *window,
|
||||
g_return_if_fail (!foreign_destroy);
|
||||
|
||||
gdk_wayland_window_hide_surface (window);
|
||||
|
||||
g_clear_pointer (&impl->cairo_surface, cairo_surface_destroy);
|
||||
drop_cairo_surfaces (window);
|
||||
}
|
||||
|
||||
static void
|
||||
|
Loading…
Reference in New Issue
Block a user