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 "qwindowsopengltester.h"
41 #include "qwindowscontext.h"
42
43 #include <QtCore/qvariant.h>
44 #include <QtCore/qdebug.h>
45 #include <QtCore/qtextstream.h>
46 #include <QtCore/qcoreapplication.h>
47 #include <QtCore/qfile.h>
48 #include <QtCore/qfileinfo.h>
49 #include <QtCore/qstandardpaths.h>
50 #include <QtCore/qlibraryinfo.h>
51 #include <QtCore/qhash.h>
52
53 #ifndef QT_NO_OPENGL
54 #include <private/qopengl_p.h>
55 #endif
56
57 #include <QtCore/qt_windows.h>
58 #include <private/qsystemlibrary_p.h>
59 #include <d3d9.h>
60
61 QT_BEGIN_NAMESPACE
62
63 static const DWORD VENDOR_ID_AMD = 0x1002;
64
adapterIdentifierToGpuDescription(const D3DADAPTER_IDENTIFIER9 & adapterIdentifier)65 static GpuDescription adapterIdentifierToGpuDescription(const D3DADAPTER_IDENTIFIER9 &adapterIdentifier)
66 {
67 GpuDescription result;
68 result.vendorId = adapterIdentifier.VendorId;
69 result.deviceId = adapterIdentifier.DeviceId;
70 result.revision = adapterIdentifier.Revision;
71 result.subSysId = adapterIdentifier.SubSysId;
72 QVector<int> version(4, 0);
73 version[0] = HIWORD(adapterIdentifier.DriverVersion.HighPart); // Product
74 version[1] = LOWORD(adapterIdentifier.DriverVersion.HighPart); // Version
75 version[2] = HIWORD(adapterIdentifier.DriverVersion.LowPart); // Sub version
76 version[3] = LOWORD(adapterIdentifier.DriverVersion.LowPart); // build
77 result.driverVersion = QVersionNumber(version);
78 result.driverName = adapterIdentifier.Driver;
79 result.description = adapterIdentifier.Description;
80 return result;
81 }
82
83 class QDirect3D9Handle
84 {
85 public:
86 Q_DISABLE_COPY_MOVE(QDirect3D9Handle)
87
88 QDirect3D9Handle();
89 ~QDirect3D9Handle();
90
isValid() const91 bool isValid() const { return m_direct3D9 != nullptr; }
92
adapterCount() const93 UINT adapterCount() const { return m_direct3D9 ? m_direct3D9->GetAdapterCount() : 0u; }
94 bool retrieveAdapterIdentifier(UINT n, D3DADAPTER_IDENTIFIER9 *adapterIdentifier) const;
95
96 private:
97 QSystemLibrary m_d3d9lib;
98 IDirect3D9 *m_direct3D9 = nullptr;
99 };
100
QDirect3D9Handle()101 QDirect3D9Handle::QDirect3D9Handle() :
102 m_d3d9lib(QStringLiteral("d3d9"))
103 {
104 using PtrDirect3DCreate9 = IDirect3D9 *(WINAPI *)(UINT);
105
106 if (m_d3d9lib.load()) {
107 if (auto direct3DCreate9 = (PtrDirect3DCreate9)m_d3d9lib.resolve("Direct3DCreate9"))
108 m_direct3D9 = direct3DCreate9(D3D_SDK_VERSION);
109 }
110 }
111
~QDirect3D9Handle()112 QDirect3D9Handle::~QDirect3D9Handle()
113 {
114 if (m_direct3D9)
115 m_direct3D9->Release();
116 }
117
retrieveAdapterIdentifier(UINT n,D3DADAPTER_IDENTIFIER9 * adapterIdentifier) const118 bool QDirect3D9Handle::retrieveAdapterIdentifier(UINT n, D3DADAPTER_IDENTIFIER9 *adapterIdentifier) const
119 {
120 return m_direct3D9
121 && SUCCEEDED(m_direct3D9->GetAdapterIdentifier(n, 0, adapterIdentifier));
122 }
123
detect()124 GpuDescription GpuDescription::detect()
125 {
126 GpuDescription result;
127 QDirect3D9Handle direct3D9;
128 if (!direct3D9.isValid())
129 return result;
130
131 D3DADAPTER_IDENTIFIER9 adapterIdentifier;
132 bool isAMD = false;
133 // Adapter "0" is D3DADAPTER_DEFAULT which returns the default adapter. In
134 // multi-GPU, multi-screen setups this is the GPU that is associated with
135 // the "main display" in the Display Settings, and this is the GPU OpenGL
136 // and D3D uses by default. Therefore querying any additional adapters is
137 // futile and not useful for our purposes in general, except for
138 // identifying a few special cases later on.
139 if (direct3D9.retrieveAdapterIdentifier(0, &adapterIdentifier)) {
140 result = adapterIdentifierToGpuDescription(adapterIdentifier);
141 isAMD = result.vendorId == VENDOR_ID_AMD;
142 }
143
144 // Detect QTBUG-50371 (having AMD as the default adapter results in a crash
145 // when starting apps on a screen connected to the Intel card) by looking
146 // for a default AMD adapter and an additional non-AMD one.
147 if (isAMD) {
148 const UINT adapterCount = direct3D9.adapterCount();
149 for (UINT adp = 1; adp < adapterCount; ++adp) {
150 if (direct3D9.retrieveAdapterIdentifier(adp, &adapterIdentifier)
151 && adapterIdentifier.VendorId != VENDOR_ID_AMD) {
152 // Bingo. Now figure out the display for the AMD card.
153 DISPLAY_DEVICE dd;
154 memset(&dd, 0, sizeof(dd));
155 dd.cb = sizeof(dd);
156 for (int dev = 0; EnumDisplayDevices(nullptr, dev, &dd, 0); ++dev) {
157 if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
158 // DeviceName is something like \\.\DISPLAY1 which can be used to
159 // match with the MONITORINFOEX::szDevice queried by QWindowsScreen.
160 result.gpuSuitableScreen = QString::fromWCharArray(dd.DeviceName);
161 break;
162 }
163 }
164 break;
165 }
166 }
167 }
168
169 return result;
170 }
171
detectAll()172 QVector<GpuDescription> GpuDescription::detectAll()
173 {
174 QVector<GpuDescription> result;
175 QDirect3D9Handle direct3D9;
176 if (const UINT adapterCount = direct3D9.adapterCount()) {
177 for (UINT adp = 0; adp < adapterCount; ++adp) {
178 D3DADAPTER_IDENTIFIER9 adapterIdentifier;
179 if (direct3D9.retrieveAdapterIdentifier(adp, &adapterIdentifier))
180 result.append(adapterIdentifierToGpuDescription(adapterIdentifier));
181 }
182 }
183 return result;
184 }
185
186 #ifndef QT_NO_DEBUG_STREAM
operator <<(QDebug d,const GpuDescription & gd)187 QDebug operator<<(QDebug d, const GpuDescription &gd)
188 {
189 QDebugStateSaver s(d);
190 d.nospace();
191 d << Qt::hex << Qt::showbase << "GpuDescription(vendorId=" << gd.vendorId
192 << ", deviceId=" << gd.deviceId << ", subSysId=" << gd.subSysId
193 << Qt::dec << Qt::noshowbase << ", revision=" << gd.revision
194 << ", driver: " << gd.driverName
195 << ", version=" << gd.driverVersion << ", " << gd.description
196 << gd.gpuSuitableScreen << ')';
197 return d;
198 }
199 #endif // !QT_NO_DEBUG_STREAM
200
201 // Return printable string formatted like the output of the dxdiag tool.
toString() const202 QString GpuDescription::toString() const
203 {
204 QString result;
205 QTextStream str(&result);
206 str << " Card name : " << description
207 << "\n Driver Name : " << driverName
208 << "\n Driver Version : " << driverVersion.toString()
209 << "\n Vendor ID : 0x" << qSetPadChar(u'0')
210 << Qt::uppercasedigits << Qt::hex << qSetFieldWidth(4) << vendorId
211 << "\n Device ID : 0x" << qSetFieldWidth(4) << deviceId
212 << "\n SubSys ID : 0x" << qSetFieldWidth(8) << subSysId
213 << "\n Revision ID : 0x" << qSetFieldWidth(4) << revision
214 << Qt::dec;
215 if (!gpuSuitableScreen.isEmpty())
216 str << "\nGL windows forced to screen: " << gpuSuitableScreen;
217 return result;
218 }
219
toVariant() const220 QVariant GpuDescription::toVariant() const
221 {
222 QVariantMap result;
223 result.insert(QStringLiteral("vendorId"), QVariant(vendorId));
224 result.insert(QStringLiteral("deviceId"), QVariant(deviceId));
225 result.insert(QStringLiteral("subSysId"),QVariant(subSysId));
226 result.insert(QStringLiteral("revision"), QVariant(revision));
227 result.insert(QStringLiteral("driver"), QVariant(QLatin1String(driverName)));
228 result.insert(QStringLiteral("driverProduct"), QVariant(driverVersion.segmentAt(0)));
229 result.insert(QStringLiteral("driverVersion"), QVariant(driverVersion.segmentAt(1)));
230 result.insert(QStringLiteral("driverSubVersion"), QVariant(driverVersion.segmentAt(2)));
231 result.insert(QStringLiteral("driverBuild"), QVariant(driverVersion.segmentAt(3)));
232 result.insert(QStringLiteral("driverVersionString"), driverVersion.toString());
233 result.insert(QStringLiteral("description"), QVariant(QLatin1String(description)));
234 result.insert(QStringLiteral("printable"), QVariant(toString()));
235 return result;
236 }
237
requestedGlesRenderer()238 QWindowsOpenGLTester::Renderer QWindowsOpenGLTester::requestedGlesRenderer()
239 {
240 const char platformVar[] = "QT_ANGLE_PLATFORM";
241 if (qEnvironmentVariableIsSet(platformVar)) {
242 const QByteArray anglePlatform = qgetenv(platformVar);
243 if (anglePlatform == "d3d11")
244 return QWindowsOpenGLTester::AngleRendererD3d11;
245 if (anglePlatform == "d3d9")
246 return QWindowsOpenGLTester::AngleRendererD3d9;
247 if (anglePlatform == "warp")
248 return QWindowsOpenGLTester::AngleRendererD3d11Warp;
249 qCWarning(lcQpaGl) << "Invalid value set for " << platformVar << ": " << anglePlatform;
250 }
251 return QWindowsOpenGLTester::InvalidRenderer;
252 }
253
requestedRenderer()254 QWindowsOpenGLTester::Renderer QWindowsOpenGLTester::requestedRenderer()
255 {
256 const char openGlVar[] = "QT_OPENGL";
257 if (QCoreApplication::testAttribute(Qt::AA_UseOpenGLES)) {
258 const Renderer glesRenderer = QWindowsOpenGLTester::requestedGlesRenderer();
259 return glesRenderer != InvalidRenderer ? glesRenderer : Gles;
260 }
261 if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL))
262 return QWindowsOpenGLTester::DesktopGl;
263 if (QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL))
264 return QWindowsOpenGLTester::SoftwareRasterizer;
265 if (qEnvironmentVariableIsSet(openGlVar)) {
266 const QByteArray requested = qgetenv(openGlVar);
267 if (requested == "angle") {
268 const Renderer glesRenderer = QWindowsOpenGLTester::requestedGlesRenderer();
269 return glesRenderer != InvalidRenderer ? glesRenderer : Gles;
270 }
271 if (requested == "desktop")
272 return QWindowsOpenGLTester::DesktopGl;
273 if (requested == "software")
274 return QWindowsOpenGLTester::SoftwareRasterizer;
275 qCWarning(lcQpaGl) << "Invalid value set for " << openGlVar << ": " << requested;
276 }
277 return QWindowsOpenGLTester::InvalidRenderer;
278 }
279
resolveBugListFile(const QString & fileName)280 static inline QString resolveBugListFile(const QString &fileName)
281 {
282 if (QFileInfo(fileName).isAbsolute())
283 return fileName;
284 // Try QLibraryInfo::SettingsPath which is typically empty unless specified in qt.conf,
285 // then resolve via QStandardPaths::ConfigLocation.
286 const QString settingsPath = QLibraryInfo::location(QLibraryInfo::SettingsPath);
287 if (!settingsPath.isEmpty()) { // SettingsPath is empty unless specified in qt.conf.
288 const QFileInfo fi(settingsPath + u'/' + fileName);
289 if (fi.isFile())
290 return fi.absoluteFilePath();
291 }
292 return QStandardPaths::locate(QStandardPaths::ConfigLocation, fileName);
293 }
294
295 #ifndef QT_NO_OPENGL
296 typedef QHash<QOpenGLConfig::Gpu, QWindowsOpenGLTester::Renderers> SupportedRenderersCache;
Q_GLOBAL_STATIC(SupportedRenderersCache,supportedRenderersCache)297 Q_GLOBAL_STATIC(SupportedRenderersCache, supportedRenderersCache)
298 #endif
299
300 QWindowsOpenGLTester::Renderers QWindowsOpenGLTester::detectSupportedRenderers(const GpuDescription &gpu,
301 Renderer requested)
302 {
303 #if defined(QT_NO_OPENGL)
304 Q_UNUSED(gpu)
305 Q_UNUSED(requested)
306 return 0;
307 #else
308 QOpenGLConfig::Gpu qgpu = QOpenGLConfig::Gpu::fromDevice(gpu.vendorId, gpu.deviceId, gpu.driverVersion, gpu.description);
309 SupportedRenderersCache *srCache = supportedRenderersCache();
310 SupportedRenderersCache::const_iterator it = srCache->constFind(qgpu);
311 if (it != srCache->cend())
312 return *it;
313
314 QWindowsOpenGLTester::Renderers result(QWindowsOpenGLTester::AngleRendererD3d11
315 | QWindowsOpenGLTester::AngleRendererD3d9
316 | QWindowsOpenGLTester::AngleRendererD3d11Warp
317 | QWindowsOpenGLTester::SoftwareRasterizer);
318
319 // Don't test for GL if explicitly requested or GLES only is requested
320 if (requested == DesktopGl
321 || ((requested & GlesMask) == 0 && testDesktopGL())) {
322 result |= QWindowsOpenGLTester::DesktopGl;
323 }
324
325 QSet<QString> features; // empty by default -> nothing gets disabled
326 if (!qEnvironmentVariableIsSet("QT_NO_OPENGL_BUGLIST")) {
327 const char bugListFileVar[] = "QT_OPENGL_BUGLIST";
328 QString buglistFileName = QStringLiteral(":/qt-project.org/windows/openglblacklists/default.json");
329 if (qEnvironmentVariableIsSet(bugListFileVar)) {
330 const QString fileName = resolveBugListFile(QFile::decodeName(qgetenv(bugListFileVar)));
331 if (!fileName.isEmpty())
332 buglistFileName = fileName;
333 }
334 features = QOpenGLConfig::gpuFeatures(qgpu, buglistFileName);
335 }
336 qCDebug(lcQpaGl) << "GPU features:" << features;
337
338 if (features.contains(QStringLiteral("disable_desktopgl"))) { // Qt-specific
339 qCDebug(lcQpaGl) << "Disabling Desktop GL: " << gpu;
340 result &= ~QWindowsOpenGLTester::DesktopGl;
341 }
342 if (features.contains(QStringLiteral("disable_angle"))) { // Qt-specific keyword
343 qCDebug(lcQpaGl) << "Disabling ANGLE: " << gpu;
344 result &= ~QWindowsOpenGLTester::GlesMask;
345 } else {
346 if (features.contains(QStringLiteral("disable_d3d11"))) { // standard keyword
347 qCDebug(lcQpaGl) << "Disabling D3D11: " << gpu;
348 result &= ~QWindowsOpenGLTester::AngleRendererD3d11;
349 }
350 if (features.contains(QStringLiteral("disable_d3d9"))) { // Qt-specific
351 qCDebug(lcQpaGl) << "Disabling D3D9: " << gpu;
352 result &= ~QWindowsOpenGLTester::AngleRendererD3d9;
353 }
354 }
355 if (features.contains(QStringLiteral("disable_rotation"))) {
356 qCDebug(lcQpaGl) << "Disabling rotation: " << gpu;
357 result |= DisableRotationFlag;
358 }
359 if (features.contains(QStringLiteral("disable_program_cache"))) {
360 qCDebug(lcQpaGl) << "Disabling program cache: " << gpu;
361 result |= DisableProgramCacheFlag;
362 }
363 srCache->insert(qgpu, result);
364 return result;
365 #endif // !QT_NO_OPENGL
366 }
367
supportedRenderers(Renderer requested)368 QWindowsOpenGLTester::Renderers QWindowsOpenGLTester::supportedRenderers(Renderer requested)
369 {
370 const GpuDescription gpu = GpuDescription::detect();
371 const QWindowsOpenGLTester::Renderers result = detectSupportedRenderers(gpu, requested);
372 qCDebug(lcQpaGl) << __FUNCTION__ << gpu << requested << "renderer: " << result;
373 return result;
374 }
375
testDesktopGL()376 bool QWindowsOpenGLTester::testDesktopGL()
377 {
378 #if !defined(QT_NO_OPENGL)
379 typedef HGLRC (WINAPI *CreateContextType)(HDC);
380 typedef BOOL (WINAPI *DeleteContextType)(HGLRC);
381 typedef BOOL (WINAPI *MakeCurrentType)(HDC, HGLRC);
382 typedef PROC (WINAPI *WglGetProcAddressType)(LPCSTR);
383
384 HMODULE lib = nullptr;
385 HWND wnd = nullptr;
386 HDC dc = nullptr;
387 HGLRC context = nullptr;
388 LPCTSTR className = L"qtopengltest";
389
390 CreateContextType CreateContext = nullptr;
391 DeleteContextType DeleteContext = nullptr;
392 MakeCurrentType MakeCurrent = nullptr;
393 WglGetProcAddressType WGL_GetProcAddress = nullptr;
394
395 bool result = false;
396
397 // Test #1: Load opengl32.dll and try to resolve an OpenGL 2 function.
398 // This will typically fail on systems that do not have a real OpenGL driver.
399 lib = LoadLibraryA("opengl32.dll");
400 if (lib) {
401 CreateContext = reinterpret_cast<CreateContextType>(
402 reinterpret_cast<QFunctionPointer>(::GetProcAddress(lib, "wglCreateContext")));
403 if (!CreateContext)
404 goto cleanup;
405 DeleteContext = reinterpret_cast<DeleteContextType>(
406 reinterpret_cast<QFunctionPointer>(::GetProcAddress(lib, "wglDeleteContext")));
407 if (!DeleteContext)
408 goto cleanup;
409 MakeCurrent = reinterpret_cast<MakeCurrentType>(
410 reinterpret_cast<QFunctionPointer>(::GetProcAddress(lib, "wglMakeCurrent")));
411 if (!MakeCurrent)
412 goto cleanup;
413 WGL_GetProcAddress = reinterpret_cast<WglGetProcAddressType>(
414 reinterpret_cast<QFunctionPointer>(::GetProcAddress(lib, "wglGetProcAddress")));
415 if (!WGL_GetProcAddress)
416 goto cleanup;
417
418 WNDCLASS wclass;
419 wclass.cbClsExtra = 0;
420 wclass.cbWndExtra = 0;
421 wclass.hInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));
422 wclass.hIcon = nullptr;
423 wclass.hCursor = nullptr;
424 wclass.hbrBackground = HBRUSH(COLOR_BACKGROUND);
425 wclass.lpszMenuName = nullptr;
426 wclass.lpfnWndProc = DefWindowProc;
427 wclass.lpszClassName = className;
428 wclass.style = CS_OWNDC;
429 if (!RegisterClass(&wclass))
430 goto cleanup;
431 wnd = CreateWindow(className, L"qtopenglproxytest", WS_OVERLAPPED,
432 0, 0, 640, 480, nullptr, nullptr, wclass.hInstance, nullptr);
433 if (!wnd)
434 goto cleanup;
435 dc = GetDC(wnd);
436 if (!dc)
437 goto cleanup;
438
439 PIXELFORMATDESCRIPTOR pfd;
440 memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
441 pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
442 pfd.nVersion = 1;
443 pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_GENERIC_FORMAT;
444 pfd.iPixelType = PFD_TYPE_RGBA;
445 // Use the GDI functions. Under the hood this will call the wgl variants in opengl32.dll.
446 int pixelFormat = ChoosePixelFormat(dc, &pfd);
447 if (!pixelFormat)
448 goto cleanup;
449 if (!SetPixelFormat(dc, pixelFormat, &pfd))
450 goto cleanup;
451 context = CreateContext(dc);
452 if (!context)
453 goto cleanup;
454 if (!MakeCurrent(dc, context))
455 goto cleanup;
456
457 // Now that there is finally a context current, try doing something useful.
458
459 // Check the version. If we got 1.x then it's all hopeless and we can stop right here.
460 typedef const GLubyte * (APIENTRY * GetString_t)(GLenum name);
461 auto GetString = reinterpret_cast<GetString_t>(
462 reinterpret_cast<QFunctionPointer>(::GetProcAddress(lib, "glGetString")));
463 if (GetString) {
464 if (const char *versionStr = reinterpret_cast<const char *>(GetString(GL_VERSION))) {
465 const QByteArray version(versionStr);
466 const int majorDot = version.indexOf('.');
467 if (majorDot != -1) {
468 int minorDot = version.indexOf('.', majorDot + 1);
469 if (minorDot == -1)
470 minorDot = version.size();
471 const int major = version.mid(0, majorDot).toInt();
472 const int minor = version.mid(majorDot + 1, minorDot - majorDot - 1).toInt();
473 qCDebug(lcQpaGl, "Basic wglCreateContext gives version %d.%d", major, minor);
474 // Try to be as lenient as possible. Missing version, bogus values and
475 // such are all accepted. The driver may still be functional. Only
476 // check for known-bad cases, like versions "1.4.0 ...".
477 if (major == 1) {
478 result = false;
479 qCDebug(lcQpaGl, "OpenGL version too low");
480 }
481 }
482 }
483 } else {
484 result = false;
485 qCDebug(lcQpaGl, "OpenGL 1.x entry points not found");
486 }
487
488 // Check for a shader-specific function.
489 if (WGL_GetProcAddress("glCreateShader")) {
490 result = true;
491 qCDebug(lcQpaGl, "OpenGL 2.0 entry points available");
492 } else {
493 qCDebug(lcQpaGl, "OpenGL 2.0 entry points not found");
494 }
495 } else {
496 qCDebug(lcQpaGl, "Failed to load opengl32.dll");
497 }
498
499 cleanup:
500 if (MakeCurrent)
501 MakeCurrent(nullptr, nullptr);
502 if (context)
503 DeleteContext(context);
504 if (dc && wnd)
505 ReleaseDC(wnd, dc);
506 if (wnd) {
507 DestroyWindow(wnd);
508 UnregisterClass(className, GetModuleHandle(nullptr));
509 }
510 // No FreeLibrary. Some implementations, Mesa in particular, deadlock when trying to unload.
511
512 return result;
513 #else
514 return false;
515 #endif // !QT_NO_OPENGL
516 }
517
518 QT_END_NAMESPACE
519