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