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