From e13f43987b9cd3320441816de0b6b570be95c01f Mon Sep 17 00:00:00 2001 From: Calvin Walton Date: Tue, 24 Mar 2015 11:21:25 -0400 Subject: [PATCH 1/5] Include libXrender as a dependency, link it to libmutter Mutter uses a function from libXrender (XRenderFindStandardFormat in src/x11/iconcache.c), but doesn't link to libXrender. This causes link issues on systems using the gold linker, particularly with -Wl,--as-needed. Since mutter is using a function from libXrender, adding 'xrender' as a dependency seems appropriate, and fixes the issue. https://bugzilla.gnome.org/show_bug.cgi?id=746692 --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index 74f4eeb..d193606 100644 --- a/configure.ac +++ b/configure.ac @@ -88,6 +88,7 @@ MUTTER_PC_MODULES=" xi >= 1.6.0 xkbfile xkeyboard-config + xrender xcb-randr " -- 2.5.0 From 04173777a23549eea3823516483909112ac77303 Mon Sep 17 00:00:00 2001 From: Rui Matos Date: Fri, 18 Apr 2014 20:21:20 +0200 Subject: [PATCH 2/5] 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. Some driver bugs were uncovered while working on this so if we have had to reboot the ring a few times, something is probably wrong and we're likely to just make things worse by continuing to try. Let's err on the side of caution, disable ourselves and fallback to the XSync() path in the compositor. https://bugzilla.gnome.org/show_bug.cgi?id=728464 --- configure.ac | 5 + src/Makefile.am | 2 + src/backends/x11/meta-backend-x11.c | 3 + src/compositor/compositor-private.h | 4 +- src/compositor/compositor.c | 71 +++-- src/compositor/meta-sync-ring.c | 566 ++++++++++++++++++++++++++++++++++++ src/compositor/meta-sync-ring.h | 14 + 7 files changed, 641 insertions(+), 24 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 d193606..fe1c1eb 100644 --- a/configure.ac +++ b/configure.ac @@ -312,6 +312,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 e73a053..d7a44bc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -126,6 +126,8 @@ libmutter_la_SOURCES = \ compositor/meta-surface-actor.h \ compositor/meta-surface-actor-x11.c \ compositor/meta-surface-actor-x11.h \ + 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/backends/x11/meta-backend-x11.c b/src/backends/x11/meta-backend-x11.c index 586f991..d77365d 100644 --- a/src/backends/x11/meta-backend-x11.c +++ b/src/backends/x11/meta-backend-x11.c @@ -43,6 +43,7 @@ #include #include "display-private.h" #include "compositor/compositor-private.h" +#include "compositor/meta-sync-ring.h" struct _MetaBackendX11Private { @@ -183,6 +184,8 @@ handle_host_xevent (MetaBackend *backend, MetaCompositor *compositor = display->compositor; if (meta_plugin_manager_xevent_filter (compositor->plugin_mgr, event)) bypass_clutter = TRUE; + if (compositor->have_x11_sync_object) + meta_sync_ring_handle_event (event); } } diff --git a/src/compositor/compositor-private.h b/src/compositor/compositor-private.h index 80fb4e2..9e3e73d 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 8bf469f..554faa1 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/x11/meta-backend-x11.h" @@ -125,7 +126,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 @@ -468,13 +473,11 @@ meta_compositor_manage (MetaCompositor *compositor) MetaDisplay *display = compositor->display; Display *xdisplay = display->xdisplay; MetaScreen *screen = display->screen; + MetaBackend *backend = meta_get_backend (); meta_screen_set_cm_selection (display->screen); - { - MetaBackend *backend = meta_get_backend (); - compositor->stage = meta_backend_get_stage (backend); - } + compositor->stage = meta_backend_get_stage (backend); /* We use connect_after() here to accomodate code in GNOME Shell that, * when benchmarking drawing performance, connects to ::after-paint @@ -510,7 +513,7 @@ meta_compositor_manage (MetaCompositor *compositor) compositor->output = screen->composite_overlay_window; - xwin = meta_backend_x11_get_xwindow (META_BACKEND_X11 (meta_get_backend ())); + xwin = meta_backend_x11_get_xwindow (META_BACKEND_X11 (backend)); XReparentWindow (xdisplay, xwin, compositor->output, 0, 0); @@ -530,6 +533,9 @@ meta_compositor_manage (MetaCompositor *compositor) * contents until we show the stage. */ XMapWindow (xdisplay, compositor->output); + + compositor->have_x11_sync_object = + meta_sync_ring_init (meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend))); } redirect_windows (display->screen); @@ -1044,11 +1050,12 @@ frame_callback (CoglOnscreen *onscreen, } } -static void -pre_paint_windows (MetaCompositor *compositor) +static gboolean +meta_pre_paint_func (gpointer data) { GList *l; MetaWindowActor *top_window; + MetaCompositor *compositor = data; if (compositor->onscreen == NULL) { @@ -1060,7 +1067,7 @@ pre_paint_windows (MetaCompositor *compositor) } if (compositor->windows == NULL) - return; + return TRUE; top_window = g_list_last (compositor->windows)->data; @@ -1077,10 +1084,12 @@ pre_paint_windows (MetaCompositor *compositor) { /* 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: * @@ -1095,18 +1104,28 @@ pre_paint_windows (MetaCompositor *compositor) * round trip request at this point is sufficient to flush the * GLX buffers. */ - XSync (compositor->display->xdisplay, False); - - compositor->frame_has_updated_xsurfaces = FALSE; + if (compositor->have_x11_sync_object) + compositor->have_x11_sync_object = meta_sync_ring_insert_wait (); + else + XSync (compositor->display->xdisplay, False); } + + return TRUE; } static gboolean -meta_repaint_func (gpointer data) +meta_post_paint_func (gpointer data) { MetaCompositor *compositor = data; - if (meta_screen_get_n_monitors (compositor->display->screen) > 0) - pre_paint_windows (compositor); + + if (compositor->frame_has_updated_xsurfaces) + { + if (compositor->have_x11_sync_object) + compositor->have_x11_sync_object = meta_sync_ring_after_frame (); + + compositor->frame_has_updated_xsurfaces = FALSE; + } + return TRUE; } @@ -1141,10 +1160,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 0000000..4ee61f8 --- /dev/null +++ b/src/compositor/meta-sync-ring.c @@ -0,0 +1,566 @@ +/* + * 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 + +#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 */ +#define MAX_REBOOT_ATTEMPTS 2 + +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 +{ + Display *xdisplay; + int xsync_event_base; + int xsync_error_base; + + GHashTable *alarm_to_sync; + + MetaSync *syncs_array[NUM_SYNCS]; + guint current_sync_idx; + MetaSync *current_sync; + guint warmup_syncs; + + guint reboots; +} MetaSyncRing; + +static MetaSyncRing meta_sync_ring = { 0 }; + +static XSyncValue SYNC_VALUE_ZERO; +static XSyncValue SYNC_VALUE_ONE; + +static const char* (*meta_gl_get_string) (GLenum name); +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) +{ + if (meta_sync_ring.reboots > MAX_REBOOT_ATTEMPTS) + return NULL; + + 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) +{ + ClutterBackend *backend; + CoglContext *cogl_context; + CoglDisplay *cogl_display; + CoglRenderer *cogl_renderer; + + backend = clutter_get_default_backend (); + cogl_context = clutter_backend_get_cogl_context (backend); + cogl_display = cogl_context_get_display (cogl_context); + cogl_renderer = cogl_display_get_renderer (cogl_display); + + switch (cogl_renderer_get_driver (cogl_renderer)) + { + case COGL_DRIVER_GL3: + { + int num_extensions, i; + gboolean arb_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) + arb_sync = TRUE; + else if (g_strcmp0 ("GL_EXT_x11_sync_object", ext) == 0) + x11_sync_object = TRUE; + } + + return arb_sync && x11_sync_object; + } + case COGL_DRIVER_GL: + { + const char *extensions = meta_gl_get_string (GL_EXTENSIONS); + return (extensions != NULL && + strstr (extensions, "GL_ARB_sync") != NULL && + strstr (extensions, "GL_EXT_x11_sync_object") != NULL); + } + default: + break; + } + + return FALSE; +} + +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 ("glGetString", (void **) &meta_gl_get_string)) + goto out; + 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 (); + + if (!ring) + return False; + + if (event->type == ring->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 (Display *xdisplay) +{ + gint major, minor; + guint i; + MetaSyncRing *ring = meta_sync_ring_get (); + + if (!ring) + return FALSE; + + g_return_val_if_fail (xdisplay != NULL, FALSE); + g_return_val_if_fail (ring->xdisplay == NULL, FALSE); + + if (!load_required_symbols ()) + return FALSE; + + if (!XSyncQueryExtension (xdisplay, &ring->xsync_event_base, &ring->xsync_error_base) || + !XSyncInitialize (xdisplay, &major, &minor)) + return FALSE; + + XSyncIntToValue (&SYNC_VALUE_ZERO, 0); + XSyncIntToValue (&SYNC_VALUE_ONE, 1); + + ring->xdisplay = xdisplay; + + ring->alarm_to_sync = g_hash_table_new (NULL, NULL); + + for (i = 0; i < NUM_SYNCS; ++i) + { + MetaSync *sync = meta_sync_new (ring->xdisplay); + 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 (); + + if (!ring) + return; + + g_return_if_fail (ring->xdisplay != 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->xsync_event_base = 0; + ring->xsync_error_base = 0; + ring->xdisplay = NULL; +} + +static gboolean +meta_sync_ring_reboot (Display *xdisplay) +{ + MetaSyncRing *ring = meta_sync_ring_get (); + + if (!ring) + return FALSE; + + meta_sync_ring_destroy (); + + ring->reboots += 1; + + if (!meta_sync_ring_get ()) + { + meta_warning ("MetaSyncRing: Too many reboots -- disabling\n"); + return FALSE; + } + + return meta_sync_ring_init (xdisplay); +} + +gboolean +meta_sync_ring_after_frame (void) +{ + MetaSyncRing *ring = meta_sync_ring_get (); + + if (!ring) + return FALSE; + + g_return_if_fail (ring->xdisplay != 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"); + return meta_sync_ring_reboot (ring->xdisplay); + } + + 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]; + + return TRUE; +} + +gboolean +meta_sync_ring_insert_wait (void) +{ + MetaSyncRing *ring = meta_sync_ring_get (); + + if (!ring) + return FALSE; + + g_return_if_fail (ring->xdisplay != NULL); + + if (ring->current_sync->state != META_SYNC_STATE_READY) + { + meta_warning ("MetaSyncRing: Sync object is not ready -- were events handled properly?\n"); + if (!meta_sync_ring_reboot (ring->xdisplay)) + return FALSE; + } + + meta_sync_insert (ring->current_sync); + + return TRUE; +} + +void +meta_sync_ring_handle_event (XEvent *xevent) +{ + XSyncAlarmNotifyEvent *event; + MetaSync *sync; + MetaSyncRing *ring = meta_sync_ring_get (); + + if (!ring) + return; + + g_return_if_fail (ring->xdisplay != NULL); + + if (xevent->type != (ring->xsync_event_base + XSyncAlarmNotify)) + return; + + event = (XSyncAlarmNotifyEvent *) xevent; + + 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 0000000..6dca8ef --- /dev/null +++ b/src/compositor/meta-sync-ring.h @@ -0,0 +1,14 @@ +#ifndef _META_SYNC_RING_H_ +#define _META_SYNC_RING_H_ + +#include + +#include + +gboolean meta_sync_ring_init (Display *dpy); +void meta_sync_ring_destroy (void); +gboolean meta_sync_ring_after_frame (void); +gboolean meta_sync_ring_insert_wait (void); +void meta_sync_ring_handle_event (XEvent *event); + +#endif /* _META_SYNC_RING_H_ */ -- 2.5.0 From 8b889d6ff46971edd4af7f412275e116e293e72a Mon Sep 17 00:00:00 2001 From: Aaron Plattner Date: Mon, 3 Aug 2015 21:15:15 -0700 Subject: [PATCH 3/5] compositor: Fix GL_EXT_x11_sync_object race condition The compositor maintains a ring of shared fences with the X server in order to properly synchronize rendering between the X server and the compositor's GPU channel. When all of the fences have been used, the compositor needs to reset one so that it can be reused. It does this by first waiting on the CPU for the fence to become triggered, and then sending a request to the X server to reset the fence. If the compositor's GPU channel is busy processing other work (e.g. the desktop switcher animation), then the X server may process the reset request before the GPU has consumed the fence. This causes the GPU channel to hang. Fix the problem by having the compositor's GPU channel trigger its own fence after waiting for the X server's fence. Wait for that fence on the CPU before sending the reset request to the X server. This ensures that the GPU has consumed the X11 fence before the server resets it. Signed-off-by: Aaron Plattner https://bugzilla.gnome.org/show_bug.cgi?id=728464 --- src/compositor/meta-sync-ring.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/compositor/meta-sync-ring.c b/src/compositor/meta-sync-ring.c index 4ee61f8..44b1c41 100644 --- a/src/compositor/meta-sync-ring.c +++ b/src/compositor/meta-sync-ring.c @@ -73,7 +73,8 @@ typedef struct Display *xdisplay; XSyncFence xfence; - GLsync glsync; + GLsync gl_x11_sync; + GLsync gpu_fence; XSyncCounter xcounter; XSyncAlarm xalarm; @@ -118,6 +119,8 @@ static void (*meta_gl_wait_sync) (GLsync sync, static GLsync (*meta_gl_import_sync) (GLenum external_sync_type, GLintptr external_sync, GLbitfield flags); +static GLsync (*meta_gl_fence_sync) (GLenum condition, + GLbitfield flags); static MetaSyncRing * meta_sync_ring_get (void) @@ -224,6 +227,8 @@ load_required_symbols (void) goto out; if (!load_gl_symbol ("glImportSyncEXT", (void **) &meta_gl_import_sync)) goto out; + if (!load_gl_symbol ("glFenceSync", (void **) &meta_gl_fence_sync)) + goto out; success = TRUE; out: @@ -238,7 +243,8 @@ meta_sync_insert (MetaSync *self) XSyncTriggerFence (self->xdisplay, self->xfence); XFlush (self->xdisplay); - meta_gl_wait_sync (self->glsync, 0, GL_TIMEOUT_IGNORED); + meta_gl_wait_sync (self->gl_x11_sync, 0, GL_TIMEOUT_IGNORED); + self->gpu_fence = meta_gl_fence_sync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0); self->state = META_SYNC_STATE_WAITING; } @@ -255,9 +261,13 @@ meta_sync_check_update_finished (MetaSync *self, status = GL_ALREADY_SIGNALED; break; case META_SYNC_STATE_WAITING: - status = meta_gl_client_wait_sync (self->glsync, 0, timeout); + status = meta_gl_client_wait_sync (self->gpu_fence, 0, timeout); if (status == GL_ALREADY_SIGNALED || status == GL_CONDITION_SATISFIED) - self->state = META_SYNC_STATE_DONE; + { + self->state = META_SYNC_STATE_DONE; + meta_gl_delete_sync (self->gpu_fence); + self->gpu_fence = 0; + } break; default: break; @@ -312,7 +322,8 @@ meta_sync_new (Display *xdisplay) 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->gl_x11_sync = meta_gl_import_sync (GL_SYNC_X11_FENCE_EXT, self->xfence, 0); + self->gpu_fence = 0; self->xcounter = XSyncCreateCounter (xdisplay, SYNC_VALUE_ZERO); @@ -365,6 +376,8 @@ meta_sync_free (MetaSync *self) switch (self->state) { case META_SYNC_STATE_WAITING: + meta_gl_delete_sync (self->gpu_fence); + break; case META_SYNC_STATE_DONE: /* nothing to do */ break; @@ -383,7 +396,7 @@ meta_sync_free (MetaSync *self) break; } - meta_gl_delete_sync (self->glsync); + meta_gl_delete_sync (self->gl_x11_sync); XSyncDestroyFence (self->xdisplay, self->xfence); XSyncDestroyCounter (self->xdisplay, self->xcounter); XSyncDestroyAlarm (self->xdisplay, self->xalarm); -- 2.5.0 From 271b864a3a5fe2a59523a0ead0667072170e7bbe Mon Sep 17 00:00:00 2001 From: Ting-Wei Lan Date: Sat, 8 Aug 2015 20:12:09 +0800 Subject: [PATCH 4/5] build: Fix return value in meta-sync-ring.c https://bugzilla.gnome.org/show_bug.cgi?id=753380 --- src/compositor/meta-sync-ring.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compositor/meta-sync-ring.c b/src/compositor/meta-sync-ring.c index 44b1c41..217ebe5 100644 --- a/src/compositor/meta-sync-ring.c +++ b/src/compositor/meta-sync-ring.c @@ -499,7 +499,7 @@ meta_sync_ring_after_frame (void) if (!ring) return FALSE; - g_return_if_fail (ring->xdisplay != NULL); + g_return_val_if_fail (ring->xdisplay != NULL, FALSE); if (ring->warmup_syncs >= NUM_SYNCS / 2) { @@ -542,7 +542,7 @@ meta_sync_ring_insert_wait (void) if (!ring) return FALSE; - g_return_if_fail (ring->xdisplay != NULL); + g_return_val_if_fail (ring->xdisplay != NULL, FALSE); if (ring->current_sync->state != META_SYNC_STATE_READY) { -- 2.5.0 From 4d4bd1fb39cf396733de4dff4f3041b5266b803a Mon Sep 17 00:00:00 2001 From: Rui Matos Date: Wed, 12 Aug 2015 15:26:34 +0200 Subject: [PATCH 5/5] compositor: Handle fences in the frontend X connection Since mutter has two X connections and does damage handling on the frontend while fence triggering is done on the backend, we have a race between XDamageSubtract() and XSyncFenceTrigger() causing missed redraws in the GL_EXT_X11_sync_object path. If the fence trigger gets processed first by the server, any client drawing that happens between that and the damage subtract being processed and is completely contained in the last damage event box that mutter got, won't be included in the current frame nor will it cause a new damage event. A simple fix for this would be XSync()ing on the frontend connection after doing all the damage subtracts but that would add a round trip on every frame again which defeats the asynchronous design of X fences. Instead, if we move fence handling to the frontend we automatically get the right ordering between damage subtracts and fence triggers. https://bugzilla.gnome.org/show_bug.cgi?id=728464 --- src/backends/x11/meta-backend-x11.c | 3 --- src/compositor/compositor.c | 6 ++++-- src/compositor/meta-sync-ring.c | 15 ++++++++++++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/backends/x11/meta-backend-x11.c b/src/backends/x11/meta-backend-x11.c index d77365d..586f991 100644 --- a/src/backends/x11/meta-backend-x11.c +++ b/src/backends/x11/meta-backend-x11.c @@ -43,7 +43,6 @@ #include #include "display-private.h" #include "compositor/compositor-private.h" -#include "compositor/meta-sync-ring.h" struct _MetaBackendX11Private { @@ -184,8 +183,6 @@ handle_host_xevent (MetaBackend *backend, MetaCompositor *compositor = display->compositor; if (meta_plugin_manager_xevent_filter (compositor->plugin_mgr, event)) bypass_clutter = TRUE; - if (compositor->have_x11_sync_object) - meta_sync_ring_handle_event (event); } } diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c index 554faa1..2e182c2 100644 --- a/src/compositor/compositor.c +++ b/src/compositor/compositor.c @@ -534,8 +534,7 @@ meta_compositor_manage (MetaCompositor *compositor) */ XMapWindow (xdisplay, compositor->output); - compositor->have_x11_sync_object = - meta_sync_ring_init (meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend))); + compositor->have_x11_sync_object = meta_sync_ring_init (xdisplay); } redirect_windows (display->screen); @@ -737,6 +736,9 @@ meta_compositor_process_event (MetaCompositor *compositor, process_damage (compositor, (XDamageNotifyEvent *) event, window); } + if (compositor->have_x11_sync_object) + meta_sync_ring_handle_event (event); + /* Clutter needs to know about MapNotify events otherwise it will think the stage is invisible */ if (!meta_is_wayland_compositor () && event->type == MapNotify) diff --git a/src/compositor/meta-sync-ring.c b/src/compositor/meta-sync-ring.c index 217ebe5..336ccd4 100644 --- a/src/compositor/meta-sync-ring.c +++ b/src/compositor/meta-sync-ring.c @@ -322,7 +322,7 @@ meta_sync_new (Display *xdisplay) self->xdisplay = xdisplay; self->xfence = XSyncCreateFence (xdisplay, DefaultRootWindow (xdisplay), FALSE); - self->gl_x11_sync = meta_gl_import_sync (GL_SYNC_X11_FENCE_EXT, self->xfence, 0); + self->gl_x11_sync = 0; self->gpu_fence = 0; self->xcounter = XSyncCreateCounter (xdisplay, SYNC_VALUE_ZERO); @@ -347,6 +347,13 @@ meta_sync_new (Display *xdisplay) return self; } +static void +meta_sync_import (MetaSync *self) +{ + g_return_if_fail (self->gl_x11_sync == 0); + self->gl_x11_sync = meta_gl_import_sync (GL_SYNC_X11_FENCE_EXT, self->xfence, 0); +} + static Bool alarm_event_predicate (Display *dpy, XEvent *event, @@ -437,6 +444,12 @@ meta_sync_ring_init (Display *xdisplay) ring->syncs_array[i] = sync; g_hash_table_replace (ring->alarm_to_sync, (gpointer) sync->xalarm, sync); } + /* Since the connection we create the X fences on isn't the same as + * the one used for the GLX context, we need to XSync() here to + * ensure glImportSync() succeeds. */ + XSync (xdisplay, False); + for (i = 0; i < NUM_SYNCS; ++i) + meta_sync_import (ring->syncs_array[i]); ring->current_sync_idx = 0; ring->current_sync = ring->syncs_array[0]; -- 2.5.0