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