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 "paintcommands.h" |
30 | #include <qbaselinetest.h> |
31 | #include <QDir> |
32 | #include <QPainter> |
33 | #include <QPdfWriter> |
34 | #include <QTemporaryFile> |
35 | #include <QProcess> |
36 | |
37 | #ifndef QT_NO_OPENGL |
38 | #include <QOpenGLFramebufferObjectFormat> |
39 | #include <QOpenGLContext> |
40 | #include <QOpenGLPaintDevice> |
41 | #endif |
42 | |
43 | #include <algorithm> |
44 | |
45 | #ifndef GL_RGB10 |
46 | #define GL_RGB10 0x8052 |
47 | #endif |
48 | |
49 | class tst_Lancelot : public QObject |
50 | { |
51 | Q_OBJECT |
52 | |
53 | public: |
54 | tst_Lancelot(); |
55 | |
56 | private: |
57 | enum GraphicsEngine { |
58 | Raster = 0, |
59 | OpenGL = 1, |
60 | Pdf = 2 |
61 | }; |
62 | |
63 | void setupTestSuite(const QStringList& blacklist = QStringList()); |
64 | void runTestSuite(GraphicsEngine engine, QImage::Format format, const QSurfaceFormat &contextFormat = QSurfaceFormat()); |
65 | void paint(QPaintDevice *device, GraphicsEngine engine, QImage::Format format, const QStringList &script, const QString &filePath); |
66 | |
67 | QStringList qpsFiles; |
68 | QHash<QString, QStringList> scripts; |
69 | QHash<QString, quint16> scriptChecksums; |
70 | QString scriptsDir; |
71 | |
72 | private slots: |
73 | void initTestCase(); |
74 | void cleanupTestCase() {} |
75 | |
76 | void testRasterARGB32PM_data(); |
77 | void testRasterARGB32PM(); |
78 | void testRasterRGB32_data(); |
79 | void testRasterRGB32(); |
80 | void testRasterARGB32_data(); |
81 | void testRasterARGB32(); |
82 | void testRasterRGB16_data(); |
83 | void testRasterRGB16(); |
84 | void testRasterA2RGB30PM_data(); |
85 | void testRasterA2RGB30PM(); |
86 | void testRasterBGR30_data(); |
87 | void testRasterBGR30(); |
88 | void testRasterARGB8565PM_data(); |
89 | void testRasterARGB8565PM(); |
90 | void testRasterGrayscale8_data(); |
91 | void testRasterGrayscale8(); |
92 | void testRasterRGBA64PM_data(); |
93 | void testRasterRGBA64PM(); |
94 | |
95 | void testPdf_data(); |
96 | void testPdf(); |
97 | |
98 | #ifndef QT_NO_OPENGL |
99 | void testOpenGL_data(); |
100 | void testOpenGL(); |
101 | void testOpenGLBGR30_data(); |
102 | void testOpenGLBGR30(); |
103 | void testCoreOpenGL_data(); |
104 | void testCoreOpenGL(); |
105 | private: |
106 | bool checkSystemGLSupport(); |
107 | bool checkSystemCoreGLSupport(); |
108 | #endif |
109 | }; |
110 | |
111 | tst_Lancelot::tst_Lancelot() |
112 | { |
113 | } |
114 | |
115 | void tst_Lancelot::initTestCase() |
116 | { |
117 | // Check and setup the environment. We treat failures because of test environment |
118 | // (e.g. script files not found) as just warnings, and not QFAILs, to avoid false negatives |
119 | // caused by environment or server instability |
120 | |
121 | QByteArray msg; |
122 | if (!QBaselineTest::connectToBaselineServer(msg: &msg)) |
123 | QSKIP(msg); |
124 | |
125 | QString baseDir = QFINDTESTDATA("scripts/text.qps" ); |
126 | scriptsDir = baseDir.left(n: baseDir.lastIndexOf(c: '/')) + '/'; |
127 | QDir qpsDir(scriptsDir); |
128 | qpsFiles = qpsDir.entryList(nameFilters: QStringList() << QLatin1String("*.qps" ), filters: QDir::Files | QDir::Readable); |
129 | if (qpsFiles.isEmpty()) { |
130 | QWARN("No qps script files found in " + qpsDir.path().toLatin1()); |
131 | QSKIP("Aborted due to errors." ); |
132 | } |
133 | |
134 | std::sort(first: qpsFiles.begin(), last: qpsFiles.end()); |
135 | foreach (const QString& fileName, qpsFiles) { |
136 | QFile file(scriptsDir + fileName); |
137 | file.open(flags: QFile::ReadOnly); |
138 | QByteArray cont = file.readAll(); |
139 | scripts.insert(akey: fileName, avalue: QString::fromUtf8(str: cont).split(sep: QLatin1Char('\n'), behavior: Qt::SkipEmptyParts)); |
140 | scriptChecksums.insert(akey: fileName, avalue: qChecksum(s: cont.constData(), len: cont.size())); |
141 | } |
142 | } |
143 | |
144 | |
145 | void tst_Lancelot::testRasterARGB32PM_data() |
146 | { |
147 | setupTestSuite(); |
148 | } |
149 | |
150 | |
151 | void tst_Lancelot::testRasterARGB32PM() |
152 | { |
153 | runTestSuite(engine: Raster, format: QImage::Format_ARGB32_Premultiplied); |
154 | } |
155 | |
156 | |
157 | void tst_Lancelot::testRasterARGB32_data() |
158 | { |
159 | setupTestSuite(); |
160 | } |
161 | |
162 | void tst_Lancelot::testRasterARGB32() |
163 | { |
164 | runTestSuite(engine: Raster, format: QImage::Format_ARGB32); |
165 | } |
166 | |
167 | |
168 | void tst_Lancelot::testRasterRGB32_data() |
169 | { |
170 | setupTestSuite(); |
171 | } |
172 | |
173 | |
174 | void tst_Lancelot::testRasterRGB32() |
175 | { |
176 | runTestSuite(engine: Raster, format: QImage::Format_RGB32); |
177 | } |
178 | |
179 | |
180 | void tst_Lancelot::testRasterRGB16_data() |
181 | { |
182 | setupTestSuite(); |
183 | } |
184 | |
185 | |
186 | void tst_Lancelot::testRasterRGB16() |
187 | { |
188 | runTestSuite(engine: Raster, format: QImage::Format_RGB16); |
189 | } |
190 | |
191 | |
192 | void tst_Lancelot::testRasterA2RGB30PM_data() |
193 | { |
194 | setupTestSuite(); |
195 | } |
196 | |
197 | |
198 | void tst_Lancelot::testRasterA2RGB30PM() |
199 | { |
200 | runTestSuite(engine: Raster, format: QImage::Format_A2RGB30_Premultiplied); |
201 | } |
202 | |
203 | |
204 | void tst_Lancelot::testRasterBGR30_data() |
205 | { |
206 | setupTestSuite(); |
207 | } |
208 | |
209 | |
210 | void tst_Lancelot::testRasterBGR30() |
211 | { |
212 | runTestSuite(engine: Raster, format: QImage::Format_BGR30); |
213 | } |
214 | |
215 | |
216 | void tst_Lancelot::testRasterARGB8565PM_data() |
217 | { |
218 | setupTestSuite(); |
219 | } |
220 | |
221 | void tst_Lancelot::testRasterARGB8565PM() |
222 | { |
223 | runTestSuite(engine: Raster, format: QImage::Format_ARGB8565_Premultiplied); |
224 | } |
225 | |
226 | |
227 | void tst_Lancelot::testRasterGrayscale8_data() |
228 | { |
229 | setupTestSuite(); |
230 | } |
231 | |
232 | void tst_Lancelot::testRasterGrayscale8() |
233 | { |
234 | runTestSuite(engine: Raster, format: QImage::Format_Grayscale8); |
235 | } |
236 | |
237 | |
238 | void tst_Lancelot::testRasterRGBA64PM_data() |
239 | { |
240 | setupTestSuite(); |
241 | } |
242 | |
243 | void tst_Lancelot::testRasterRGBA64PM() |
244 | { |
245 | runTestSuite(engine: Raster, format: QImage::Format_RGBA64_Premultiplied); |
246 | } |
247 | |
248 | |
249 | void tst_Lancelot::testPdf_data() |
250 | { |
251 | #ifdef Q_OS_MACOS |
252 | setupTestSuite(); |
253 | #else |
254 | QSKIP("Pdf testing only implemented for macOS" ); |
255 | #endif |
256 | } |
257 | |
258 | void tst_Lancelot::testPdf() |
259 | { |
260 | runTestSuite(engine: Pdf, format: QImage::Format_RGB32); |
261 | } |
262 | |
263 | |
264 | #ifndef QT_NO_OPENGL |
265 | bool tst_Lancelot::checkSystemGLSupport() |
266 | { |
267 | QWindow win; |
268 | win.setSurfaceType(QSurface::OpenGLSurface); |
269 | win.create(); |
270 | QOpenGLFramebufferObjectFormat fmt; |
271 | fmt.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); |
272 | fmt.setSamples(4); |
273 | QOpenGLContext ctx; |
274 | if (!ctx.create() || !ctx.makeCurrent(surface: &win)) |
275 | return false; |
276 | QOpenGLFramebufferObject fbo(800, 800, fmt); |
277 | if (!fbo.isValid() || !fbo.bind()) |
278 | return false; |
279 | |
280 | return true; |
281 | } |
282 | |
283 | bool tst_Lancelot::checkSystemCoreGLSupport() |
284 | { |
285 | if (QOpenGLContext::openGLModuleType() != QOpenGLContext::LibGL) |
286 | return false; |
287 | |
288 | QSurfaceFormat coreFormat; |
289 | coreFormat.setVersion(major: 3, minor: 2); |
290 | coreFormat.setProfile(QSurfaceFormat::CoreProfile); |
291 | QWindow win; |
292 | win.setSurfaceType(QSurface::OpenGLSurface); |
293 | win.setFormat(coreFormat); |
294 | win.create(); |
295 | QOpenGLFramebufferObjectFormat fmt; |
296 | fmt.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); |
297 | fmt.setSamples(4); |
298 | QOpenGLContext ctx; |
299 | ctx.setFormat(coreFormat); |
300 | if (!ctx.create() || !ctx.makeCurrent(surface: &win)) |
301 | return false; |
302 | QOpenGLFramebufferObject fbo(800, 800, fmt); |
303 | if (!fbo.isValid() || !fbo.bind()) |
304 | return false; |
305 | |
306 | return true; |
307 | } |
308 | |
309 | void tst_Lancelot::testOpenGL_data() |
310 | { |
311 | if (!checkSystemGLSupport()) |
312 | QSKIP("System under test does not meet preconditions for GL testing. Skipping." ); |
313 | QStringList localBlacklist = QStringList() << QLatin1String("rasterops.qps" ); |
314 | setupTestSuite(localBlacklist); |
315 | } |
316 | |
317 | |
318 | void tst_Lancelot::testOpenGL() |
319 | { |
320 | runTestSuite(engine: OpenGL, format: QImage::Format_RGB32); |
321 | } |
322 | |
323 | void tst_Lancelot::testOpenGLBGR30_data() |
324 | { |
325 | tst_Lancelot::testOpenGL_data(); |
326 | } |
327 | |
328 | void tst_Lancelot::testOpenGLBGR30() |
329 | { |
330 | runTestSuite(engine: OpenGL, format: QImage::Format_BGR30); |
331 | } |
332 | |
333 | void tst_Lancelot::testCoreOpenGL_data() |
334 | { |
335 | if (!checkSystemCoreGLSupport()) |
336 | QSKIP("System under test does not meet preconditions for Core Profile GL testing. Skipping." ); |
337 | QStringList localBlacklist = QStringList() << QLatin1String("rasterops.qps" ); |
338 | setupTestSuite(localBlacklist); |
339 | } |
340 | |
341 | void tst_Lancelot::testCoreOpenGL() |
342 | { |
343 | QSurfaceFormat coreFormat; |
344 | coreFormat.setVersion(major: 3, minor: 2); |
345 | coreFormat.setProfile(QSurfaceFormat::CoreProfile); |
346 | runTestSuite(engine: OpenGL, format: QImage::Format_RGB32, contextFormat: coreFormat); |
347 | } |
348 | #endif |
349 | |
350 | |
351 | void tst_Lancelot::setupTestSuite(const QStringList& blacklist) |
352 | { |
353 | QTest::addColumn<QString>(name: "qpsFile" ); |
354 | foreach (const QString &fileName, qpsFiles) { |
355 | if (blacklist.contains(str: fileName)) |
356 | continue; |
357 | QBaselineTest::newRow(dataTag: fileName.toLatin1(), checksum: scriptChecksums.value(akey: fileName)) << fileName; |
358 | } |
359 | } |
360 | |
361 | |
362 | void tst_Lancelot::runTestSuite(GraphicsEngine engine, QImage::Format format, const QSurfaceFormat &contextFormat) |
363 | { |
364 | QFETCH(QString, qpsFile); |
365 | |
366 | QString filePath = scriptsDir + qpsFile; |
367 | QStringList script = scripts.value(akey: qpsFile); |
368 | QImage rendered; |
369 | |
370 | if (engine == Raster) { |
371 | QImage img(800, 800, format); |
372 | paint(device: &img, engine, format, script, filePath: QFileInfo(filePath).absoluteFilePath()); |
373 | rendered = img; |
374 | #ifndef QT_NO_OPENGL |
375 | } else if (engine == OpenGL) { |
376 | QWindow win; |
377 | win.setSurfaceType(QSurface::OpenGLSurface); |
378 | win.setFormat(contextFormat); |
379 | win.create(); |
380 | QOpenGLFramebufferObjectFormat fmt; |
381 | fmt.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); |
382 | fmt.setSamples(4); |
383 | if (format == QImage::Format_BGR30) |
384 | fmt.setInternalTextureFormat(GL_RGB10); |
385 | QOpenGLContext ctx; |
386 | ctx.setFormat(contextFormat); |
387 | QVERIFY(ctx.create()); |
388 | QVERIFY(ctx.makeCurrent(&win)); |
389 | QOpenGLFramebufferObject fbo(800, 800, fmt); |
390 | fbo.bind(); |
391 | QOpenGLPaintDevice pdv(800, 800); |
392 | paint(device: &pdv, engine, format, script, filePath: QFileInfo(filePath).absoluteFilePath()); |
393 | rendered = fbo.toImage().convertToFormat(f: format); |
394 | #endif |
395 | } else if (engine == Pdf) { |
396 | QString tempStem(QDir::tempPath() + QLatin1String("/lancelot_XXXXXX_" ) + qpsFile.chopped(n: 4)); |
397 | |
398 | QTemporaryFile pdfFile(tempStem + QLatin1String(".pdf" )); |
399 | pdfFile.open(); |
400 | QPdfWriter writer(&pdfFile); |
401 | writer.setPdfVersion(QPdfWriter::PdfVersion_1_6); |
402 | writer.setResolution(150); |
403 | paint(device: &writer, engine, format, script, filePath: QFileInfo(filePath).absoluteFilePath()); |
404 | pdfFile.close(); |
405 | |
406 | // Convert pdf to something we can read into a QImage, using macOS' sips utility |
407 | QTemporaryFile pngFile(tempStem + QLatin1String(".png" )); |
408 | pngFile.open(); // Just create the file name |
409 | pngFile.close(); |
410 | QProcess proc; |
411 | const char *rawArgs = "-s format png --cropOffset 20 20 -c 800 800 -o" ; |
412 | QStringList argList = QString::fromLatin1(str: rawArgs).split(sep: QLatin1Char(' ')); |
413 | proc.start(program: QLatin1String("sips" ), arguments: argList << pngFile.fileName() << pdfFile.fileName()); |
414 | proc.waitForFinished(msecs: 2 * 60 * 1000); // May need some time |
415 | |
416 | rendered = QImage(pngFile.fileName()); |
417 | } |
418 | |
419 | QBASELINE_TEST(rendered); |
420 | } |
421 | |
422 | void tst_Lancelot::paint(QPaintDevice *device, GraphicsEngine engine, QImage::Format format, const QStringList &script, const QString &filePath) |
423 | { |
424 | QPainter p(device); |
425 | PaintCommands pcmd(script, 800, 800, format); |
426 | //pcmd.setShouldDrawText(false); |
427 | switch (engine) { |
428 | case OpenGL: |
429 | pcmd.setType(OpenGLBufferType); // version/profile is communicated through the context's format() |
430 | break; |
431 | case Pdf: |
432 | pcmd.setType(PdfType); |
433 | break; |
434 | case Raster: // fallthrough |
435 | default: |
436 | pcmd.setType(ImageType); |
437 | break; |
438 | } |
439 | pcmd.setPainter(&p); |
440 | pcmd.setFilePath(filePath); |
441 | pcmd.runCommands(); |
442 | p.end(); |
443 | } |
444 | |
445 | #define main _realmain |
446 | QTEST_MAIN(tst_Lancelot) |
447 | #undef main |
448 | |
449 | int main(int argc, char *argv[]) |
450 | { |
451 | qSetGlobalQHashSeed(newSeed: 0); // Avoid rendering variations caused by QHash randomization |
452 | |
453 | QBaselineTest::handleCmdLineArgs(argcp: &argc, argvp: &argv); |
454 | return _realmain(argc, argv); |
455 | } |
456 | |
457 | #include "tst_lancelot.moc" |
458 | |