From 1c94e1bfc402f50c2b4a1c7f54561bf72d3ceb77 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Fri, 11 Jun 2021 14:11:57 +0300 Subject: [PATCH] scenes/opengl: Move out X11 explicit sync This moves X11 specific code to a better place, which allows us make item rendering code less platform specific and easier to change. --- src/CMakeLists.txt | 1 + src/composite.cpp | 25 +- src/composite.h | 7 +- src/plugins/scenes/opengl/scene_opengl.cpp | 271 --------------------- src/plugins/scenes/opengl/scene_opengl.h | 7 - src/scene.cpp | 4 - src/scene.h | 2 - src/surfaceitem_x11.cpp | 12 + src/surfaceitem_x11.h | 2 + src/x11syncmanager.cpp | 232 ++++++++++++++++++ src/x11syncmanager.h | 69 ++++++ 11 files changed, 346 insertions(+), 286 deletions(-) create mode 100644 src/x11syncmanager.cpp create mode 100644 src/x11syncmanager.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 09f9bac502..69270754e5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,6 +142,7 @@ set(kwin_SRCS workspace.cpp x11client.cpp x11eventfilter.cpp + x11syncmanager.cpp xcbutils.cpp xcursortheme.cpp xdgactivationv1.cpp diff --git a/src/composite.cpp b/src/composite.cpp index 57c2d825f1..471707bf41 100644 --- a/src/composite.cpp +++ b/src/composite.cpp @@ -27,6 +27,7 @@ #include "utils.h" #include "wayland_server.h" #include "workspace.h" +#include "x11syncmanager.h" #include "xcbutils.h" #include @@ -694,6 +695,11 @@ X11Compositor::~X11Compositor() stop(); // this can't be called in the destructor of Compositor } +X11SyncManager *X11Compositor::syncManager() const +{ + return m_syncManager.data(); +} + void X11Compositor::toggleCompositing() { if (m_suspended) { @@ -773,6 +779,13 @@ void X11Compositor::start() return; } startupWithWorkspace(); + m_syncManager.reset(X11SyncManager::create()); +} + +void X11Compositor::stop() +{ + m_syncManager.reset(); + Compositor::stop(); } void X11Compositor::composite(RenderLoop *renderLoop) @@ -795,7 +808,9 @@ void X11Compositor::composite(RenderLoop *renderLoop) } if (dirtyItems.count() > 0) { - scene()->triggerFence(); + if (m_syncManager) { + m_syncManager->triggerFence(); + } xcb_flush(kwinApp()->x11Connection()); } @@ -810,6 +825,14 @@ void X11Compositor::composite(RenderLoop *renderLoop) Compositor::composite(renderLoop); + if (m_syncManager) { + if (!m_syncManager->endFrame()) { + qCDebug(KWIN_CORE) << "Aborting explicit synchronization with the X command stream."; + qCDebug(KWIN_CORE) << "Future frames will be rendered unsynchronized."; + m_syncManager.reset(); + } + } + if (m_framesToTestForSafety > 0) { if (scene()->compositingType() & OpenGLCompositing) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostFrame); diff --git a/src/composite.h b/src/composite.h index 5a44a792cf..4e720f559d 100644 --- a/src/composite.h +++ b/src/composite.h @@ -23,6 +23,7 @@ class CompositorSelectionOwner; class RenderLoop; class Scene; class X11Client; +class X11SyncManager; class KWIN_EXPORT Compositor : public QObject { @@ -96,7 +97,7 @@ protected: explicit Compositor(QObject *parent = nullptr); virtual void start() = 0; - void stop(); + virtual void stop(); /** * @brief Prepares start. @@ -177,6 +178,8 @@ public: static X11Compositor *create(QObject *parent = nullptr); ~X11Compositor() override; + X11SyncManager *syncManager() const; + /** * @brief Suspends the Compositor if it is currently active. * @@ -229,10 +232,12 @@ public: protected: void start() override; + void stop() override; void composite(RenderLoop *renderLoop) override; private: explicit X11Compositor(QObject *parent); + QScopedPointer m_syncManager; /** * Whether the Compositor is currently suspended, 8 bits encoding the reason */ diff --git a/src/plugins/scenes/opengl/scene_opengl.cpp b/src/plugins/scenes/opengl/scene_opengl.cpp index ca93a559c5..d9560045a3 100644 --- a/src/plugins/scenes/opengl/scene_opengl.cpp +++ b/src/plugins/scenes/opengl/scene_opengl.cpp @@ -9,11 +9,6 @@ Based on glcompmgr code by Felix Bellaby. Using code from Compiz and Beryl. - Explicit command stream synchronization based on the sample - implementation by James Jones , - - SPDX-FileCopyrightText: 2011 NVIDIA Corporation - SPDX-License-Identifier: GPL-2.0-or-later */ #include "scene_opengl.h" @@ -73,224 +68,6 @@ namespace KWin { -/** - * SyncObject represents a fence used to synchronize operations in - * the kwin command stream with operations in the X command stream. - */ -class SyncObject -{ -public: - enum State { Ready, TriggerSent, Waiting, Done, Resetting }; - - SyncObject(); - ~SyncObject(); - - State state() const { return m_state; } - - void trigger(); - void wait(); - bool finish(); - void reset(); - void finishResetting(); - -private: - State m_state; - GLsync m_sync; - xcb_sync_fence_t m_fence; - xcb_get_input_focus_cookie_t m_reset_cookie; -}; - -SyncObject::SyncObject() -{ - m_state = Ready; - - xcb_connection_t * const c = connection(); - - m_fence = xcb_generate_id(c); - xcb_sync_create_fence(c, rootWindow(), m_fence, false); - xcb_flush(c); - - m_sync = glImportSyncEXT(GL_SYNC_X11_FENCE_EXT, m_fence, 0); -} - -SyncObject::~SyncObject() -{ - // If glDeleteSync is called before the xcb fence is signalled - // the nvidia driver (the only one to implement GL_SYNC_X11_FENCE_EXT) - // deadlocks waiting for the fence to be signalled. - // To avoid this, make sure the fence is signalled before - // deleting the sync. - if (m_state == Resetting || m_state == Ready){ - trigger(); - // The flush is necessary! - // The trigger command needs to be sent to the X server. - xcb_flush(connection()); - } - xcb_sync_destroy_fence(connection(), m_fence); - glDeleteSync(m_sync); - - if (m_state == Resetting) - xcb_discard_reply(connection(), m_reset_cookie.sequence); -} - -void SyncObject::trigger() -{ - Q_ASSERT(m_state == Ready || m_state == Resetting); - - // Finish resetting the fence if necessary - if (m_state == Resetting) - finishResetting(); - - xcb_sync_trigger_fence(connection(), m_fence); - m_state = TriggerSent; -} - -void SyncObject::wait() -{ - if (m_state != TriggerSent) - return; - - glWaitSync(m_sync, 0, GL_TIMEOUT_IGNORED); - m_state = Waiting; -} - -bool SyncObject::finish() -{ - if (m_state == Done) - return true; - - // Note: It is possible that we never inserted a wait for the fence. - // This can happen if we ended up not rendering the damaged - // window because it is fully occluded. - Q_ASSERT(m_state == TriggerSent || m_state == Waiting); - - // Check if the fence is signaled - GLint value; - glGetSynciv(m_sync, GL_SYNC_STATUS, 1, nullptr, &value); - - if (value != GL_SIGNALED) { - qCDebug(KWIN_OPENGL) << "Waiting for X fence to finish"; - - // Wait for the fence to become signaled with a one second timeout - const GLenum result = glClientWaitSync(m_sync, 0, 1000000000); - - switch (result) { - case GL_TIMEOUT_EXPIRED: - qCWarning(KWIN_OPENGL) << "Timeout while waiting for X fence"; - return false; - - case GL_WAIT_FAILED: - qCWarning(KWIN_OPENGL) << "glClientWaitSync() failed"; - return false; - } - } - - m_state = Done; - return true; -} - -void SyncObject::reset() -{ - Q_ASSERT(m_state == Done); - - xcb_connection_t * const c = connection(); - - // Send the reset request along with a sync request. - // We use the cookie to ensure that the server has processed the reset - // request before we trigger the fence and call glWaitSync(). - // Otherwise there is a race condition between the reset finishing and - // the glWaitSync() call. - xcb_sync_reset_fence(c, m_fence); - m_reset_cookie = xcb_get_input_focus(c); - xcb_flush(c); - - m_state = Resetting; -} - -void SyncObject::finishResetting() -{ - Q_ASSERT(m_state == Resetting); - free(xcb_get_input_focus_reply(connection(), m_reset_cookie, nullptr)); - m_state = Ready; -} - - - -// ----------------------------------------------------------------------- - - - -/** - * SyncManager manages a set of fences used for explicit synchronization - * with the X command stream. - */ -class SyncManager -{ -public: - enum { MaxFences = 4 }; - - SyncManager(); - ~SyncManager(); - - SyncObject *nextFence(); - bool updateFences(); - -private: - std::array m_fences; - int m_next; -}; - -SyncManager::SyncManager() - : m_next(0) -{ -} - -SyncManager::~SyncManager() -{ -} - -SyncObject *SyncManager::nextFence() -{ - SyncObject *fence = &m_fences[m_next]; - m_next = (m_next + 1) % MaxFences; - return fence; -} - -bool SyncManager::updateFences() -{ - for (int i = 0; i < qMin(2, MaxFences - 1); i++) { - const int index = (m_next + i) % MaxFences; - SyncObject &fence = m_fences[index]; - - switch (fence.state()) { - case SyncObject::Ready: - break; - - case SyncObject::TriggerSent: - case SyncObject::Waiting: - if (!fence.finish()) - return false; - fence.reset(); - break; - - // Should not happen in practice since we always reset the fence - // after finishing it - case SyncObject::Done: - fence.reset(); - break; - - case SyncObject::Resetting: - fence.finishResetting(); - break; - } - } - - return true; -} - - -// ----------------------------------------------------------------------- - /************************************************ * SceneOpenGL ***********************************************/ @@ -299,8 +76,6 @@ SceneOpenGL::SceneOpenGL(OpenGLBackend *backend, QObject *parent) : Scene(parent) , init_ok(true) , m_backend(backend) - , m_syncManager(nullptr) - , m_currentFence(nullptr) { if (m_backend->isFailed()) { init_ok = false; @@ -330,21 +105,6 @@ SceneOpenGL::SceneOpenGL(OpenGLBackend *backend, QObject *parent) if (options->isGlStrictBindingFollowsDriver()) { options->setGlStrictBinding(!glPlatform->supports(LooseBinding)); } - - bool haveSyncObjects = glPlatform->isGLES() - ? hasGLVersion(3, 0) - : hasGLVersion(3, 2) || hasGLExtension("GL_ARB_sync"); - - if (hasGLExtension("GL_EXT_x11_sync_object") && haveSyncObjects && kwinApp()->operationMode() == Application::OperationModeX11) { - const QByteArray useExplicitSync = qgetenv("KWIN_EXPLICIT_SYNC"); - - if (useExplicitSync != "0") { - qCDebug(KWIN_OPENGL) << "Initializing fences for synchronization with the X command stream"; - m_syncManager = new SyncManager; - } else { - qCDebug(KWIN_OPENGL) << "Explicit synchronization with the X command stream disabled by environment variable"; - } - } } SceneOpenGL::~SceneOpenGL() @@ -354,8 +114,6 @@ SceneOpenGL::~SceneOpenGL() } SceneOpenGL::EffectFrame::cleanup(); - delete m_syncManager; - // backend might be still needed for a different scene delete m_backend; } @@ -514,22 +272,6 @@ void SceneOpenGL::handleGraphicsReset(GLenum status) m_resetOccurred = true; } - -void SceneOpenGL::triggerFence() -{ - if (m_syncManager) { - m_currentFence = m_syncManager->nextFence(); - m_currentFence->trigger(); - } -} - -void SceneOpenGL::insertWait() -{ - if (m_currentFence && m_currentFence->state() != SyncObject::Waiting) { - m_currentFence->wait(); - } -} - /** * Render cursor texture in case hardware cursor is disabled. * Useful for screen recording apps or backends that can't do planes. @@ -711,16 +453,6 @@ void SceneOpenGL::paint(int screenId, const QRegion &damage, const QListendOfFrame(); m_backend->endFrame(screenId, valid, update); GLVertexBuffer::streamingBuffer()->framePosted(); - - if (m_currentFence) { - if (!m_syncManager->updateFences()) { - qCDebug(KWIN_OPENGL) << "Aborting explicit synchronization with the X command stream."; - qCDebug(KWIN_OPENGL) << "Future frames will be rendered unsynchronized."; - delete m_syncManager; - m_syncManager = nullptr; - } - m_currentFence = nullptr; - } } } @@ -1241,9 +973,6 @@ void OpenGLWindow::createRenderNode(Item *item, RenderContext *context) } else if (auto surfaceItem = qobject_cast(item)) { WindowQuadList quads = clipQuads(item, context); if (!quads.isEmpty()) { - if (!surfaceItem->damage().isEmpty()) { // only relevant on X11 - m_scene->insertWait(); - } SurfacePixmap *pixmap = surfaceItem->pixmap(); if (pixmap) { context->renderNodes.append(RenderNode{ diff --git a/src/plugins/scenes/opengl/scene_opengl.h b/src/plugins/scenes/opengl/scene_opengl.h index 2ba2b84134..8dacb9451d 100644 --- a/src/plugins/scenes/opengl/scene_opengl.h +++ b/src/plugins/scenes/opengl/scene_opengl.h @@ -23,8 +23,6 @@ namespace KWin { class LanczosFilter; class OpenGLBackend; -class SyncManager; -class SyncObject; class KWIN_EXPORT SceneOpenGL : public Scene @@ -45,15 +43,12 @@ public: bool supportsSurfacelessContext() const override; bool supportsNativeFence() const override; DecorationRenderer *createDecorationRenderer(Decoration::DecoratedClientImpl *impl) override; - void triggerFence() override; virtual QMatrix4x4 projectionMatrix() const = 0; bool animationsSupported() const override; PlatformSurfaceTexture *createPlatformSurfaceTextureInternal(SurfacePixmapInternal *pixmap) override; PlatformSurfaceTexture *createPlatformSurfaceTextureX11(SurfacePixmapX11 *pixmap) override; PlatformSurfaceTexture *createPlatformSurfaceTextureWayland(SurfacePixmapWayland *pixmap) override; - void insertWait(); - bool debug() const { return m_debug; } void initDebugOutput(); @@ -89,8 +84,6 @@ private: bool m_resetOccurred = false; bool m_debug; OpenGLBackend *m_backend; - SyncManager *m_syncManager; - SyncObject *m_currentFence; }; class SceneOpenGL2 : public SceneOpenGL diff --git a/src/scene.cpp b/src/scene.cpp index 5f4eec916b..9b4ad518a2 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -681,10 +681,6 @@ bool Scene::supportsNativeFence() const return false; } -void Scene::triggerFence() -{ -} - QMatrix4x4 Scene::screenProjectionMatrix() const { return QMatrix4x4(); diff --git a/src/scene.h b/src/scene.h index 7033577bed..7b8cd1782c 100644 --- a/src/scene.h +++ b/src/scene.h @@ -153,8 +153,6 @@ public: virtual QMatrix4x4 screenProjectionMatrix() const; - virtual void triggerFence(); - virtual DecorationRenderer *createDecorationRenderer(Decoration::DecoratedClientImpl *) = 0; /** diff --git a/src/surfaceitem_x11.cpp b/src/surfaceitem_x11.cpp index bce26234d5..4ffc775702 100644 --- a/src/surfaceitem_x11.cpp +++ b/src/surfaceitem_x11.cpp @@ -7,6 +7,7 @@ #include "surfaceitem_x11.h" #include "composite.h" #include "scene.h" +#include "x11syncmanager.h" namespace KWin { @@ -34,6 +35,17 @@ SurfaceItemX11::~SurfaceItemX11() { } +void SurfaceItemX11::preprocess() +{ + if (!damage().isEmpty()) { + X11Compositor *compositor = X11Compositor::self(); + if (X11SyncManager *syncManager = compositor->syncManager()) { + syncManager->insertWait(); + } + } + SurfaceItem::preprocess(); +} + void SurfaceItemX11::processDamage() { m_isDamaged = true; diff --git a/src/surfaceitem_x11.h b/src/surfaceitem_x11.h index ad2401dda3..55f539fede 100644 --- a/src/surfaceitem_x11.h +++ b/src/surfaceitem_x11.h @@ -22,6 +22,8 @@ public: explicit SurfaceItemX11(Scene::Window *window, Item *parent = nullptr); ~SurfaceItemX11() override; + void preprocess() override; + void processDamage(); bool fetchDamage(); void waitForDamage(); diff --git a/src/x11syncmanager.cpp b/src/x11syncmanager.cpp new file mode 100644 index 0000000000..83aae04c95 --- /dev/null +++ b/src/x11syncmanager.cpp @@ -0,0 +1,232 @@ +/* + SPDX-FileCopyrightText: 2014 Fredrik Höglund + + Explicit command stream synchronization based on the sample implementation by James Jones , + SPDX-FileCopyrightText: 2011 NVIDIA Corporation + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "x11syncmanager.h" +#include "composite.h" +#include "main.h" +#include "platform.h" +#include "scene.h" +#include "utils.h" + +#include "kwinglplatform.h" + +namespace KWin +{ + +X11SyncObject::X11SyncObject() +{ + m_state = Ready; + + xcb_connection_t *const connection = kwinApp()->x11Connection(); + m_fence = xcb_generate_id(connection); + xcb_sync_create_fence(connection, kwinApp()->x11RootWindow(), m_fence, false); + xcb_flush(connection); + + m_sync = glImportSyncEXT(GL_SYNC_X11_FENCE_EXT, m_fence, 0); +} + +X11SyncObject::~X11SyncObject() +{ + xcb_connection_t *const connection = kwinApp()->x11Connection(); + // If glDeleteSync is called before the xcb fence is signalled + // the nvidia driver (the only one to implement GL_SYNC_X11_FENCE_EXT) + // deadlocks waiting for the fence to be signalled. + // To avoid this, make sure the fence is signalled before + // deleting the sync. + if (m_state == Resetting || m_state == Ready){ + trigger(); + // The flush is necessary! + // The trigger command needs to be sent to the X server. + xcb_flush(connection); + } + xcb_sync_destroy_fence(connection, m_fence); + glDeleteSync(m_sync); + + if (m_state == Resetting) { + xcb_discard_reply(connection, m_reset_cookie.sequence); + } +} + +void X11SyncObject::trigger() +{ + Q_ASSERT(m_state == Ready || m_state == Resetting); + + // Finish resetting the fence if necessary + if (m_state == Resetting) { + finishResetting(); + } + + xcb_sync_trigger_fence(kwinApp()->x11Connection(), m_fence); + m_state = TriggerSent; +} + +void X11SyncObject::wait() +{ + if (m_state != TriggerSent) { + return; + } + + glWaitSync(m_sync, 0, GL_TIMEOUT_IGNORED); + m_state = Waiting; +} + +bool X11SyncObject::finish() +{ + if (m_state == Done) { + return true; + } + + // Note: It is possible that we never inserted a wait for the fence. + // This can happen if we ended up not rendering the damaged + // window because it is fully occluded. + Q_ASSERT(m_state == TriggerSent || m_state == Waiting); + + // Check if the fence is signaled + GLint value; + glGetSynciv(m_sync, GL_SYNC_STATUS, 1, nullptr, &value); + + if (value != GL_SIGNALED) { + qCDebug(KWIN_CORE) << "Waiting for X fence to finish"; + + // Wait for the fence to become signaled with a one second timeout + const GLenum result = glClientWaitSync(m_sync, 0, 1000000000); + + switch (result) { + case GL_TIMEOUT_EXPIRED: + qCWarning(KWIN_CORE) << "Timeout while waiting for X fence"; + return false; + + case GL_WAIT_FAILED: + qCWarning(KWIN_CORE) << "glClientWaitSync() failed"; + return false; + } + } + + m_state = Done; + return true; +} + +void X11SyncObject::reset() +{ + Q_ASSERT(m_state == Done); + + xcb_connection_t *const connection = kwinApp()->x11Connection(); + + // Send the reset request along with a sync request. + // We use the cookie to ensure that the server has processed the reset + // request before we trigger the fence and call glWaitSync(). + // Otherwise there is a race condition between the reset finishing and + // the glWaitSync() call. + xcb_sync_reset_fence(connection, m_fence); + m_reset_cookie = xcb_get_input_focus(connection); + xcb_flush(connection); + + m_state = Resetting; +} + +void X11SyncObject::finishResetting() +{ + Q_ASSERT(m_state == Resetting); + free(xcb_get_input_focus_reply(kwinApp()->x11Connection(), m_reset_cookie, nullptr)); + m_state = Ready; +} + +X11SyncManager *X11SyncManager::create() +{ + if (kwinApp()->operationMode() != Application::OperationModeX11) { + return nullptr; + } + + Scene *scene = Compositor::self()->scene(); + if (scene->compositingType() != OpenGLCompositing) { + return nullptr; + } + + GLPlatform *glPlatform = GLPlatform::instance(); + const bool haveSyncObjects = glPlatform->isGLES() + ? hasGLVersion(3, 0) + : hasGLVersion(3, 2) || hasGLExtension("GL_ARB_sync"); + + if (hasGLExtension("GL_EXT_x11_sync_object") && haveSyncObjects) { + const QString useExplicitSync = qEnvironmentVariable("KWIN_EXPLICIT_SYNC"); + + if (useExplicitSync != QLatin1String("0")) { + qCDebug(KWIN_CORE) << "Initializing fences for synchronization with the X command stream"; + return new X11SyncManager; + } else { + qCDebug(KWIN_CORE) << "Explicit synchronization with the X command stream disabled by environment variable"; + } + } + return nullptr; +} + +X11SyncManager::X11SyncManager() +{ + for (int i = 0; i < MaxFences; ++i) { + m_fences.append(new X11SyncObject); + } +} + +X11SyncManager::~X11SyncManager() +{ + Compositor::self()->scene()->makeOpenGLContextCurrent(); + qDeleteAll(m_fences); +} + +bool X11SyncManager::endFrame() +{ + if (!m_currentFence) { + return true; + } + + for (int i = 0; i < std::min(2, m_fences.count() - 1); i++) { + const int index = (m_next + i) % m_fences.count(); + X11SyncObject *fence = m_fences[index]; + + switch (fence->state()) { + case X11SyncObject::Ready: + break; + + case X11SyncObject::TriggerSent: + case X11SyncObject::Waiting: + if (!fence->finish()) { + return false; + } + fence->reset(); + break; + + // Should not happen in practice since we always reset the fence after finishing it + case X11SyncObject::Done: + fence->reset(); + break; + + case X11SyncObject::Resetting: + fence->finishResetting(); + break; + } + } + + m_currentFence = nullptr; + return true; +} + +void X11SyncManager::triggerFence() +{ + m_currentFence = m_fences[m_next]; + m_next = (m_next + 1) % m_fences.count(); + m_currentFence->trigger(); +} + +void X11SyncManager::insertWait() +{ + if (m_currentFence && m_currentFence->state() != X11SyncObject::Waiting) { + m_currentFence->wait(); + } +} + +} // namespace KWin diff --git a/src/x11syncmanager.h b/src/x11syncmanager.h new file mode 100644 index 0000000000..89425e5dc2 --- /dev/null +++ b/src/x11syncmanager.h @@ -0,0 +1,69 @@ +/* + SPDX-FileCopyrightText: 2014 Fredrik Höglund + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "kwinglutils.h" + +#include +#include + +namespace KWin +{ + +/** + * SyncObject represents a fence used to synchronize operations in the kwin command stream + * with operations in the X command stream. + */ +class X11SyncObject +{ +public: + enum State { Ready, TriggerSent, Waiting, Done, Resetting, }; + + X11SyncObject(); + ~X11SyncObject(); + + State state() const { return m_state; } + + void trigger(); + void wait(); + bool finish(); + void reset(); + void finishResetting(); + +private: + State m_state; + GLsync m_sync; + xcb_sync_fence_t m_fence; + xcb_get_input_focus_cookie_t m_reset_cookie; +}; + +/** + * SyncManager manages a set of fences used for explicit synchronization with the X command + * stream. + */ +class X11SyncManager +{ +public: + enum { MaxFences = 4 }; + + static X11SyncManager *create(); + ~X11SyncManager(); + + bool endFrame(); + + void triggerFence(); + void insertWait(); + +private: + X11SyncManager(); + + X11SyncObject *m_currentFence = nullptr; + QVector m_fences; + int m_next = 0; +}; + +} // namespace KWin