1 /*
2     SPDX-FileCopyrightText: 2014 Fredrik Höglund <fredrik@kde.org>
3 
4     Explicit command stream synchronization based on the sample implementation by James Jones <jajones@nvidia.com>,
5     SPDX-FileCopyrightText: 2011 NVIDIA Corporation
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 #include "x11syncmanager.h"
10 #include "composite.h"
11 #include "main.h"
12 #include "platform.h"
13 #include "scene.h"
14 #include "utils.h"
15 
16 #include "kwinglplatform.h"
17 
18 namespace KWin
19 {
20 
X11SyncObject()21 X11SyncObject::X11SyncObject()
22 {
23     m_state = Ready;
24 
25     xcb_connection_t *const connection = kwinApp()->x11Connection();
26     m_fence = xcb_generate_id(connection);
27     xcb_sync_create_fence(connection, kwinApp()->x11RootWindow(), m_fence, false);
28     xcb_flush(connection);
29 
30     m_sync = glImportSyncEXT(GL_SYNC_X11_FENCE_EXT, m_fence, 0);
31 }
32 
~X11SyncObject()33 X11SyncObject::~X11SyncObject()
34 {
35     xcb_connection_t *const connection = kwinApp()->x11Connection();
36     // If glDeleteSync is called before the xcb fence is signalled
37     // the nvidia driver (the only one to implement GL_SYNC_X11_FENCE_EXT)
38     // deadlocks waiting for the fence to be signalled.
39     // To avoid this, make sure the fence is signalled before
40     // deleting the sync.
41     if (m_state == Resetting || m_state == Ready){
42         trigger();
43         // The flush is necessary!
44         // The trigger command needs to be sent to the X server.
45         xcb_flush(connection);
46     }
47     xcb_sync_destroy_fence(connection, m_fence);
48     glDeleteSync(m_sync);
49 
50     if (m_state == Resetting) {
51         xcb_discard_reply(connection, m_reset_cookie.sequence);
52     }
53 }
54 
trigger()55 void X11SyncObject::trigger()
56 {
57     Q_ASSERT(m_state == Ready || m_state == Resetting);
58 
59     // Finish resetting the fence if necessary
60     if (m_state == Resetting) {
61         finishResetting();
62     }
63 
64     xcb_sync_trigger_fence(kwinApp()->x11Connection(), m_fence);
65     m_state = TriggerSent;
66 }
67 
wait()68 void X11SyncObject::wait()
69 {
70     if (m_state != TriggerSent) {
71         return;
72     }
73 
74     glWaitSync(m_sync, 0, GL_TIMEOUT_IGNORED);
75     m_state = Waiting;
76 }
77 
finish()78 bool X11SyncObject::finish()
79 {
80     if (m_state == Done) {
81         return true;
82     }
83 
84     // Note: It is possible that we never inserted a wait for the fence.
85     //       This can happen if we ended up not rendering the damaged
86     //       window because it is fully occluded.
87     Q_ASSERT(m_state == TriggerSent || m_state == Waiting);
88 
89     // Check if the fence is signaled
90     GLint value;
91     glGetSynciv(m_sync, GL_SYNC_STATUS, 1, nullptr, &value);
92 
93     if (value != GL_SIGNALED) {
94         qCDebug(KWIN_CORE) << "Waiting for X fence to finish";
95 
96         // Wait for the fence to become signaled with a one second timeout
97         const GLenum result = glClientWaitSync(m_sync, 0, 1000000000);
98 
99         switch (result) {
100         case GL_TIMEOUT_EXPIRED:
101             qCWarning(KWIN_CORE) << "Timeout while waiting for X fence";
102             return false;
103 
104         case GL_WAIT_FAILED:
105             qCWarning(KWIN_CORE) << "glClientWaitSync() failed";
106             return false;
107         }
108     }
109 
110     m_state = Done;
111     return true;
112 }
113 
reset()114 void X11SyncObject::reset()
115 {
116     Q_ASSERT(m_state == Done);
117 
118     xcb_connection_t *const connection = kwinApp()->x11Connection();
119 
120     // Send the reset request along with a sync request.
121     // We use the cookie to ensure that the server has processed the reset
122     // request before we trigger the fence and call glWaitSync().
123     // Otherwise there is a race condition between the reset finishing and
124     // the glWaitSync() call.
125     xcb_sync_reset_fence(connection, m_fence);
126     m_reset_cookie = xcb_get_input_focus(connection);
127     xcb_flush(connection);
128 
129     m_state = Resetting;
130 }
131 
finishResetting()132 void X11SyncObject::finishResetting()
133 {
134     Q_ASSERT(m_state == Resetting);
135     free(xcb_get_input_focus_reply(kwinApp()->x11Connection(), m_reset_cookie, nullptr));
136     m_state = Ready;
137 }
138 
create()139 X11SyncManager *X11SyncManager::create()
140 {
141     if (kwinApp()->operationMode() != Application::OperationModeX11) {
142         return nullptr;
143     }
144 
145     Scene *scene = Compositor::self()->scene();
146     if (scene->compositingType() != OpenGLCompositing) {
147         return nullptr;
148     }
149 
150     GLPlatform *glPlatform = GLPlatform::instance();
151     const bool haveSyncObjects = glPlatform->isGLES()
152         ? hasGLVersion(3, 0)
153         : hasGLVersion(3, 2) || hasGLExtension("GL_ARB_sync");
154 
155     if (hasGLExtension("GL_EXT_x11_sync_object") && haveSyncObjects) {
156         const QString useExplicitSync = qEnvironmentVariable("KWIN_EXPLICIT_SYNC");
157 
158         if (useExplicitSync != QLatin1String("0")) {
159             qCDebug(KWIN_CORE) << "Initializing fences for synchronization with the X command stream";
160             return new X11SyncManager;
161         } else {
162             qCDebug(KWIN_CORE) << "Explicit synchronization with the X command stream disabled by environment variable";
163         }
164     }
165     return nullptr;
166 }
167 
X11SyncManager()168 X11SyncManager::X11SyncManager()
169 {
170     for (int i = 0; i < MaxFences; ++i) {
171         m_fences.append(new X11SyncObject);
172     }
173 }
174 
~X11SyncManager()175 X11SyncManager::~X11SyncManager()
176 {
177     Compositor::self()->scene()->makeOpenGLContextCurrent();
178     qDeleteAll(m_fences);
179 }
180 
endFrame()181 bool X11SyncManager::endFrame()
182 {
183     if (!m_currentFence) {
184         return true;
185     }
186 
187     for (int i = 0; i < std::min(2, m_fences.count() - 1); i++) {
188         const int index = (m_next + i) % m_fences.count();
189         X11SyncObject *fence = m_fences[index];
190 
191         switch (fence->state()) {
192         case X11SyncObject::Ready:
193             break;
194 
195         case X11SyncObject::TriggerSent:
196         case X11SyncObject::Waiting:
197             if (!fence->finish()) {
198                 return false;
199             }
200             fence->reset();
201             break;
202 
203         // Should not happen in practice since we always reset the fence after finishing it
204         case X11SyncObject::Done:
205             fence->reset();
206             break;
207 
208         case X11SyncObject::Resetting:
209             fence->finishResetting();
210             break;
211         }
212     }
213 
214     m_currentFence = nullptr;
215     return true;
216 }
217 
triggerFence()218 void X11SyncManager::triggerFence()
219 {
220     m_currentFence = m_fences[m_next];
221     m_next = (m_next + 1) % m_fences.count();
222     m_currentFence->trigger();
223 }
224 
insertWait()225 void X11SyncManager::insertWait()
226 {
227     if (m_currentFence && m_currentFence->state() != X11SyncObject::Waiting) {
228         m_currentFence->wait();
229     }
230 }
231 
232 } // namespace KWin
233