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