diff --git a/plugins/platforms/x11/standalone/glxbackend.h b/plugins/platforms/x11/standalone/glxbackend.h --- a/plugins/platforms/x11/standalone/glxbackend.h +++ b/plugins/platforms/x11/standalone/glxbackend.h @@ -61,6 +61,31 @@ }; +// ------------------------------------------------------------------ + + +class AsyncSwapBuffersHandler : public QObject +{ + Q_OBJECT + +public: + AsyncSwapBuffersHandler(Display *dpy, GLXFBConfig config, GLXDrawable drawable, + GLXContext shareContext = nullptr); + ~AsyncSwapBuffersHandler() override final; + + void makeCurrent(); + void swapBuffers(GLsync fence); + +Q_SIGNALS: + void swapComplete(std::chrono::microseconds timestamp); + +private: + Display *m_dpy = nullptr; + GLXContext m_context = nullptr; + GLXDrawable m_drawable = None; +}; + + /** * @brief OpenGL Backend using GLX over an X overlay window. */ @@ -113,6 +138,8 @@ bool m_haveMESASwapControl = false; bool m_haveEXTSwapControl = false; Display *m_x11Display; + AsyncSwapBuffersHandler *m_asyncSwapHandler = nullptr; + QThread *m_asyncSwapThread = nullptr; friend class GlxTexture; }; diff --git a/plugins/platforms/x11/standalone/glxbackend.cpp b/plugins/platforms/x11/standalone/glxbackend.cpp --- a/plugins/platforms/x11/standalone/glxbackend.cpp +++ b/plugins/platforms/x11/standalone/glxbackend.cpp @@ -43,6 +43,7 @@ // Qt #include #include +#include #include #include // system @@ -105,6 +106,57 @@ +AsyncSwapBuffersHandler::AsyncSwapBuffersHandler(Display *dpy, + GLXFBConfig config, + GLXDrawable drawable, + GLXContext shareContext) + : QObject(), + m_dpy(dpy), + m_drawable(drawable) +{ + // Create the context we will make current to the other thread + const int attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, 2, + GLX_CONTEXT_MINOR_VERSION_ARB, 1, + 0 + }; + + m_context = glXCreateContextAttribsARB(m_dpy, config, shareContext, True /*direct*/, attribs); +} + +AsyncSwapBuffersHandler::~AsyncSwapBuffersHandler() +{ + glXDestroyContext(m_dpy, m_context); +} + +void AsyncSwapBuffersHandler::makeCurrent() +{ + glXMakeContextCurrent(m_dpy, m_drawable, m_drawable, m_context); +} + +void AsyncSwapBuffersHandler::swapBuffers(GLsync fence) +{ + const GLenum result = glClientWaitSync(fence, 0, GL_TIMEOUT_IGNORED); + if (result != GL_ALREADY_SIGNALED && result != GL_CONDITION_SATISFIED) { + qCWarning(KWIN_X11STANDALONE) << "glClientWaitSync() before buffer swap failed!"; + } + + glXSwapBuffers(m_dpy, m_drawable); + + const std::chrono::nanoseconds now = std::chrono::steady_clock::now().time_since_epoch(); + const std::chrono::microseconds timestamp = + std::chrono::duration_cast(now); + + emit swapComplete(timestamp); + + glDeleteSync(fence); +} + + +// ----------------------------------------------------------------------- + + + GlxBackend::GlxBackend(Display *display) : OpenGLBackend() , m_overlayWindow(kwinApp()->platform()->createOverlayWindow()) @@ -115,14 +167,21 @@ , m_bufferAge(0) , m_x11Display(display) { + // Ensures calls to glXSwapBuffers will always block until the next + // retrace when using the proprietary NVIDIA driver. This must be + // set before libGL.so is loaded. + setenv("__GL_MaxFramesAllowed", "1", true); + // Force initialization of GLX integration in the Qt's xcb backend // to make it call XESetWireToEvent callbacks, which is required // by Mesa when using DRI2. QOpenGLContext::supportsThreadedOpenGL(); } GlxBackend::~GlxBackend() { + m_asyncSwapThread->quit(); + if (isFailed()) { m_overlayWindow->destroy(); } @@ -146,6 +205,10 @@ overlayWindow()->destroy(); delete m_overlayWindow; + + m_asyncSwapThread->wait(); + delete m_asyncSwapHandler; + delete m_asyncSwapThread; } typedef void (*glXFuncPtr)(); @@ -213,6 +276,28 @@ qgetenv("KWIN_USE_INTEL_SWAP_EVENT") != QByteArrayLiteral("0")) { m_swapEventFilter = std::make_unique(window, glxWindow); glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); + } else { + m_asyncSwapHandler = new AsyncSwapBuffersHandler(display(), fbconfig, glxWindow, ctx); + QObject::connect(m_asyncSwapHandler, + &AsyncSwapBuffersHandler::swapComplete, + [this](std::chrono::microseconds timestamp) { + Q_UNUSED(timestamp) + + if (supportsBufferAge()) { + glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, + (GLuint *) &m_bufferAge); + } + + Compositor::self()->bufferSwapComplete(); + }); + + m_asyncSwapThread = new QThread; + m_asyncSwapHandler->moveToThread(m_asyncSwapThread); + m_asyncSwapThread->start(); + + QMetaObject::invokeMethod(m_asyncSwapHandler, + &AsyncSwapBuffersHandler::makeCurrent, + Qt::QueuedConnection); } setSupportsBufferAge(false); @@ -672,10 +757,19 @@ Compositor::self()->aboutToSwapBuffers(); } - glXSwapBuffers(display(), glxWindow); + if (m_asyncSwapHandler) { + GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + QMetaObject::invokeMethod(m_asyncSwapHandler, + [=]{ m_asyncSwapHandler->swapBuffers(fence); }, + Qt::QueuedConnection); + } else { + glXSwapBuffers(display(), glxWindow); - if (supportsBufferAge()) { - glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); + if (supportsBufferAge()) { + glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, + (GLuint *) &m_bufferAge); + } } } else if (m_haveMESACopySubBuffer) { for (const QRect &r : lastDamage()) { @@ -785,7 +879,7 @@ bool GlxBackend::hasSwapEvent() const { - return m_swapEventFilter != nullptr; + return m_swapEventFilter != nullptr || m_asyncSwapHandler != nullptr; } /********************************************************