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 test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtGui/QOpenGLFunctions> |
30 | #include <QtGui/QScreen> |
31 | #include <QtGui/QWindow> |
32 | #include <private/qguiapplication_p.h> |
33 | #include <qpa/qplatformintegration.h> |
34 | #include <qpa/qplatformnativeinterface.h> |
35 | #include <private/qopengl_p.h> |
36 | |
37 | #include <QtTest/QtTest> |
38 | |
39 | #include <QtCore/QSysInfo> |
40 | #include <QtCore/QLibraryInfo> |
41 | #include <QtCore/QScopedPointer> |
42 | #include <QtCore/QVariant> |
43 | #include <QtCore/QDebug> |
44 | #include <QtCore/QTextStream> |
45 | #include <QtCore/QJsonDocument> |
46 | |
47 | #include <algorithm> |
48 | |
49 | #define DUMP_CAPABILITY(str, integration, capability) \ |
50 | if (platformIntegration->hasCapability(QPlatformIntegration::capability)) \ |
51 | str << ' ' << #capability; |
52 | |
53 | QTextStream &operator<<(QTextStream &str, const QSize &s) |
54 | { |
55 | str << s.width() << 'x' << s.height(); |
56 | return str; |
57 | } |
58 | |
59 | QTextStream &operator<<(QTextStream &str, const QRect &r) |
60 | { |
61 | str << r.size() << '+' << r.x() << '+' << r.y(); |
62 | return str; |
63 | } |
64 | |
65 | QTextStream &operator<<(QTextStream &str, const QSizeF &s) |
66 | { |
67 | str << s.width() << 'x' << s.height(); |
68 | return str; |
69 | } |
70 | |
71 | QTextStream &operator<<(QTextStream &str, const QSurfaceFormat &format) |
72 | { |
73 | str << "Version: " << format.majorVersion() << '.' |
74 | << format.minorVersion() << " Profile: " << format.profile() |
75 | << " Swap behavior: " << format.swapBehavior() |
76 | << " Buffer size (RGB" ; |
77 | if (format.hasAlpha()) |
78 | str << 'A'; |
79 | str << "): " << format.redBufferSize() << ',' << format.greenBufferSize() |
80 | << ',' << format.blueBufferSize(); |
81 | if (format.hasAlpha()) |
82 | str << ',' << format.alphaBufferSize(); |
83 | if (const int dbs = format.depthBufferSize()) |
84 | str << " Depth buffer: " << dbs; |
85 | if (const int sbs = format.stencilBufferSize()) |
86 | str << " Stencil buffer: " << sbs; |
87 | const int samples = format.samples(); |
88 | if (samples > 0) |
89 | str << " Samples: " << samples; |
90 | return str; |
91 | } |
92 | |
93 | /* This test contains code from the qtdiag tool. Its purpose is to output the |
94 | * graphics configuration to the CI log and to verify that Open GL can be |
95 | * initialized for platforms on which the qopengl test is marked as |
96 | * insignificant. */ |
97 | |
98 | class tst_QOpenGlConfig : public QObject |
99 | { |
100 | Q_OBJECT |
101 | |
102 | private slots: |
103 | void initTestCase(); |
104 | void testConfiguration(); |
105 | void testGlConfiguration(); |
106 | void testBugList(); |
107 | void testDefaultWindowsBlacklist(); |
108 | }; |
109 | |
110 | static void dumpConfiguration(QTextStream &str) |
111 | { |
112 | const QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration(); |
113 | str << "\nBuild : " << QLibraryInfo::build() |
114 | << "\nPlatform : " << QGuiApplication::platformName() |
115 | << "\nOS : " << QSysInfo::prettyProductName() << " [" |
116 | << QSysInfo::kernelType() << " version " << QSysInfo::kernelVersion() << ']' |
117 | << "\nArchitecture : " << QSysInfo::currentCpuArchitecture() |
118 | << "\nCapabilities :" ; |
119 | DUMP_CAPABILITY(str, platformIntegration, ThreadedPixmaps) |
120 | DUMP_CAPABILITY(str, platformIntegration, OpenGL) |
121 | DUMP_CAPABILITY(str, platformIntegration, ThreadedOpenGL) |
122 | DUMP_CAPABILITY(str, platformIntegration, SharedGraphicsCache) |
123 | DUMP_CAPABILITY(str, platformIntegration, BufferQueueingOpenGL) |
124 | DUMP_CAPABILITY(str, platformIntegration, WindowMasks) |
125 | DUMP_CAPABILITY(str, platformIntegration, RasterGLSurface) |
126 | DUMP_CAPABILITY(str, platformIntegration, AllGLFunctionsQueryable) |
127 | str << '\n'; |
128 | |
129 | const QList<QScreen*> screens = QGuiApplication::screens(); |
130 | const int screenCount = screens.size(); |
131 | str << "\nScreens: " << screenCount << '\n'; |
132 | for (int s = 0; s < screenCount; ++s) { |
133 | const QScreen *screen = screens.at(i: s); |
134 | str << '#' << ' ' << s << " \"" << screen->name() << '"' |
135 | << " Depth: " << screen->depth() |
136 | << " Primary: " << (screen == QGuiApplication::primaryScreen() ? "yes" : "no" ) |
137 | << "\n Geometry: " << screen->geometry() << " Available: " << screen->availableGeometry(); |
138 | if (screen->geometry() != screen->virtualGeometry()) |
139 | str << "\n Virtual geometry: " << screen->virtualGeometry() << " Available: " << screen->availableVirtualGeometry(); |
140 | if (screen->virtualSiblings().size() > 1) |
141 | str << "\n " << screen->virtualSiblings().size() << " virtual siblings" ; |
142 | str << "\n Physical size: " << screen->physicalSize() << " mm" |
143 | << " Refresh: " << screen->refreshRate() << " Hz" |
144 | << "\n Physical DPI: " << screen->physicalDotsPerInchX() |
145 | << ',' << screen->physicalDotsPerInchY() |
146 | << " Logical DPI: " << screen->logicalDotsPerInchX() |
147 | << ',' << screen->logicalDotsPerInchY() |
148 | << "\n DevicePixelRatio: " << screen->devicePixelRatio() |
149 | << " Primary orientation: " << screen->primaryOrientation() |
150 | << "\n Orientation: " << screen->orientation() |
151 | << " Native orientation: " << screen->nativeOrientation() |
152 | << " OrientationUpdateMask: " << screen->orientationUpdateMask() << '\n'; |
153 | } |
154 | |
155 | // On Windows, this will provide addition GPU info similar to the output of dxdiag. |
156 | if (QGuiApplication::platformNativeInterface()) { |
157 | const QVariant gpuInfoV = QGuiApplication::platformNativeInterface()->property(name: "gpu" ); |
158 | if (gpuInfoV.type() == QVariant::Map) { |
159 | const QString description = gpuInfoV.toMap().value(QStringLiteral("printable" )).toString(); |
160 | if (!description.isEmpty()) |
161 | str << "\nGPU:\n" << description << "\n\n" ; |
162 | } |
163 | } |
164 | } |
165 | |
166 | void tst_QOpenGlConfig::initTestCase() |
167 | { |
168 | if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::OpenGL)) |
169 | QSKIP("OpenGL is not supported on this platform." ); |
170 | } |
171 | |
172 | void tst_QOpenGlConfig::testConfiguration() |
173 | { |
174 | QString result; |
175 | QTextStream str(&result); |
176 | dumpConfiguration(str); |
177 | |
178 | qDebug().noquote() << '\n' << result; |
179 | } |
180 | |
181 | static void dumpGlConfiguration(QOpenGLContext &context, QTextStream &str) |
182 | { |
183 | str << "Type : " ; |
184 | #ifdef QT_OPENGL_DYNAMIC |
185 | str << "Dynamic GL " ; |
186 | #endif |
187 | switch (context.openGLModuleType()) { |
188 | case QOpenGLContext::LibGL: |
189 | str << "LibGL" ; |
190 | break; |
191 | case QOpenGLContext::LibGLES: |
192 | str << "LibGLES" ; |
193 | break; |
194 | } |
195 | QOpenGLFunctions functions(&context); |
196 | |
197 | str << "\nVendor : " << reinterpret_cast<const char *>(functions.glGetString(GL_VENDOR)) |
198 | << "\nRenderer : " << reinterpret_cast<const char *>(functions.glGetString(GL_RENDERER)) |
199 | << "\nVersion : " << reinterpret_cast<const char *>(functions.glGetString(GL_VERSION)) |
200 | << "\nShading language : " << reinterpret_cast<const char *>(functions.glGetString(GL_SHADING_LANGUAGE_VERSION)) |
201 | << "\nFormat : " << context.format(); |
202 | |
203 | QList<QByteArray> extensionList = context.extensions().values(); |
204 | std::sort(first: extensionList.begin(), last: extensionList.end()); |
205 | const int extensionCount = extensionList.size(); |
206 | str << "\n\nFound " << extensionCount << " extensions:\n" ; |
207 | for (int e = 0; e < extensionCount; ++e) |
208 | str << ((e % 4) ? ' ' : '\n') << extensionList.at(i: e); |
209 | } |
210 | |
211 | void tst_QOpenGlConfig::testGlConfiguration() |
212 | { |
213 | QString result; |
214 | QTextStream str(&result); |
215 | |
216 | QWindow window; |
217 | window.setSurfaceType(QSurface::OpenGLSurface); |
218 | window.create(); |
219 | QOpenGLContext context; |
220 | QVERIFY(context.create()); |
221 | QVERIFY(context.makeCurrent(&window)); |
222 | dumpGlConfiguration(context, str); |
223 | context.doneCurrent(); |
224 | |
225 | qDebug().noquote() << '\n' << result; |
226 | |
227 | // fromContext either uses the current context or creates a temporary dummy one. |
228 | QOpenGLConfig::Gpu gpu = QOpenGLConfig::Gpu::fromContext(); |
229 | qDebug().noquote() << '\n' << "GL_VENDOR queried by QOpenGLConfig::Gpu:" << gpu.glVendor; |
230 | QVERIFY(!gpu.glVendor.isEmpty()); |
231 | } |
232 | |
233 | static inline QByteArray msgSetMismatch(const QSet<QString> &expected, |
234 | const QSet<QString> &actual) |
235 | { |
236 | const QString result = QStringList(expected.values()).join(sep: QLatin1Char(',')) |
237 | + QLatin1String(" != " ) |
238 | + QStringList(actual.values()).join(sep: QLatin1Char(',')); |
239 | return result.toLatin1(); |
240 | } |
241 | |
242 | void tst_QOpenGlConfig::testBugList() |
243 | { |
244 | // Check bug list parsing for some arbitrary NVidia card |
245 | // faking Windows OS. |
246 | const QString fileName = QFINDTESTDATA("buglist.json" ); |
247 | QVERIFY(!fileName.isEmpty()); |
248 | |
249 | QSet<QString> expectedFeatures; |
250 | expectedFeatures << "feature1" ; |
251 | |
252 | // adapter info |
253 | QVersionNumber driverVersion(QVector<int>() << 9 << 18 << 13 << 4460); |
254 | QOpenGLConfig::Gpu gpu = QOpenGLConfig::Gpu::fromDevice(vendorId: 0x10DE, deviceId: 0x0DE9, driverVersion, QByteArrayLiteral("Unknown" )); |
255 | |
256 | QSet<QString> actualFeatures = QOpenGLConfig::gpuFeatures(gpu, QStringLiteral("win" ), |
257 | kernelVersion: QVersionNumber(6, 3), QStringLiteral("7" ), fileName); |
258 | QVERIFY2(expectedFeatures == actualFeatures, |
259 | msgSetMismatch(expectedFeatures, actualFeatures)); |
260 | |
261 | // driver_description |
262 | gpu = QOpenGLConfig::Gpu::fromDevice(vendorId: 0xDEAD, deviceId: 0xBEEF, driverVersion, QByteArrayLiteral("Very Long And Special Driver Description" )); |
263 | actualFeatures = QOpenGLConfig::gpuFeatures(gpu, QStringLiteral("win" ), |
264 | kernelVersion: QVersionNumber(6, 3), QStringLiteral("8" ), fileName); |
265 | expectedFeatures = QSet<QString>() << "feature2" ; |
266 | QVERIFY2(expectedFeatures == actualFeatures, |
267 | msgSetMismatch(expectedFeatures, actualFeatures)); |
268 | |
269 | // os.release |
270 | gpu = QOpenGLConfig::Gpu::fromDevice(vendorId: 0xDEAD, deviceId: 0xBEEF, driverVersion, QByteArrayLiteral("WinVerTest" )); |
271 | actualFeatures = QOpenGLConfig::gpuFeatures(gpu, QStringLiteral("win" ), |
272 | kernelVersion: QVersionNumber(12, 34), QStringLiteral("10" ), fileName); |
273 | expectedFeatures = QSet<QString>() << "win10_feature" ; |
274 | QVERIFY2(expectedFeatures == actualFeatures, |
275 | msgSetMismatch(expectedFeatures, actualFeatures)); |
276 | |
277 | // gl_vendor |
278 | gpu = QOpenGLConfig::Gpu::fromGLVendor(QByteArrayLiteral("Somebody Else" )); |
279 | expectedFeatures.clear(); |
280 | actualFeatures = QOpenGLConfig::gpuFeatures(gpu, QStringLiteral("linux" ), |
281 | kernelVersion: QVersionNumber(1, 0), osVersion: QString(), fileName); |
282 | QVERIFY2(expectedFeatures == actualFeatures, |
283 | msgSetMismatch(expectedFeatures, actualFeatures)); |
284 | |
285 | gpu = QOpenGLConfig::Gpu::fromGLVendor(QByteArrayLiteral("The Qt Company" )); |
286 | expectedFeatures = QSet<QString>() << "cool_feature" ; |
287 | actualFeatures = QOpenGLConfig::gpuFeatures(gpu, QStringLiteral("linux" ), |
288 | kernelVersion: QVersionNumber(1, 0), osVersion: QString(), fileName); |
289 | QVERIFY2(expectedFeatures == actualFeatures, |
290 | msgSetMismatch(expectedFeatures, actualFeatures)); |
291 | } |
292 | |
293 | void tst_QOpenGlConfig::testDefaultWindowsBlacklist() |
294 | { |
295 | if (QGuiApplication::platformName().compare(other: QLatin1String("windows" ), cs: Qt::CaseInsensitive)) |
296 | QSKIP("Only applicable to Windows" ); |
297 | |
298 | QFile f(QStringLiteral(":/qt-project.org/windows/openglblacklists/default.json" )); |
299 | QVERIFY(f.open(QIODevice::ReadOnly | QIODevice::Text)); |
300 | QJsonParseError err; |
301 | QJsonDocument doc = QJsonDocument::fromJson(json: f.readAll(), error: &err); |
302 | QVERIFY2(err.error == 0, |
303 | QStringLiteral("Failed to parse built-in Windows GPU blacklist. %1 : %2" ) |
304 | .arg(err.offset).arg(err.errorString()).toLatin1()); |
305 | } |
306 | |
307 | QTEST_MAIN(tst_QOpenGlConfig) |
308 | |
309 | #include "tst_qopenglconfig.moc" |
310 | |