232 lines
6.3 KiB
C++
232 lines
6.3 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2014 Fredrik Höglund <fredrik@kde.org>
|
|
|
|
Explicit command stream synchronization based on the sample implementation by James Jones <jajones@nvidia.com>,
|
|
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"
|
|
|
|
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;
|
|
}
|
|
|
|
if (Compositor::self()->backend()->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<int>(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
|