/* * 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 "config.h" #include "compositor/meta-sync-ring.h" #include #include #include #include #include "clutter/clutter.h" #include "cogl/cogl.h" #include "meta/util.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 gl_x11_sync; GLsync gpu_fence; 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 GLsync (*meta_gl_fence_sync) (GLenum condition, 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\"", 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; } 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"); 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; if (!load_gl_symbol ("glFenceSync", (void **) &meta_gl_fence_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->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; } 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->gpu_fence, 0, timeout); if (status == GL_ALREADY_SIGNALED || status == GL_CONDITION_SATISFIED) { self->state = META_SYNC_STATE_DONE; meta_gl_delete_sync (self->gpu_fence); self->gpu_fence = 0; } 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->gl_x11_sync = 0; self->gpu_fence = 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 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, 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: meta_gl_delete_sync (self->gpu_fence); break; 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); } G_GNUC_FALLTHROUGH; case META_SYNC_STATE_READY: XSyncTriggerFence (self->xdisplay, self->xfence); XFlush (self->xdisplay); break; default: break; } meta_gl_delete_sync (self->gl_x11_sync); 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); } /* 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]; 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"); 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_val_if_fail (ring->xdisplay != NULL, FALSE); 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?"); 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."); 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_val_if_fail (ring->xdisplay != NULL, FALSE); if (ring->current_sync->state != META_SYNC_STATE_READY) { meta_warning ("MetaSyncRing: Sync object is not ready -- were events handled properly?"); 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); }