1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the plugins of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include <QDebug> |
41 | |
42 | #include "qxcbwindow.h" |
43 | #include "qxcbscreen.h" |
44 | |
45 | #define register /* C++17 deprecated register */ |
46 | #include <X11/Xlib.h> |
47 | #include <X11/Xutil.h> |
48 | #undef register |
49 | #include <GL/glx.h> |
50 | |
51 | #if QT_CONFIG(regularexpression) |
52 | # include <QtCore/QRegularExpression> |
53 | #endif |
54 | #include <QtGui/QOpenGLContext> |
55 | #include <QtGui/QOffscreenSurface> |
56 | |
57 | #include "qglxintegration.h" |
58 | #include <QtGlxSupport/private/qglxconvenience_p.h> |
59 | #include <QtPlatformHeaders/QGLXNativeContext> |
60 | |
61 | #include "qxcbglintegration.h" |
62 | |
63 | QT_BEGIN_NAMESPACE |
64 | |
65 | typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); |
66 | typedef const GLubyte *(*glGetStringiProc)(GLenum, GLuint); |
67 | |
68 | #ifndef GLX_CONTEXT_CORE_PROFILE_BIT_ARB |
69 | #define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 |
70 | #endif |
71 | |
72 | #ifndef GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB |
73 | #define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 |
74 | #endif |
75 | |
76 | #ifndef GLX_CONTEXT_ES2_PROFILE_BIT_EXT |
77 | #define GLX_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 |
78 | #endif |
79 | |
80 | #ifndef GLX_CONTEXT_PROFILE_MASK_ARB |
81 | #define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 |
82 | #endif |
83 | |
84 | #ifndef GL_CONTEXT_FLAG_DEBUG_BIT |
85 | #define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 |
86 | #endif |
87 | |
88 | #ifndef GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB |
89 | #define GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 |
90 | #endif |
91 | |
92 | #ifndef GL_RESET_NOTIFICATION_STRATEGY_ARB |
93 | #define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 |
94 | #endif |
95 | |
96 | #ifndef GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB |
97 | #define GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 |
98 | #endif |
99 | |
100 | #ifndef GL_LOSE_CONTEXT_ON_RESET_ARB |
101 | #define GL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 |
102 | #endif |
103 | |
104 | #ifndef GLX_LOSE_CONTEXT_ON_RESET_ARB |
105 | #define GLX_LOSE_CONTEXT_ON_RESET_ARB 0x8252 |
106 | #endif |
107 | |
108 | #ifndef GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV |
109 | #define GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV 0x20F7 |
110 | #endif |
111 | |
112 | static Window createDummyWindow(Display *dpy, XVisualInfo *visualInfo, int screenNumber, Window rootWin) |
113 | { |
114 | Colormap cmap = XCreateColormap(dpy, rootWin, visualInfo->visual, AllocNone); |
115 | XSetWindowAttributes a; |
116 | a.background_pixel = WhitePixel(dpy, screenNumber); |
117 | a.border_pixel = BlackPixel(dpy, screenNumber); |
118 | a.colormap = cmap; |
119 | a.override_redirect = true; |
120 | |
121 | Window window = XCreateWindow(dpy, rootWin, |
122 | 0, 0, 100, 100, |
123 | 0, visualInfo->depth, InputOutput, visualInfo->visual, |
124 | CWBackPixel|CWBorderPixel|CWColormap|CWOverrideRedirect, &a); |
125 | #ifndef QT_NO_DEBUG |
126 | XStoreName(dpy, window, "Qt GLX dummy window" ); |
127 | #endif |
128 | XFreeColormap(dpy, cmap); |
129 | return window; |
130 | } |
131 | |
132 | static Window createDummyWindow(Display *dpy, GLXFBConfig config, int screenNumber, Window rootWin) |
133 | { |
134 | XVisualInfo *visualInfo = glXGetVisualFromFBConfig(dpy, config); |
135 | if (Q_UNLIKELY(!visualInfo)) |
136 | qFatal(msg: "Could not initialize GLX" ); |
137 | Window window = createDummyWindow(dpy, visualInfo, screenNumber, rootWin); |
138 | XFree(visualInfo); |
139 | return window; |
140 | } |
141 | |
142 | static inline QByteArray getGlString(GLenum param) |
143 | { |
144 | if (const GLubyte *s = glGetString(name: param)) |
145 | return QByteArray(reinterpret_cast<const char*>(s)); |
146 | return QByteArray(); |
147 | } |
148 | |
149 | static bool hasGlExtension(const QSurfaceFormat &format, const char *ext) |
150 | { |
151 | if (format.majorVersion() < 3) { |
152 | auto exts = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)); |
153 | return exts && strstr(haystack: exts, needle: ext); |
154 | } else { |
155 | auto glGetStringi = reinterpret_cast<glGetStringiProc>( |
156 | glXGetProcAddress(procname: reinterpret_cast<const GLubyte*>("glGetStringi" ))); |
157 | if (glGetStringi) { |
158 | GLint n = 0; |
159 | glGetIntegerv(GL_NUM_EXTENSIONS, params: &n); |
160 | for (GLint i = 0; i < n; ++i) { |
161 | const char *p = reinterpret_cast<const char *>(glGetStringi(GL_EXTENSIONS, i)); |
162 | if (p && !strcmp(s1: p, s2: ext)) |
163 | return true; |
164 | } |
165 | } |
166 | return false; |
167 | } |
168 | } |
169 | |
170 | static void updateFormatFromContext(QSurfaceFormat &format) |
171 | { |
172 | // Update the version, profile, and context bit of the format |
173 | int major = 0, minor = 0; |
174 | QByteArray versionString(getGlString(GL_VERSION)); |
175 | if (QPlatformOpenGLContext::parseOpenGLVersion(versionString, major, minor)) { |
176 | format.setMajorVersion(major); |
177 | format.setMinorVersion(minor); |
178 | } |
179 | |
180 | format.setProfile(QSurfaceFormat::NoProfile); |
181 | const bool isStereo = format.testOption(option: QSurfaceFormat::StereoBuffers); |
182 | format.setOptions(QSurfaceFormat::FormatOptions()); |
183 | // Restore flags that come from the VisualInfo/FBConfig. |
184 | if (isStereo) |
185 | format.setOption(option: QSurfaceFormat::StereoBuffers); |
186 | |
187 | if (format.renderableType() == QSurfaceFormat::OpenGL) { |
188 | if (hasGlExtension(format, ext: "GL_ARB_robustness" )) { |
189 | GLint value = 0; |
190 | glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_ARB, params: &value); |
191 | if (value == GL_LOSE_CONTEXT_ON_RESET_ARB) |
192 | format.setOption(option: QSurfaceFormat::ResetNotification); |
193 | } |
194 | |
195 | if (format.version() < qMakePair(x: 3, y: 0)) { |
196 | format.setOption(option: QSurfaceFormat::DeprecatedFunctions); |
197 | return; |
198 | } |
199 | |
200 | // Version 3.0 onwards - check if it includes deprecated functionality or is |
201 | // a debug context |
202 | GLint value = 0; |
203 | glGetIntegerv(GL_CONTEXT_FLAGS, params: &value); |
204 | if (!(value & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT)) |
205 | format.setOption(option: QSurfaceFormat::DeprecatedFunctions); |
206 | if (value & GL_CONTEXT_FLAG_DEBUG_BIT) |
207 | format.setOption(option: QSurfaceFormat::DebugContext); |
208 | if (format.version() < qMakePair(x: 3, y: 2)) |
209 | return; |
210 | |
211 | // Version 3.2 and newer have a profile |
212 | value = 0; |
213 | glGetIntegerv(GL_CONTEXT_PROFILE_MASK, params: &value); |
214 | |
215 | if (value & GL_CONTEXT_CORE_PROFILE_BIT) |
216 | format.setProfile(QSurfaceFormat::CoreProfile); |
217 | else if (value & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) |
218 | format.setProfile(QSurfaceFormat::CompatibilityProfile); |
219 | } |
220 | } |
221 | |
222 | QGLXContext::QGLXContext(QXcbScreen *screen, const QSurfaceFormat &format, QPlatformOpenGLContext *share, |
223 | const QVariant &nativeHandle) |
224 | : QPlatformOpenGLContext() |
225 | , m_display(static_cast<Display *>(screen->connection()->xlib_display())) |
226 | , m_config(nullptr) |
227 | , m_context(nullptr) |
228 | , m_shareContext(nullptr) |
229 | , m_format(format) |
230 | , m_isPBufferCurrent(false) |
231 | , m_ownsContext(nativeHandle.isNull()) |
232 | , m_getGraphicsResetStatus(nullptr) |
233 | , m_lost(false) |
234 | { |
235 | if (nativeHandle.isNull()) |
236 | init(screen, share); |
237 | else |
238 | init(screen, share, nativeHandle); |
239 | } |
240 | |
241 | void QGLXContext::init(QXcbScreen *screen, QPlatformOpenGLContext *share) |
242 | { |
243 | if (m_format.renderableType() == QSurfaceFormat::DefaultRenderableType) |
244 | #if defined(QT_OPENGL_ES_2) |
245 | m_format.setRenderableType(QSurfaceFormat::OpenGLES); |
246 | #else |
247 | m_format.setRenderableType(QSurfaceFormat::OpenGL); |
248 | #endif |
249 | if (m_format.renderableType() != QSurfaceFormat::OpenGL && m_format.renderableType() != QSurfaceFormat::OpenGLES) |
250 | return; |
251 | |
252 | if (share) |
253 | m_shareContext = static_cast<const QGLXContext*>(share)->glxContext(); |
254 | |
255 | GLXFBConfig config = qglx_findConfig(display: m_display, screen: screen->screenNumber(), format: m_format); |
256 | m_config = config; |
257 | XVisualInfo *visualInfo = nullptr; |
258 | Window window = 0; // Temporary window used to query OpenGL context |
259 | |
260 | if (config) { |
261 | const QByteArrayList glxExt = QByteArray(glXQueryExtensionsString(dpy: m_display, screen: screen->screenNumber())).split(sep: ' '); |
262 | |
263 | // Resolve entry point for glXCreateContextAttribsARB |
264 | glXCreateContextAttribsARBProc glXCreateContextAttribsARB = nullptr; |
265 | if (glxExt.contains(t: "GLX_ARB_create_context" )) |
266 | glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) glXGetProcAddress(procname: (const GLubyte*)"glXCreateContextAttribsARB" ); |
267 | |
268 | const bool supportsProfiles = glxExt.contains(t: "GLX_ARB_create_context_profile" ); |
269 | const bool supportsRobustness = glxExt.contains(t: "GLX_ARB_create_context_robustness" ); |
270 | const bool supportsVideoMemoryPurge = glxExt.contains(t: "GLX_NV_robustness_video_memory_purge" ); |
271 | |
272 | // Use glXCreateContextAttribsARB if available |
273 | // Also, GL ES context creation requires GLX_EXT_create_context_es2_profile |
274 | if (glXCreateContextAttribsARB != nullptr |
275 | && (m_format.renderableType() != QSurfaceFormat::OpenGLES || (supportsProfiles && glxExt.contains(t: "GLX_EXT_create_context_es2_profile" )))) { |
276 | // Try to create an OpenGL context for each known OpenGL version in descending |
277 | // order from the requested version. |
278 | const int requestedVersion = m_format.majorVersion() * 10 + qMin(a: m_format.minorVersion(), b: 9); |
279 | |
280 | QVector<int> glVersions; |
281 | if (m_format.renderableType() == QSurfaceFormat::OpenGL) { |
282 | if (requestedVersion > 46) |
283 | glVersions << requestedVersion; |
284 | |
285 | // Don't bother with versions below 2.0 |
286 | glVersions << 46 << 45 << 44 << 43 << 42 << 41 << 40 << 33 << 32 << 31 << 30 << 21 << 20; |
287 | } else if (m_format.renderableType() == QSurfaceFormat::OpenGLES) { |
288 | if (requestedVersion > 32) |
289 | glVersions << requestedVersion; |
290 | |
291 | // Don't bother with versions below ES 2.0 |
292 | glVersions << 32 << 31 << 30 << 20; |
293 | // ES does not support any format option |
294 | m_format.setOptions(QSurfaceFormat::FormatOptions()); |
295 | } |
296 | // Robustness must match that of the shared context. |
297 | if (share && share->format().testOption(option: QSurfaceFormat::ResetNotification)) |
298 | m_format.setOption(option: QSurfaceFormat::ResetNotification); |
299 | Q_ASSERT(glVersions.count() > 0); |
300 | |
301 | for (int i = 0; !m_context && i < glVersions.count(); i++) { |
302 | const int version = glVersions[i]; |
303 | if (version > requestedVersion) |
304 | continue; |
305 | |
306 | const int majorVersion = version / 10; |
307 | const int minorVersion = version % 10; |
308 | |
309 | QVector<int> contextAttributes; |
310 | contextAttributes << GLX_CONTEXT_MAJOR_VERSION_ARB << majorVersion |
311 | << GLX_CONTEXT_MINOR_VERSION_ARB << minorVersion; |
312 | |
313 | |
314 | if (m_format.renderableType() == QSurfaceFormat::OpenGL) { |
315 | // If asking for OpenGL 3.2 or newer we should also specify a profile |
316 | if (version >= 32 && supportsProfiles) { |
317 | if (m_format.profile() == QSurfaceFormat::CoreProfile) |
318 | contextAttributes << GLX_CONTEXT_PROFILE_MASK_ARB << GLX_CONTEXT_CORE_PROFILE_BIT_ARB; |
319 | else |
320 | contextAttributes << GLX_CONTEXT_PROFILE_MASK_ARB << GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; |
321 | } |
322 | |
323 | int flags = 0; |
324 | |
325 | if (supportsRobustness) |
326 | flags |= GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB; |
327 | |
328 | if (m_format.testOption(option: QSurfaceFormat::DebugContext)) |
329 | flags |= GLX_CONTEXT_DEBUG_BIT_ARB; |
330 | |
331 | // A forward-compatible context may be requested for 3.0 and later |
332 | if (version >= 30 && !m_format.testOption(option: QSurfaceFormat::DeprecatedFunctions)) |
333 | flags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; |
334 | |
335 | if (flags != 0) |
336 | contextAttributes << GLX_CONTEXT_FLAGS_ARB << flags; |
337 | } else if (m_format.renderableType() == QSurfaceFormat::OpenGLES) { |
338 | contextAttributes << GLX_CONTEXT_PROFILE_MASK_ARB << GLX_CONTEXT_ES2_PROFILE_BIT_EXT; |
339 | } |
340 | |
341 | if (supportsRobustness && m_format.testOption(option: QSurfaceFormat::ResetNotification)) { |
342 | QVector<int> contextAttributesWithRobustness = contextAttributes; |
343 | contextAttributesWithRobustness << GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB << GLX_LOSE_CONTEXT_ON_RESET_ARB; |
344 | if (supportsVideoMemoryPurge) |
345 | contextAttributesWithRobustness << GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV << GL_TRUE; |
346 | |
347 | contextAttributesWithRobustness << None; |
348 | m_context = glXCreateContextAttribsARB(m_display, config, m_shareContext, true, |
349 | contextAttributesWithRobustness.data()); |
350 | // Context creation against a shared context may fail specifically due to this request, so try |
351 | // without before dropping sharing. |
352 | } |
353 | |
354 | if (m_context) { |
355 | m_getGraphicsResetStatus = reinterpret_cast<GLenum (QOPENGLF_APIENTRYP)()>(getProcAddress(procName: "glGetGraphicsResetStatusARB" )); |
356 | } else { |
357 | contextAttributes << None; |
358 | m_context = glXCreateContextAttribsARB(m_display, config, m_shareContext, true, contextAttributes.data()); |
359 | if (!m_context && m_shareContext) { |
360 | // re-try without a shared glx context |
361 | m_context = glXCreateContextAttribsARB(m_display, config, nullptr, true, contextAttributes.data()); |
362 | if (m_context) |
363 | m_shareContext = nullptr; |
364 | } |
365 | } |
366 | } |
367 | } |
368 | |
369 | // Could not create a context using glXCreateContextAttribsARB, falling back to glXCreateNewContext. |
370 | if (!m_context) { |
371 | // requesting an OpenGL ES context requires glXCreateContextAttribsARB, so bail out |
372 | if (m_format.renderableType() == QSurfaceFormat::OpenGLES) |
373 | return; |
374 | |
375 | m_context = glXCreateNewContext(dpy: m_display, config, GLX_RGBA_TYPE, shareList: m_shareContext, direct: true); |
376 | if (!m_context && m_shareContext) { |
377 | // re-try without a shared glx context |
378 | m_context = glXCreateNewContext(dpy: m_display, config, GLX_RGBA_TYPE, shareList: nullptr, direct: true); |
379 | if (m_context) |
380 | m_shareContext = nullptr; |
381 | } |
382 | } |
383 | |
384 | // Get the basic surface format details |
385 | if (m_context) |
386 | qglx_surfaceFormatFromGLXFBConfig(format: &m_format, display: m_display, config); |
387 | |
388 | // Create a temporary window so that we can make the new context current |
389 | window = createDummyWindow(dpy: m_display, config, screenNumber: screen->screenNumber(), rootWin: screen->root()); |
390 | } else { |
391 | // requesting an OpenGL ES context requires glXCreateContextAttribsARB, so bail out |
392 | if (m_format.renderableType() == QSurfaceFormat::OpenGLES) |
393 | return; |
394 | |
395 | // Note that m_format gets updated with the used surface format |
396 | visualInfo = qglx_findVisualInfo(display: m_display, screen: screen->screenNumber(), format: &m_format); |
397 | if (Q_UNLIKELY(!visualInfo)) |
398 | qFatal(msg: "Could not initialize GLX" ); |
399 | m_context = glXCreateContext(dpy: m_display, vis: visualInfo, shareList: m_shareContext, direct: true); |
400 | if (!m_context && m_shareContext) { |
401 | // re-try without a shared glx context |
402 | m_shareContext = nullptr; |
403 | m_context = glXCreateContext(dpy: m_display, vis: visualInfo, shareList: nullptr, direct: true); |
404 | } |
405 | |
406 | // Create a temporary window so that we can make the new context current |
407 | window = createDummyWindow(dpy: m_display, visualInfo, screenNumber: screen->screenNumber(), rootWin: screen->root()); |
408 | XFree(visualInfo); |
409 | } |
410 | |
411 | // Query the OpenGL version and profile |
412 | if (m_context && window) { |
413 | GLXContext prevContext = glXGetCurrentContext(); |
414 | GLXDrawable prevDrawable = glXGetCurrentDrawable(); |
415 | glXMakeCurrent(dpy: m_display, drawable: window, ctx: m_context); |
416 | updateFormatFromContext(format&: m_format); |
417 | |
418 | // Make our context non-current |
419 | glXMakeCurrent(dpy: m_display, drawable: prevDrawable, ctx: prevContext); |
420 | } |
421 | |
422 | // Destroy our temporary window |
423 | XDestroyWindow(m_display, window); |
424 | } |
425 | |
426 | void QGLXContext::init(QXcbScreen *screen, QPlatformOpenGLContext *share, const QVariant &nativeHandle) |
427 | { |
428 | if (!nativeHandle.canConvert<QGLXNativeContext>()) { |
429 | qWarning(msg: "QGLXContext: Requires a QGLXNativeContext" ); |
430 | return; |
431 | } |
432 | QGLXNativeContext handle = qvariant_cast<QGLXNativeContext>(v: nativeHandle); |
433 | GLXContext context = handle.context(); |
434 | if (!context) { |
435 | qWarning(msg: "QGLXContext: No GLXContext given" ); |
436 | return; |
437 | } |
438 | |
439 | // Use the provided Display, if available. If not, use our own. It may still work. |
440 | Display *dpy = handle.display(); |
441 | if (!dpy) |
442 | dpy = m_display; |
443 | |
444 | // Legacy contexts created using glXCreateContext are created using a visual |
445 | // and the FBConfig cannot be queried. The only way to adapt these contexts |
446 | // is to figure out the visual id. |
447 | XVisualInfo *vinfo = nullptr; |
448 | // If the VisualID is provided use it. |
449 | VisualID vid = handle.visualId(); |
450 | if (!vid) { |
451 | // In the absence of the VisualID figure it out from the window. |
452 | Window wnd = handle.window(); |
453 | if (wnd) { |
454 | XWindowAttributes attrs; |
455 | XGetWindowAttributes(dpy, wnd, &attrs); |
456 | vid = XVisualIDFromVisual(attrs.visual); |
457 | } |
458 | } |
459 | if (vid) { |
460 | XVisualInfo v; |
461 | v.screen = screen->screenNumber(); |
462 | v.visualid = vid; |
463 | int n = 0; |
464 | vinfo = XGetVisualInfo(dpy, VisualScreenMask | VisualIDMask, &v, &n); |
465 | if (n < 1) { |
466 | XFree(vinfo); |
467 | vinfo = nullptr; |
468 | } |
469 | } |
470 | |
471 | // For contexts created with an FBConfig using the modern functions providing the |
472 | // visual or window is not mandatory. Just query the config from the context. |
473 | GLXFBConfig config = nullptr; |
474 | if (!vinfo) { |
475 | int configId = 0; |
476 | if (glXQueryContext(dpy, ctx: context, GLX_FBCONFIG_ID, value: &configId) != Success) { |
477 | qWarning(msg: "QGLXContext: Failed to query config from the provided context" ); |
478 | return; |
479 | } |
480 | |
481 | GLXFBConfig *configs; |
482 | int numConfigs = 0; |
483 | static const int attribs[] = { GLX_FBCONFIG_ID, configId, None }; |
484 | configs = glXChooseFBConfig(dpy, screen: screen->screenNumber(), attribList: attribs, nitems: &numConfigs); |
485 | if (!configs || numConfigs < 1) { |
486 | qWarning(msg: "QGLXContext: Failed to find config" ); |
487 | return; |
488 | } |
489 | if (configs && numConfigs > 1) // this is suspicious so warn but let it continue |
490 | qWarning(msg: "QGLXContext: Multiple configs for FBConfig ID %d" , configId); |
491 | |
492 | config = configs[0]; |
493 | // Store the config. |
494 | m_config = config; |
495 | } |
496 | |
497 | Q_ASSERT(vinfo || config); |
498 | |
499 | int screenNumber = DefaultScreen(dpy); |
500 | Window window; |
501 | if (vinfo) |
502 | window = createDummyWindow(dpy, visualInfo: vinfo, screenNumber, RootWindow(dpy, screenNumber)); |
503 | else |
504 | window = createDummyWindow(dpy, config, screenNumber, RootWindow(dpy, screenNumber)); |
505 | if (!window) { |
506 | qWarning(msg: "QGLXContext: Failed to create dummy window" ); |
507 | return; |
508 | } |
509 | |
510 | // Update OpenGL version and buffer sizes in our format. |
511 | GLXContext prevContext = glXGetCurrentContext(); |
512 | GLXDrawable prevDrawable = glXGetCurrentDrawable(); |
513 | if (!glXMakeCurrent(dpy, drawable: window, ctx: context)) { |
514 | qWarning(msg: "QGLXContext: Failed to make provided context current" ); |
515 | return; |
516 | } |
517 | m_format = QSurfaceFormat(); |
518 | m_format.setRenderableType(QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL |
519 | ? QSurfaceFormat::OpenGL : QSurfaceFormat::OpenGLES); |
520 | updateFormatFromContext(format&: m_format); |
521 | if (vinfo) |
522 | qglx_surfaceFormatFromVisualInfo(format: &m_format, display: dpy, visualInfo: vinfo); |
523 | else |
524 | qglx_surfaceFormatFromGLXFBConfig(format: &m_format, display: dpy, config); |
525 | glXMakeCurrent(dpy, drawable: prevDrawable, ctx: prevContext); |
526 | XDestroyWindow(dpy, window); |
527 | |
528 | if (vinfo) |
529 | XFree(vinfo); |
530 | |
531 | // Success. Store the context. From this point on isValid() is true. |
532 | m_context = context; |
533 | |
534 | if (share) |
535 | m_shareContext = static_cast<const QGLXContext*>(share)->glxContext(); |
536 | } |
537 | |
538 | QGLXContext::~QGLXContext() |
539 | { |
540 | if (m_ownsContext) |
541 | glXDestroyContext(dpy: m_display, ctx: m_context); |
542 | } |
543 | |
544 | static QXcbScreen *screenForPlatformSurface(QPlatformSurface *surface) |
545 | { |
546 | QSurface::SurfaceClass surfaceClass = surface->surface()->surfaceClass(); |
547 | if (surfaceClass == QSurface::Window) { |
548 | return static_cast<QXcbScreen *>(static_cast<QXcbWindow *>(surface)->screen()); |
549 | } else if (surfaceClass == QSurface::Offscreen) { |
550 | return static_cast<QXcbScreen *>(static_cast<QGLXPbuffer *>(surface)->screen()); |
551 | } |
552 | return nullptr; |
553 | } |
554 | |
555 | QVariant QGLXContext::nativeHandle() const |
556 | { |
557 | return QVariant::fromValue<QGLXNativeContext>(value: QGLXNativeContext(m_context)); |
558 | } |
559 | |
560 | bool QGLXContext::makeCurrent(QPlatformSurface *surface) |
561 | { |
562 | bool success = false; |
563 | Q_ASSERT(surface->surface()->supportsOpenGL()); |
564 | |
565 | GLXDrawable glxDrawable = 0; |
566 | QSurface::SurfaceClass surfaceClass = surface->surface()->surfaceClass(); |
567 | if (surfaceClass == QSurface::Window) { |
568 | m_isPBufferCurrent = false; |
569 | QXcbWindow *window = static_cast<QXcbWindow *>(surface); |
570 | glxDrawable = window->xcb_window(); |
571 | success = glXMakeCurrent(dpy: m_display, drawable: glxDrawable, ctx: m_context); |
572 | m_lost = false; |
573 | if (m_getGraphicsResetStatus && m_getGraphicsResetStatus() != GL_NO_ERROR) { |
574 | m_lost = true; |
575 | success = false; |
576 | // Drop the surface. Will recreate on the next makeCurrent. |
577 | window->invalidateSurface(); |
578 | } |
579 | } else if (surfaceClass == QSurface::Offscreen) { |
580 | m_isPBufferCurrent = true; |
581 | QGLXPbuffer *pbuffer = static_cast<QGLXPbuffer *>(surface); |
582 | glxDrawable = pbuffer->pbuffer(); |
583 | success = glXMakeContextCurrent(dpy: m_display, draw: glxDrawable, read: glxDrawable, ctx: m_context); |
584 | m_lost = false; |
585 | if (m_getGraphicsResetStatus && m_getGraphicsResetStatus() != GL_NO_ERROR) { |
586 | m_lost = true; |
587 | success = false; |
588 | } |
589 | } |
590 | |
591 | if (success && surfaceClass == QSurface::Window) { |
592 | int interval = surface->format().swapInterval(); |
593 | QXcbWindow *window = static_cast<QXcbWindow *>(surface); |
594 | QXcbScreen *screen = screenForPlatformSurface(surface); |
595 | if (interval >= 0 && interval != window->swapInterval() && screen) { |
596 | typedef void (*qt_glXSwapIntervalEXT)(Display *, GLXDrawable, int); |
597 | typedef void (*qt_glXSwapIntervalMESA)(unsigned int); |
598 | static qt_glXSwapIntervalEXT glXSwapIntervalEXT = nullptr; |
599 | static qt_glXSwapIntervalMESA glXSwapIntervalMESA = nullptr; |
600 | static bool resolved = false; |
601 | if (!resolved) { |
602 | resolved = true; |
603 | QList<QByteArray> glxExt = QByteArray(glXQueryExtensionsString(dpy: m_display, |
604 | screen: screen->screenNumber())).split(sep: ' '); |
605 | if (glxExt.contains(t: "GLX_EXT_swap_control" )) |
606 | glXSwapIntervalEXT = (qt_glXSwapIntervalEXT) getProcAddress(procName: "glXSwapIntervalEXT" ); |
607 | if (glxExt.contains(t: "GLX_MESA_swap_control" )) |
608 | glXSwapIntervalMESA = (qt_glXSwapIntervalMESA) getProcAddress(procName: "glXSwapIntervalMESA" ); |
609 | } |
610 | if (glXSwapIntervalEXT) |
611 | glXSwapIntervalEXT(m_display, glxDrawable, interval); |
612 | else if (glXSwapIntervalMESA) |
613 | glXSwapIntervalMESA(interval); |
614 | window->setSwapInterval(interval); |
615 | } |
616 | } |
617 | |
618 | return success; |
619 | } |
620 | |
621 | void QGLXContext::doneCurrent() |
622 | { |
623 | if (m_isPBufferCurrent) |
624 | glXMakeContextCurrent(dpy: m_display, draw: 0, read: 0, ctx: nullptr); |
625 | else |
626 | glXMakeCurrent(dpy: m_display, drawable: 0, ctx: nullptr); |
627 | m_isPBufferCurrent = false; |
628 | } |
629 | |
630 | void QGLXContext::swapBuffers(QPlatformSurface *surface) |
631 | { |
632 | GLXDrawable glxDrawable = 0; |
633 | if (surface->surface()->surfaceClass() == QSurface::Offscreen) |
634 | glxDrawable = static_cast<QGLXPbuffer *>(surface)->pbuffer(); |
635 | else |
636 | glxDrawable = static_cast<QXcbWindow *>(surface)->xcb_window(); |
637 | glXSwapBuffers(dpy: m_display, drawable: glxDrawable); |
638 | |
639 | if (surface->surface()->surfaceClass() == QSurface::Window) { |
640 | QXcbWindow *platformWindow = static_cast<QXcbWindow *>(surface); |
641 | // OpenGL context might be bound to a non-gui thread use QueuedConnection to sync |
642 | // the window from the platformWindow's thread as QXcbWindow is no QObject, an |
643 | // event is sent to QXcbConnection. (this is faster than a metacall) |
644 | if (platformWindow->needsSync()) |
645 | platformWindow->postSyncWindowRequest(); |
646 | } |
647 | } |
648 | |
649 | QFunctionPointer QGLXContext::getProcAddress(const char *procName) |
650 | { |
651 | return glXGetProcAddress(procname: reinterpret_cast<const GLubyte *>(procName)); |
652 | } |
653 | |
654 | QSurfaceFormat QGLXContext::format() const |
655 | { |
656 | return m_format; |
657 | } |
658 | |
659 | bool QGLXContext::isSharing() const |
660 | { |
661 | return m_shareContext != nullptr; |
662 | } |
663 | |
664 | bool QGLXContext::isValid() const |
665 | { |
666 | return m_context != nullptr && !m_lost; |
667 | } |
668 | |
669 | bool QGLXContext::m_queriedDummyContext = false; |
670 | bool QGLXContext::m_supportsThreading = true; |
671 | |
672 | |
673 | // If this list grows to any significant size, change it a |
674 | // proper string table and make the implementation below use |
675 | // binary search. |
676 | static const char *qglx_threadedgl_blacklist_renderer[] = { |
677 | "Chromium" , // QTBUG-32225 (initialization fails) |
678 | nullptr |
679 | }; |
680 | |
681 | static const char *qglx_threadedgl_blacklist_vendor[] = { |
682 | "llvmpipe" , // QTCREATORBUG-10666 |
683 | "nouveau" , // https://bugs.freedesktop.org/show_bug.cgi?id=91632 |
684 | nullptr |
685 | }; |
686 | |
687 | void QGLXContext::queryDummyContext() |
688 | { |
689 | if (m_queriedDummyContext) |
690 | return; |
691 | m_queriedDummyContext = true; |
692 | |
693 | static bool skip = qEnvironmentVariableIsSet(varName: "QT_OPENGL_NO_SANITY_CHECK" ); |
694 | if (skip) |
695 | return; |
696 | |
697 | QOpenGLContext *oldContext = QOpenGLContext::currentContext(); |
698 | QSurface *oldSurface = nullptr; |
699 | if (oldContext) |
700 | oldSurface = oldContext->surface(); |
701 | |
702 | QScopedPointer<QSurface> surface; |
703 | Display *display = glXGetCurrentDisplay(); |
704 | if (!display) { |
705 | // FIXME: Since Qt 5.6 we don't need to check whether primary screen is NULL |
706 | if (QScreen *screen = QGuiApplication::primaryScreen()) { |
707 | QXcbScreen *xcbScreen = static_cast<QXcbScreen *>(screen->handle()); |
708 | display = static_cast<Display *>(xcbScreen->connection()->xlib_display()); |
709 | } |
710 | } |
711 | const char *glxvendor = glXGetClientString(dpy: display, GLX_VENDOR); |
712 | if (glxvendor && !strcmp(s1: glxvendor, s2: "ATI" )) { |
713 | QWindow *window = new QWindow; |
714 | window->resize(w: 64, h: 64); |
715 | window->setSurfaceType(QSurface::OpenGLSurface); |
716 | window->create(); |
717 | surface.reset(other: window); |
718 | } else { |
719 | QOffscreenSurface *offSurface = new QOffscreenSurface; |
720 | offSurface->create(); |
721 | surface.reset(other: offSurface); |
722 | } |
723 | |
724 | QOpenGLContext context; |
725 | if (!context.create() || !context.makeCurrent(surface: surface.data())) { |
726 | qWarning(msg: "QGLXContext: Failed to create dummy context" ); |
727 | m_supportsThreading = false; |
728 | return; |
729 | } |
730 | |
731 | m_supportsThreading = true; |
732 | |
733 | if (const char *renderer = (const char *) glGetString(GL_RENDERER)) { |
734 | for (int i = 0; qglx_threadedgl_blacklist_renderer[i]; ++i) { |
735 | if (strstr(haystack: renderer, needle: qglx_threadedgl_blacklist_renderer[i]) != nullptr) { |
736 | qCDebug(lcQpaGl).nospace() << "Multithreaded OpenGL disabled: " |
737 | "blacklisted renderer \"" |
738 | << qglx_threadedgl_blacklist_renderer[i] |
739 | << "\"" ; |
740 | m_supportsThreading = false; |
741 | break; |
742 | } |
743 | } |
744 | } |
745 | if (const char *vendor = (const char *) glGetString(GL_VENDOR)) { |
746 | for (int i = 0; qglx_threadedgl_blacklist_vendor[i]; ++i) { |
747 | if (strstr(haystack: vendor, needle: qglx_threadedgl_blacklist_vendor[i]) != nullptr) { |
748 | qCDebug(lcQpaGl).nospace() << "Multithreaded OpenGL disabled: " |
749 | "blacklisted vendor \"" |
750 | << qglx_threadedgl_blacklist_vendor[i] |
751 | << "\"" ; |
752 | m_supportsThreading = false; |
753 | break; |
754 | } |
755 | } |
756 | } |
757 | |
758 | if (glxvendor && m_supportsThreading) { |
759 | // Blacklist Mesa drivers due to QTCREATORBUG-10875 (crash in creator), |
760 | // QTBUG-34492 (flickering in fullscreen) and QTBUG-38221 |
761 | const char *mesaVersionStr = nullptr; |
762 | if (strstr(haystack: glxvendor, needle: "Mesa Project" ) != nullptr) { |
763 | mesaVersionStr = (const char *) glGetString(GL_VERSION); |
764 | m_supportsThreading = false; |
765 | } |
766 | |
767 | if (mesaVersionStr) { |
768 | // The issue was fixed in Xcb 1.11, but we can't check for that |
769 | // at runtime, so instead assume it fixed with recent Mesa versions |
770 | // released several years after the Xcb fix. |
771 | #if QT_CONFIG(regularexpression) |
772 | QRegularExpression versionTest(QStringLiteral("Mesa (\\d+)" )); |
773 | QRegularExpressionMatch result = versionTest.match(subject: QString::fromLatin1(str: mesaVersionStr)); |
774 | int versionNr = 0; |
775 | if (result.hasMatch()) |
776 | versionNr = result.captured(nth: 1).toInt(); |
777 | if (versionNr >= 17) { |
778 | // White-listed |
779 | m_supportsThreading = true; |
780 | } |
781 | #endif |
782 | } |
783 | if (!m_supportsThreading) { |
784 | qCDebug(lcQpaGl).nospace() << "Multithreaded OpenGL disabled: " |
785 | "blacklisted vendor \"Mesa Project\"" ; |
786 | } |
787 | } |
788 | |
789 | static bool nomultithread = qEnvironmentVariableIsSet(varName: "QT_XCB_NO_THREADED_OPENGL" ); |
790 | if (nomultithread) |
791 | m_supportsThreading = false; |
792 | |
793 | context.doneCurrent(); |
794 | if (oldContext && oldSurface) |
795 | oldContext->makeCurrent(surface: oldSurface); |
796 | |
797 | if (!m_supportsThreading) { |
798 | qCDebug(lcQpaGl) << "Force-enable multithreaded OpenGL by setting " |
799 | "environment variable QT_OPENGL_NO_SANITY_CHECK" ; |
800 | } |
801 | } |
802 | |
803 | bool QGLXContext::supportsThreading() |
804 | { |
805 | queryDummyContext(); |
806 | return m_supportsThreading; |
807 | } |
808 | |
809 | QGLXPbuffer::QGLXPbuffer(QOffscreenSurface *offscreenSurface) |
810 | : QPlatformOffscreenSurface(offscreenSurface) |
811 | , m_screen(static_cast<QXcbScreen *>(offscreenSurface->screen()->handle())) |
812 | , m_format(m_screen->surfaceFormatFor(format: offscreenSurface->requestedFormat())) |
813 | , m_display(static_cast<Display *>(m_screen->connection()->xlib_display())) |
814 | , m_pbuffer(0) |
815 | { |
816 | GLXFBConfig config = qglx_findConfig(display: m_display, screen: m_screen->screenNumber(), format: m_format); |
817 | |
818 | if (config) { |
819 | const int attributes[] = { |
820 | GLX_PBUFFER_WIDTH, offscreenSurface->size().width(), |
821 | GLX_PBUFFER_HEIGHT, offscreenSurface->size().height(), |
822 | GLX_LARGEST_PBUFFER, False, |
823 | GLX_PRESERVED_CONTENTS, False, |
824 | None |
825 | }; |
826 | |
827 | m_pbuffer = glXCreatePbuffer(dpy: m_display, config, attribList: attributes); |
828 | |
829 | if (m_pbuffer) |
830 | qglx_surfaceFormatFromGLXFBConfig(format: &m_format, display: m_display, config); |
831 | } |
832 | } |
833 | |
834 | QGLXPbuffer::~QGLXPbuffer() |
835 | { |
836 | if (m_pbuffer) |
837 | glXDestroyPbuffer(dpy: m_display, pbuf: m_pbuffer); |
838 | } |
839 | |
840 | |
841 | QT_END_NAMESPACE |
842 | |