From fc5de438b5aa4155aea2e562964072997c7d3858 Mon Sep 17 00:00:00 2001 From: Rui Matos Date: Fri, 18 Apr 2014 20:21:20 +0200 Subject: [PATCH] compositor: Add support for GL_EXT_x11_sync_object If GL advertises this extension we'll use it to synchronize X with GL rendering instead of relying on the XSync() behavior with open source drivers. https://bugzilla.gnome.org/show_bug.cgi?id=728464 --- configure.ac | 5 + src/Makefile.am | 2 + src/compositor/compositor-private.h | 4 +- src/compositor/compositor.c | 58 +++- src/compositor/meta-sync-ring.c | 482 ++++++++++++++++++++++++++++ src/compositor/meta-sync-ring.h | 17 + 6 files changed, 556 insertions(+), 12 deletions(-) create mode 100644 src/compositor/meta-sync-ring.c create mode 100644 src/compositor/meta-sync-ring.h diff --git a/configure.ac b/configure.ac index b845f0191..23fe0da0d 100644 --- a/configure.ac +++ b/configure.ac @@ -324,6 +324,11 @@ fi GTK_DOC_CHECK([1.15], [--flavour no-tmpl]) +AC_CHECK_DECL([GL_EXT_x11_sync_object], + [], + [AC_MSG_ERROR([GL_EXT_x11_sync_object definition not found, please update your GL headers])], + [#include ]) + #### Warnings (last since -Werror can disturb other tests) # Stay command-line compatible with the gnome-common configure option. Here diff --git a/src/Makefile.am b/src/Makefile.am index 6779b4891..505e8287b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -118,6 +118,8 @@ libmutter_la_SOURCES = \ compositor/meta-surface-actor-wayland.h \ compositor/meta-stage.h \ compositor/meta-stage.c \ + compositor/meta-sync-ring.c \ + compositor/meta-sync-ring.h \ compositor/meta-texture-rectangle.c \ compositor/meta-texture-rectangle.h \ compositor/meta-texture-tower.c \ diff --git a/src/compositor/compositor-private.h b/src/compositor/compositor-private.h index a41f56365..24d75ee7b 100644 --- a/src/compositor/compositor-private.h +++ b/src/compositor/compositor-private.h @@ -15,7 +15,8 @@ struct _MetaCompositor { MetaDisplay *display; - guint repaint_func_id; + guint pre_paint_func_id; + guint post_paint_func_id; gint64 server_time_query_time; gint64 server_time_offset; @@ -40,6 +41,7 @@ struct _MetaCompositor MetaPluginManager *plugin_mgr; gboolean frame_has_updated_xsurfaces; + gboolean have_x11_sync_object; }; /* Wait 2ms after vblank before starting to draw next frame */ diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c index 736a0f388..42d3a0d7e 100644 --- a/src/compositor/compositor.c +++ b/src/compositor/compositor.c @@ -79,6 +79,7 @@ #include "frame.h" #include #include +#include "meta-sync-ring.h" #include "backends/meta-backend.h" #include "backends/x11/meta-backend-x11.h" @@ -136,7 +137,11 @@ meta_switch_workspace_completed (MetaCompositor *compositor) void meta_compositor_destroy (MetaCompositor *compositor) { - clutter_threads_remove_repaint_func (compositor->repaint_func_id); + clutter_threads_remove_repaint_func (compositor->pre_paint_func_id); + clutter_threads_remove_repaint_func (compositor->post_paint_func_id); + + if (compositor->have_x11_sync_object) + meta_sync_ring_destroy (); } static void @@ -571,6 +576,8 @@ meta_compositor_manage (MetaCompositor *compositor) * contents until we show the stage. */ XMapWindow (xdisplay, compositor->output); + + compositor->have_x11_sync_object = meta_sync_ring_init (display); } redirect_windows (display->screen); @@ -782,6 +789,10 @@ meta_compositor_process_event (MetaCompositor *compositor, if (!meta_is_wayland_compositor () && event->type == MapNotify) clutter_x11_handle_event (event); + if (compositor->have_x11_sync_object && + event->type == (compositor->display->xsync_event_base + XSyncAlarmNotify)) + meta_sync_ring_handle_event ((XSyncAlarmNotifyEvent *) event); + /* The above handling is basically just "observing" the events, so we return * FALSE to indicate that the event should not be filtered out; if we have * GTK+ windows in the same process, GTK+ needs the ConfigureNotify event, for example. @@ -1128,7 +1139,7 @@ frame_callback (CoglOnscreen *onscreen, } static gboolean -meta_repaint_func (gpointer data) +meta_pre_paint_func (gpointer data) { GList *l; MetaWindowActor *top_window; @@ -1161,10 +1172,12 @@ meta_repaint_func (gpointer data) { /* We need to make sure that any X drawing that happens before * the XDamageSubtract() for each window above is visible to - * subsequent GL rendering; the only standardized way to do this - * is EXT_x11_sync_object, which isn't yet widely available. For - * now, we count on details of Xorg and the open source drivers, - * and hope for the best otherwise. + * subsequent GL rendering; the standardized way to do this is + * GL_EXT_X11_sync_object. Since this isn't implemented yet in + * mesa, we also have a path that relies on the implementation + * of the open source drivers. + * + * Anything else, we just hope for the best. * * Xorg and open source driver specifics: * @@ -1179,7 +1192,24 @@ meta_repaint_func (gpointer data) * round trip request at this point is sufficient to flush the * GLX buffers. */ - XSync (compositor->display->xdisplay, False); + if (compositor->have_x11_sync_object) + meta_sync_ring_insert_wait (); + else + XSync (compositor->display->xdisplay, False); + } + + return TRUE; +} + +static gboolean +meta_post_paint_func (gpointer data) +{ + MetaCompositor *compositor = data; + + if (compositor->frame_has_updated_xsurfaces) + { + if (compositor->have_x11_sync_object) + meta_sync_ring_after_frame (); compositor->frame_has_updated_xsurfaces = FALSE; } @@ -1222,10 +1252,16 @@ meta_compositor_new (MetaDisplay *display) G_CALLBACK (on_shadow_factory_changed), compositor); - compositor->repaint_func_id = clutter_threads_add_repaint_func (meta_repaint_func, - compositor, - NULL); - + compositor->pre_paint_func_id = + clutter_threads_add_repaint_func_full (CLUTTER_REPAINT_FLAGS_PRE_PAINT, + meta_pre_paint_func, + compositor, + NULL); + compositor->post_paint_func_id = + clutter_threads_add_repaint_func_full (CLUTTER_REPAINT_FLAGS_POST_PAINT, + meta_post_paint_func, + compositor, + NULL); return compositor; } diff --git a/src/compositor/meta-sync-ring.c b/src/compositor/meta-sync-ring.c new file mode 100644 index 000000000..c17c8141a --- /dev/null +++ b/src/compositor/meta-sync-ring.c @@ -0,0 +1,482 @@ +/* + * This is based on an original C++ implementation for compiz that + * carries the following copyright notice: + * + * + * Copyright © 2011 NVIDIA Corporation + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of NVIDIA + * Corporation not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. NVIDIA Corporation makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * NVIDIA CORPORATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * + * Authors: James Jones + */ + +#include + +#include +#include + +#include + +#include +#include + +#include "meta-sync-ring.h" + +/* Theory of operation: + * + * We use a ring of NUM_SYNCS fence objects. On each frame we advance + * to the next fence in the ring. For each fence we do: + * + * 1. fence is XSyncTriggerFence()'d and glWaitSync()'d + * 2. NUM_SYNCS / 2 frames later, fence should be triggered + * 3. fence is XSyncResetFence()'d + * 4. NUM_SYNCS / 2 frames later, fence should be reset + * 5. go back to 1 and re-use fence + * + * glClientWaitSync() and XAlarms are used in steps 2 and 4, + * respectively, to double-check the expectections. + */ + +#define NUM_SYNCS 10 +#define MAX_SYNC_WAIT_TIME (1 * 1000 * 1000 * 1000) /* one sec */ + +typedef enum +{ + META_SYNC_STATE_READY, + META_SYNC_STATE_WAITING, + META_SYNC_STATE_DONE, + META_SYNC_STATE_RESET_PENDING, +} MetaSyncState; + +typedef struct +{ + Display *xdisplay; + + XSyncFence xfence; + GLsync glsync; + + XSyncCounter xcounter; + XSyncAlarm xalarm; + XSyncValue next_counter_value; + + MetaSyncState state; +} MetaSync; + +typedef struct +{ + MetaDisplay *display; + + GHashTable *alarm_to_sync; + + MetaSync *syncs_array[NUM_SYNCS]; + guint current_sync_idx; + MetaSync *current_sync; + guint warmup_syncs; +} MetaSyncRing; + +static MetaSyncRing meta_sync_ring = { 0 }; + +static XSyncValue SYNC_VALUE_ZERO; +static XSyncValue SYNC_VALUE_ONE; + +static void (*meta_gl_get_integerv) (GLenum pname, + GLint *params); +static const char* (*meta_gl_get_stringi) (GLenum name, + GLuint index); +static void (*meta_gl_delete_sync) (GLsync sync); +static GLenum (*meta_gl_client_wait_sync) (GLsync sync, + GLbitfield flags, + GLuint64 timeout); +static void (*meta_gl_wait_sync) (GLsync sync, + GLbitfield flags, + GLuint64 timeout); +static GLsync (*meta_gl_import_sync) (GLenum external_sync_type, + GLintptr external_sync, + GLbitfield flags); + +static MetaSyncRing * +meta_sync_ring_get (void) +{ + return &meta_sync_ring; +} + +static gboolean +load_gl_symbol (const char *name, + void **func) +{ + *func = cogl_get_proc_address (name); + if (!*func) + { + meta_verbose ("MetaSyncRing: failed to resolve required GL symbol \"%s\"\n", name); + return FALSE; + } + return TRUE; +} + +static gboolean +check_gl_extensions (void) +{ + int num_extensions, i; + gboolean sync = FALSE; + gboolean x11_sync_object = FALSE; + + meta_gl_get_integerv (GL_NUM_EXTENSIONS, &num_extensions); + + for (i = 0; i < num_extensions; ++i) + { + const char *ext = meta_gl_get_stringi (GL_EXTENSIONS, i); + + if (g_strcmp0 ("GL_ARB_sync", ext) == 0) + sync = TRUE; + else if (g_strcmp0 ("GL_EXT_x11_sync_object", ext) == 0) + x11_sync_object = TRUE; + } + + return sync && x11_sync_object; +} + +static gboolean +load_required_symbols (void) +{ + static gboolean success = FALSE; + + if (success) + return TRUE; + + /* We don't link against libGL directly because cogl may want to + * use something else. This assumes that cogl has been initialized + * and dynamically loaded libGL at this point. + */ + + if (!load_gl_symbol ("glGetIntegerv", (void **) &meta_gl_get_integerv)) + goto out; + if (!load_gl_symbol ("glGetStringi", (void **) &meta_gl_get_stringi)) + goto out; + + if (!check_gl_extensions ()) + { + meta_verbose ("MetaSyncRing: couldn't find required GL extensions\n"); + goto out; + } + + if (!load_gl_symbol ("glDeleteSync", (void **) &meta_gl_delete_sync)) + goto out; + if (!load_gl_symbol ("glClientWaitSync", (void **) &meta_gl_client_wait_sync)) + goto out; + if (!load_gl_symbol ("glWaitSync", (void **) &meta_gl_wait_sync)) + goto out; + if (!load_gl_symbol ("glImportSyncEXT", (void **) &meta_gl_import_sync)) + goto out; + + success = TRUE; + out: + return success; +} + +static void +meta_sync_insert (MetaSync *self) +{ + g_return_if_fail (self->state == META_SYNC_STATE_READY); + + XSyncTriggerFence (self->xdisplay, self->xfence); + XFlush (self->xdisplay); + + meta_gl_wait_sync (self->glsync, 0, GL_TIMEOUT_IGNORED); + + self->state = META_SYNC_STATE_WAITING; +} + +static GLenum +meta_sync_check_update_finished (MetaSync *self, + GLuint64 timeout) +{ + GLenum status = GL_WAIT_FAILED; + + switch (self->state) + { + case META_SYNC_STATE_DONE: + status = GL_ALREADY_SIGNALED; + break; + case META_SYNC_STATE_WAITING: + status = meta_gl_client_wait_sync (self->glsync, 0, timeout); + if (status == GL_ALREADY_SIGNALED || status == GL_CONDITION_SATISFIED) + self->state = META_SYNC_STATE_DONE; + break; + default: + break; + } + + g_warn_if_fail (status != GL_WAIT_FAILED); + + return status; +} + +static void +meta_sync_reset (MetaSync *self) +{ + XSyncAlarmAttributes attrs; + int overflow; + + g_return_if_fail (self->state == META_SYNC_STATE_DONE); + + XSyncResetFence (self->xdisplay, self->xfence); + + attrs.trigger.wait_value = self->next_counter_value; + + XSyncChangeAlarm (self->xdisplay, self->xalarm, XSyncCAValue, &attrs); + XSyncSetCounter (self->xdisplay, self->xcounter, self->next_counter_value); + + XSyncValueAdd (&self->next_counter_value, + self->next_counter_value, + SYNC_VALUE_ONE, + &overflow); + + self->state = META_SYNC_STATE_RESET_PENDING; +} + +static void +meta_sync_handle_event (MetaSync *self, + XSyncAlarmNotifyEvent *event) +{ + g_return_if_fail (event->alarm == self->xalarm); + g_return_if_fail (self->state == META_SYNC_STATE_RESET_PENDING); + + self->state = META_SYNC_STATE_READY; +} + +static MetaSync * +meta_sync_new (Display *xdisplay) +{ + MetaSync *self; + XSyncAlarmAttributes attrs; + + self = g_malloc0 (sizeof (MetaSync)); + + self->xdisplay = xdisplay; + + self->xfence = XSyncCreateFence (xdisplay, DefaultRootWindow (xdisplay), FALSE); + self->glsync = meta_gl_import_sync (GL_SYNC_X11_FENCE_EXT, self->xfence, 0); + + self->xcounter = XSyncCreateCounter (xdisplay, SYNC_VALUE_ZERO); + + attrs.trigger.counter = self->xcounter; + attrs.trigger.value_type = XSyncAbsolute; + attrs.trigger.wait_value = SYNC_VALUE_ONE; + attrs.trigger.test_type = XSyncPositiveTransition; + attrs.events = TRUE; + self->xalarm = XSyncCreateAlarm (xdisplay, + XSyncCACounter | + XSyncCAValueType | + XSyncCAValue | + XSyncCATestType | + XSyncCAEvents, + &attrs); + + XSyncIntToValue (&self->next_counter_value, 1); + + self->state = META_SYNC_STATE_READY; + + return self; +} + +static Bool +alarm_event_predicate (Display *dpy, + XEvent *event, + XPointer data) +{ + MetaSyncRing *ring = meta_sync_ring_get (); + int xsync_event_base = meta_display_get_sync_event_base (ring->display); + + if (event->type == xsync_event_base + XSyncAlarmNotify) + { + if (((MetaSync *) data)->xalarm == ((XSyncAlarmNotifyEvent *) event)->alarm) + return True; + } + return False; +} + +static void +meta_sync_free (MetaSync *self) +{ + /* When our assumptions don't hold, something has gone wrong but we + * don't know what, so we reboot the ring. While doing that, we + * trigger fences before deleting them to try to get ourselves out + * of a potentially stuck GPU state. + */ + switch (self->state) + { + case META_SYNC_STATE_WAITING: + case META_SYNC_STATE_DONE: + /* nothing to do */ + break; + case META_SYNC_STATE_RESET_PENDING: + { + XEvent event; + XIfEvent (self->xdisplay, &event, alarm_event_predicate, (XPointer) self); + meta_sync_handle_event (self, (XSyncAlarmNotifyEvent *) &event); + } + /* fall through */ + case META_SYNC_STATE_READY: + XSyncTriggerFence (self->xdisplay, self->xfence); + XFlush (self->xdisplay); + break; + default: + break; + } + + meta_gl_delete_sync (self->glsync); + XSyncDestroyFence (self->xdisplay, self->xfence); + XSyncDestroyCounter (self->xdisplay, self->xcounter); + XSyncDestroyAlarm (self->xdisplay, self->xalarm); + + g_free (self); +} + +gboolean +meta_sync_ring_init (MetaDisplay *display) +{ + guint i; + MetaSyncRing *ring = meta_sync_ring_get (); + + g_return_val_if_fail (display != NULL, FALSE); + g_return_val_if_fail (ring->display == NULL, FALSE); + + if (!load_required_symbols ()) + return FALSE; + + if (!meta_display_has_sync (display)) + return FALSE; + + XSyncIntToValue (&SYNC_VALUE_ZERO, 0); + XSyncIntToValue (&SYNC_VALUE_ONE, 1); + + ring->display = display; + + ring->alarm_to_sync = g_hash_table_new (NULL, NULL); + + for (i = 0; i < NUM_SYNCS; ++i) + { + MetaSync *sync = meta_sync_new (meta_display_get_xdisplay (display)); + ring->syncs_array[i] = sync; + g_hash_table_replace (ring->alarm_to_sync, (gpointer) sync->xalarm, sync); + } + + ring->current_sync_idx = 0; + ring->current_sync = ring->syncs_array[0]; + ring->warmup_syncs = 0; + + return TRUE; +} + +void +meta_sync_ring_destroy (void) +{ + guint i; + MetaSyncRing *ring = meta_sync_ring_get (); + + g_return_if_fail (ring->display != NULL); + + ring->current_sync_idx = 0; + ring->current_sync = NULL; + ring->warmup_syncs = 0; + + for (i = 0; i < NUM_SYNCS; ++i) + meta_sync_free (ring->syncs_array[i]); + + g_hash_table_destroy (ring->alarm_to_sync); + + ring->display = NULL; +} + +static void +meta_sync_ring_reboot (MetaDisplay *display) +{ + meta_sync_ring_destroy (); + meta_sync_ring_init (display); +} + +void +meta_sync_ring_after_frame (void) +{ + MetaSyncRing *ring = meta_sync_ring_get (); + + g_return_if_fail (ring->display != NULL); + + if (ring->warmup_syncs >= NUM_SYNCS / 2) + { + guint reset_sync_idx = (ring->current_sync_idx + NUM_SYNCS - (NUM_SYNCS / 2)) % NUM_SYNCS; + MetaSync *sync_to_reset = ring->syncs_array[reset_sync_idx]; + + GLenum status = meta_sync_check_update_finished (sync_to_reset, 0); + if (status == GL_TIMEOUT_EXPIRED) + { + meta_warning ("MetaSyncRing: We should never wait for a sync -- add more syncs?\n"); + status = meta_sync_check_update_finished (sync_to_reset, MAX_SYNC_WAIT_TIME); + } + + if (status != GL_ALREADY_SIGNALED && status != GL_CONDITION_SATISFIED) + { + meta_warning ("MetaSyncRing: Timed out waiting for sync object.\n"); + meta_sync_ring_reboot (ring->display); + return; + } + + meta_sync_reset (sync_to_reset); + } + else + { + ring->warmup_syncs += 1; + } + + ring->current_sync_idx += 1; + ring->current_sync_idx %= NUM_SYNCS; + + ring->current_sync = ring->syncs_array[ring->current_sync_idx]; +} + +void +meta_sync_ring_insert_wait (void) +{ + MetaSyncRing *ring = meta_sync_ring_get (); + + g_return_if_fail (ring->display != NULL); + + if (ring->current_sync->state != META_SYNC_STATE_READY) + { + meta_warning ("MetaSyncRing: Sync object is not ready -- were events handled properly?\n"); + meta_sync_ring_reboot (ring->display); + } + + meta_sync_insert (ring->current_sync); +} + +void +meta_sync_ring_handle_event (XSyncAlarmNotifyEvent *event) +{ + MetaSync *sync; + MetaSyncRing *ring = meta_sync_ring_get (); + + g_return_if_fail (ring->display != NULL); + + sync = g_hash_table_lookup (ring->alarm_to_sync, (gpointer) event->alarm); + if (sync) + meta_sync_handle_event (sync, event); +} diff --git a/src/compositor/meta-sync-ring.h b/src/compositor/meta-sync-ring.h new file mode 100644 index 000000000..b0d227591 --- /dev/null +++ b/src/compositor/meta-sync-ring.h @@ -0,0 +1,17 @@ +#ifndef _META_SYNC_RING_H_ +#define _META_SYNC_RING_H_ + +#include + +#include +#include + +#include + +gboolean meta_sync_ring_init (MetaDisplay *dpy); +void meta_sync_ring_destroy (void); +void meta_sync_ring_after_frame (void); +void meta_sync_ring_insert_wait (void); +void meta_sync_ring_handle_event (XSyncAlarmNotifyEvent *event); + +#endif /* _META_SYNC_RING_H_ */