/* 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 "compositor.h" #include "core/outputbackend.h" #include "core/renderbackend.h" #include "main.h" #include "scene/workspacescene.h" #include "utils/common.h" #include "opengl/glplatform.h" #include "platformsupport/scenes/opengl/openglbackend.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(RenderBackend *backend) { if (kwinApp()->operationMode() != Application::OperationModeX11) { return nullptr; } if (Compositor::self()->backend()->compositingType() != OpenGLCompositing) { return nullptr; } const auto context = static_cast(backend)->openglContext(); if (context->hasOpenglExtension("GL_EXT_x11_sync_object") && context->haveSyncFences()) { 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