[mutter] For NVIDIA proprietary drivers, implement sync events with a thread
- From: Owen Taylor <otaylor src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [mutter] For NVIDIA proprietary drivers, implement sync events with a thread
- Date: Wed, 22 Feb 2017 16:15:22 +0000 (UTC)
commit 690b2322580f3a9b563bed37160fcfd4df2b7ce8
Author: Owen W. Taylor <otaylor fishsoup net>
Date: Thu Feb 11 17:15:13 2016 -0500
For NVIDIA proprietary drivers, implement sync events with a thread
It's a good guess that the buffer swap will occur at the next vblank,
so use glXWaitVideoSync in a separate thread to deliver a sync event
rather than just letting the client block when frame drawing, which
can signficantly change app logic as compared to the INTEL_swap_event
case.
https://bugzilla.gnome.org/show_bug.cgi?id=779039
cogl/cogl/cogl-private.h | 3 +
cogl/cogl/winsys/cogl-winsys-glx.c | 294 ++++++++++++++++++++++++++++++++++--
2 files changed, 286 insertions(+), 11 deletions(-)
---
diff --git a/cogl/cogl/cogl-private.h b/cogl/cogl/cogl-private.h
index 333955c..e5dc9fe 100644
--- a/cogl/cogl/cogl-private.h
+++ b/cogl/cogl/cogl-private.h
@@ -77,6 +77,9 @@ typedef enum
COGL_PRIVATE_FEATURE_GL_PROGRAMMABLE,
COGL_PRIVATE_FEATURE_GL_EMBEDDED,
COGL_PRIVATE_FEATURE_GL_WEB,
+ /* This is currently only implemented for GLX, but isn't actually
+ * that winsys dependent */
+ COGL_PRIVATE_FEATURE_THREADED_SWAP_WAIT,
COGL_N_PRIVATE_FEATURES
} CoglPrivateFeature;
diff --git a/cogl/cogl/winsys/cogl-winsys-glx.c b/cogl/cogl/winsys/cogl-winsys-glx.c
index 7ad1a3f..1418d15 100644
--- a/cogl/cogl/winsys/cogl-winsys-glx.c
+++ b/cogl/cogl/winsys/cogl-winsys-glx.c
@@ -65,12 +65,16 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
+#include <errno.h>
#include <fcntl.h>
#include <time.h>
+#include <unistd.h>
#include <GL/glx.h>
#include <X11/Xlib.h>
+#include <glib.h>
+
/* This is a relatively new extension */
#ifndef GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV
#define GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV 0x20F7
@@ -100,6 +104,14 @@ typedef struct _CoglOnscreenGLX
CoglBool pending_sync_notify;
CoglBool pending_complete_notify;
CoglBool pending_resize_notify;
+
+ GThread *swap_wait_thread;
+ GQueue *swap_wait_queue;
+ GCond swap_wait_cond;
+ GMutex swap_wait_mutex;
+ int swap_wait_pipe[2];
+ GLXContext swap_wait_context;
+ CoglBool closing_down;
} CoglOnscreenGLX;
typedef struct _CoglPixmapTextureEyeGLX
@@ -885,6 +897,28 @@ update_winsys_features (CoglContext *context, CoglError **error)
COGL_FEATURE_ID_PRESENTATION_TIME,
TRUE);
}
+ else
+ {
+ CoglGpuInfo *info = &context->gpu;
+ if (glx_display->have_vblank_counter &&
+ info->vendor == COGL_GPU_INFO_VENDOR_NVIDIA)
+ {
+ COGL_FLAGS_SET (context->winsys_features,
+ COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT, TRUE);
+ COGL_FLAGS_SET (context->winsys_features,
+ COGL_WINSYS_FEATURE_SWAP_BUFFERS_EVENT, TRUE);
+ /* TODO: remove this deprecated feature */
+ COGL_FLAGS_SET (context->features,
+ COGL_FEATURE_ID_SWAP_BUFFERS_EVENT,
+ TRUE);
+ COGL_FLAGS_SET (context->features,
+ COGL_FEATURE_ID_PRESENTATION_TIME,
+ TRUE);
+ COGL_FLAGS_SET (context->private_features,
+ COGL_PRIVATE_FEATURE_THREADED_SWAP_WAIT,
+ TRUE);
+ }
+ }
/* We'll manually handle queueing dirty events in response to
* Expose events from X */
@@ -1481,7 +1515,8 @@ _cogl_winsys_onscreen_init (CoglOnscreen *onscreen,
}
#ifdef GLX_INTEL_swap_event
- if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT))
+ if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT) &&
+ !_cogl_has_private_feature (context, COGL_PRIVATE_FEATURE_THREADED_SWAP_WAIT))
{
GLXDrawable drawable =
glx_onscreen->glxwin ? glx_onscreen->glxwin : xlib_onscreen->xwin;
@@ -1524,6 +1559,31 @@ _cogl_winsys_onscreen_deinit (CoglOnscreen *onscreen)
xlib_onscreen->output = NULL;
}
+ if (glx_onscreen->swap_wait_thread)
+ {
+ g_mutex_lock (&glx_onscreen->swap_wait_mutex);
+ glx_onscreen->closing_down = TRUE;
+ g_cond_signal (&glx_onscreen->swap_wait_cond);
+ g_mutex_unlock (&glx_onscreen->swap_wait_mutex);
+ g_thread_join (glx_onscreen->swap_wait_thread);
+ glx_onscreen->swap_wait_thread = NULL;
+
+ g_cond_clear (&glx_onscreen->swap_wait_cond);
+ g_mutex_clear (&glx_onscreen->swap_wait_mutex);
+
+ g_queue_free (glx_onscreen->swap_wait_queue);
+ glx_onscreen->swap_wait_queue = NULL;
+
+ _cogl_poll_renderer_remove_fd (context->display->renderer,
+ glx_onscreen->swap_wait_pipe[0]);
+
+ close (glx_onscreen->swap_wait_pipe[0]);
+ close (glx_onscreen->swap_wait_pipe[1]);
+
+ glx_renderer->glXDestroyContext (xlib_renderer->xdpy,
+ glx_onscreen->swap_wait_context);
+ }
+
_cogl_xlib_renderer_trap_errors (context->display->renderer, &old_state);
drawable =
@@ -1757,6 +1817,199 @@ set_frame_info_output (CoglOnscreen *onscreen,
}
}
+static gpointer
+threaded_swap_wait (gpointer data)
+{
+ CoglOnscreen *onscreen = data;
+
+ CoglOnscreenGLX *glx_onscreen = onscreen->winsys;
+
+ CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
+ CoglContext *context = framebuffer->context;
+ CoglDisplay *display = context->display;
+ CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (display->renderer);
+ CoglGLXDisplay *glx_display = display->winsys;
+ CoglGLXRenderer *glx_renderer = display->renderer->winsys;
+ GLXDrawable dummy_drawable;
+
+ if (glx_display->dummy_glxwin)
+ dummy_drawable = glx_display->dummy_glxwin;
+ else
+ dummy_drawable = glx_display->dummy_xwin;
+
+ glx_renderer->glXMakeContextCurrent (xlib_renderer->xdpy,
+ dummy_drawable,
+ dummy_drawable,
+ glx_onscreen->swap_wait_context);
+
+ g_mutex_lock (&glx_onscreen->swap_wait_mutex);
+
+ while (TRUE)
+ {
+ gpointer queue_element;
+ uint32_t vblank_counter;
+
+ while (!glx_onscreen->closing_down && glx_onscreen->swap_wait_queue->length == 0)
+ g_cond_wait (&glx_onscreen->swap_wait_cond, &glx_onscreen->swap_wait_mutex);
+
+ if (glx_onscreen->closing_down)
+ break;
+
+ queue_element = g_queue_pop_tail (glx_onscreen->swap_wait_queue);
+ vblank_counter = GPOINTER_TO_UINT(queue_element);
+
+ g_mutex_unlock (&glx_onscreen->swap_wait_mutex);
+ glx_renderer->glXWaitVideoSync (2,
+ (vblank_counter + 1) % 2,
+ &vblank_counter);
+ g_mutex_lock (&glx_onscreen->swap_wait_mutex);
+
+ if (!glx_onscreen->closing_down)
+ {
+ int bytes_written = 0;
+
+ union {
+ char bytes[8];
+ int64_t presentation_time;
+ } u;
+
+ u.presentation_time = get_monotonic_time_ns ();
+
+ while (bytes_written < 8)
+ {
+ int res = write (glx_onscreen->swap_wait_pipe[1], u.bytes + bytes_written, 8 - bytes_written);
+ if (res == -1)
+ {
+ if (errno != EINTR)
+ g_error ("Error writing to swap notification pipe: %s\n",
+ g_strerror (errno));
+ }
+ else
+ {
+ bytes_written += res;
+ }
+ }
+ }
+ }
+
+ g_mutex_unlock (&glx_onscreen->swap_wait_mutex);
+
+ glx_renderer->glXMakeContextCurrent (xlib_renderer->xdpy,
+ None,
+ None,
+ NULL);
+
+ return NULL;
+}
+
+static int64_t
+threaded_swap_wait_pipe_prepare (void *user_data)
+{
+ return -1;
+}
+
+static void
+threaded_swap_wait_pipe_dispatch (void *user_data, int revents)
+{
+ CoglOnscreen *onscreen = user_data;
+ CoglOnscreenGLX *glx_onscreen = onscreen->winsys;
+
+ CoglFrameInfo *info;
+
+ if ((revents & COGL_POLL_FD_EVENT_IN))
+ {
+ int bytes_read = 0;
+
+ union {
+ char bytes[8];
+ int64_t presentation_time;
+ } u;
+
+ while (bytes_read < 8)
+ {
+ int res = read (glx_onscreen->swap_wait_pipe[0], u.bytes + bytes_read, 8 - bytes_read);
+ if (res == -1)
+ {
+ if (errno != EINTR)
+ g_error ("Error reading from swap notification pipe: %s\n",
+ g_strerror (errno));
+ }
+ else
+ {
+ bytes_read += res;
+ }
+ }
+
+ set_sync_pending (onscreen);
+ set_complete_pending (onscreen);
+
+ info = g_queue_peek_head (&onscreen->pending_frame_infos);
+ info->presentation_time = u.presentation_time;
+ }
+}
+
+static void
+start_threaded_swap_wait (CoglOnscreen *onscreen,
+ uint32_t vblank_counter)
+{
+ CoglOnscreenGLX *glx_onscreen = onscreen->winsys;
+ CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
+ CoglContext *context = framebuffer->context;
+
+ if (glx_onscreen->swap_wait_thread == NULL)
+ {
+ CoglDisplay *display = context->display;
+ CoglGLXRenderer *glx_renderer = display->renderer->winsys;
+ CoglGLXDisplay *glx_display = display->winsys;
+ CoglOnscreenXlib *xlib_onscreen = onscreen->winsys;
+ CoglXlibRenderer *xlib_renderer =
+ _cogl_xlib_renderer_get_data (display->renderer);
+
+ GLXDrawable drawable =
+ glx_onscreen->glxwin ? glx_onscreen->glxwin : xlib_onscreen->xwin;
+ int i;
+
+ ensure_ust_type (display->renderer, drawable);
+
+ if ((pipe (glx_onscreen->swap_wait_pipe) == -1))
+ g_error ("Couldn't create pipe for swap notification: %s\n",
+ g_strerror (errno));
+
+ for (i = 0; i < 2; i++)
+ {
+ if (fcntl(glx_onscreen->swap_wait_pipe[i], F_SETFD,
+ fcntl(glx_onscreen->swap_wait_pipe[i], F_GETFD, 0) | FD_CLOEXEC) == -1)
+ g_error ("Couldn't set swap notification pipe CLOEXEC: %s\n",
+ g_strerror (errno));
+ }
+
+ _cogl_poll_renderer_add_fd (display->renderer,
+ glx_onscreen->swap_wait_pipe[0],
+ COGL_POLL_FD_EVENT_IN,
+ threaded_swap_wait_pipe_prepare,
+ threaded_swap_wait_pipe_dispatch,
+ onscreen);
+
+ glx_onscreen->swap_wait_queue = g_queue_new ();
+ g_mutex_init (&glx_onscreen->swap_wait_mutex);
+ g_cond_init (&glx_onscreen->swap_wait_cond);
+ glx_onscreen->swap_wait_context =
+ glx_renderer->glXCreateNewContext (xlib_renderer->xdpy,
+ glx_display->fbconfig,
+ GLX_RGBA_TYPE,
+ glx_display->glx_context,
+ True);
+ glx_onscreen->swap_wait_thread = g_thread_new ("cogl_glx_swap_wait",
+ threaded_swap_wait,
+ onscreen);
+ }
+
+ g_mutex_lock (&glx_onscreen->swap_wait_mutex);
+ g_queue_push_head (glx_onscreen->swap_wait_queue, GUINT_TO_POINTER(vblank_counter));
+ g_cond_signal (&glx_onscreen->swap_wait_cond);
+ g_mutex_unlock (&glx_onscreen->swap_wait_mutex);
+}
+
static void
_cogl_winsys_onscreen_swap_region (CoglOnscreen *onscreen,
const int *user_rectangles,
@@ -2000,19 +2253,38 @@ _cogl_winsys_onscreen_swap_buffers_with_damage (CoglOnscreen *onscreen,
if (framebuffer->config.swap_throttled)
{
- uint32_t end_frame_vsync_counter = 0;
-
have_counter = glx_display->have_vblank_counter;
- /* If the swap_region API is also being used then we need to track
- * the vsync counter for each swap request so we can manually
- * throttle swap_region requests. */
- if (have_counter)
- end_frame_vsync_counter = _cogl_winsys_get_vsync_counter (context);
-
- if (!glx_renderer->glXSwapInterval)
+ if (glx_renderer->glXSwapInterval)
{
- CoglBool can_wait = glx_display->can_vblank_wait;
+ if (_cogl_has_private_feature (context, COGL_PRIVATE_FEATURE_THREADED_SWAP_WAIT))
+ {
+ /* If we didn't wait for the GPU here, then it's easy to get the case
+ * where there is a VBlank between the point where we get the vsync counter
+ * and the point where the GPU is ready to actually perform the glXSwapBuffers(),
+ * and the swap wait terminates at the first VBlank rather than the one
+ * where the swap buffers happens. Calling glFinish() here makes this a
+ * rare race since the GPU is already ready to swap when we call glXSwapBuffers().
+ * The glFinish() also prevents any serious damage if the rare race happens,
+ * since it will wait for the preceding glXSwapBuffers() and prevent us from
+ * getting premanently ahead. (For NVIDIA drivers, glFinish() after glXSwapBuffers()
+ * waits for the buffer swap to happen.)
+ */
+ _cogl_winsys_wait_for_gpu (onscreen);
+ start_threaded_swap_wait (onscreen, _cogl_winsys_get_vsync_counter (context));
+ }
+ }
+ else
+ {
+ CoglBool can_wait = have_counter || glx_display->can_vblank_wait;
+
+ uint32_t end_frame_vsync_counter = 0;
+
+ /* If the swap_region API is also being used then we need to track
+ * the vsync counter for each swap request so we can manually
+ * throttle swap_region requests. */
+ if (have_counter)
+ end_frame_vsync_counter = _cogl_winsys_get_vsync_counter (context);
/* If we are going to wait for VBLANK manually, we not only
* need to flush out pending drawing to the GPU before we
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]