kwin/src/x11syncmanager.cpp
Vlad Zahorodnii 48f943bd75 Introduce explicit base render backend type
The main idea behind the render backend is to decouple low level bits
from scenes. The end goal is to make the render backend provide render
targets where the scene can render.

Design-wise, such a split is more flexible than the current state, for
example we could start experimenting with using qtquick (assuming that
the legacy scene is properly encapsulated) or creating multiple scenes,
for example for each output layer, etc.

So far, the RenderBackend class only contains one getter, more stuff will
be moved from the Scene as it makes sense.
2021-11-11 08:55:29 +00:00

232 lines
6.2 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 "composite.h"
#include "main.h"
#include "platform.h"
#include "renderbackend.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;
}
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(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