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