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 <qtest.h> |
30 | |
31 | #if QT_CONFIG(opengl) |
32 | #include <QOffscreenSurface> |
33 | #include <QOpenGLContext> |
34 | #include <QOpenGLFunctions> |
35 | #endif |
36 | |
37 | #include <QtQuick> |
38 | #include <QtQml> |
39 | |
40 | #if QT_CONFIG(opengl) |
41 | #include <private/qopenglcontext_p.h> |
42 | #endif |
43 | |
44 | #include <private/qsgcontext_p.h> |
45 | #include <private/qsgrenderloop_p.h> |
46 | |
47 | #include "../../shared/util.h" |
48 | #include "../shared/visualtestutil.h" |
49 | |
50 | #include <QtGui/private/qguiapplication_p.h> |
51 | #include <QtGui/qpa/qplatformintegration.h> |
52 | |
53 | using namespace QQuickVisualTestUtil; |
54 | |
55 | class PerPixelRect : public QQuickItem |
56 | { |
57 | Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) |
58 | Q_OBJECT |
59 | public: |
60 | PerPixelRect() { |
61 | setFlag(flag: ItemHasContents); |
62 | } |
63 | |
64 | void setColor(const QColor &c) { |
65 | if (c == m_color) |
66 | return; |
67 | m_color = c; |
68 | emit colorChanged(c); |
69 | } |
70 | |
71 | QColor color() const { return m_color; } |
72 | |
73 | QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *) |
74 | { |
75 | delete node; |
76 | node = new QSGNode; |
77 | |
78 | const int w = int(width()); |
79 | const int h = int(height()); |
80 | QQuickWindow *win = window(); |
81 | for (int y = 0; y < h; ++y) { |
82 | for (int x = 0; x < w; ++x) { |
83 | QSGRectangleNode *rn = win->createRectangleNode(); |
84 | rn->setRect(x, y, w: 1, h: 1); |
85 | rn->setColor(m_color); |
86 | node->appendChildNode(node: rn); |
87 | } |
88 | } |
89 | |
90 | return node; |
91 | } |
92 | |
93 | Q_SIGNALS: |
94 | void colorChanged(const QColor &c); |
95 | |
96 | private: |
97 | QColor m_color; |
98 | }; |
99 | |
100 | class tst_SceneGraph : public QQmlDataTest |
101 | { |
102 | Q_OBJECT |
103 | |
104 | private slots: |
105 | void initTestCase(); |
106 | |
107 | void manyWindows_data(); |
108 | void manyWindows(); |
109 | |
110 | void render_data(); |
111 | void render(); |
112 | #if QT_CONFIG(opengl) |
113 | void hideWithOtherContext(); |
114 | #endif |
115 | void createTextureFromImage_data(); |
116 | void createTextureFromImage(); |
117 | |
118 | private: |
119 | bool m_brokenMipmapSupport; |
120 | QQuickView *createView(const QString &file, QWindow *parent = nullptr, int x = -1, int y = -1, int w = -1, int h = -1); |
121 | bool isRunningOnOpenGLDirectly(); |
122 | bool isRunningOnRhi(); |
123 | }; |
124 | |
125 | template <typename T> class ScopedList : public QList<T> { |
126 | public: |
127 | ~ScopedList() { qDeleteAll(*this); } |
128 | }; |
129 | |
130 | void tst_SceneGraph::initTestCase() |
131 | { |
132 | qmlRegisterType<PerPixelRect>(uri: "SceneGraphTest" , versionMajor: 1, versionMinor: 0, qmlName: "PerPixelRect" ); |
133 | |
134 | QQmlDataTest::initTestCase(); |
135 | |
136 | QSGRenderLoop *loop = QSGRenderLoop::instance(); |
137 | qDebug() << "RenderLoop: " << loop; |
138 | |
139 | #if QT_CONFIG(opengl) |
140 | if (QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::OpenGL)) { |
141 | QOpenGLContext context; |
142 | context.setFormat(loop->sceneGraphContext()->defaultSurfaceFormat()); |
143 | context.create(); |
144 | QSurfaceFormat format = context.format(); |
145 | |
146 | QOffscreenSurface surface; |
147 | surface.setFormat(format); |
148 | surface.create(); |
149 | if (!context.makeCurrent(surface: &surface)) |
150 | qFatal(msg: "Failed to create a GL context..." ); |
151 | |
152 | QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); |
153 | qDebug() << "R/G/B/A Buffers: " << format.redBufferSize() << format.greenBufferSize() << format.blueBufferSize() << format.alphaBufferSize(); |
154 | qDebug() << "Depth Buffer: " << format.depthBufferSize(); |
155 | qDebug() << "Stencil Buffer: " << format.stencilBufferSize(); |
156 | qDebug() << "Samples: " << format.samples(); |
157 | int textureSize; |
158 | funcs->glGetIntegerv(GL_MAX_TEXTURE_SIZE, params: &textureSize); |
159 | qDebug() << "Max Texture Size: " << textureSize; |
160 | qDebug() << "GL_VENDOR: " << (const char *) funcs->glGetString(GL_VENDOR); |
161 | qDebug() << "GL_RENDERER: " << (const char *) funcs->glGetString(GL_RENDERER); |
162 | QByteArray version = (const char *) funcs->glGetString(GL_VERSION); |
163 | qDebug() << "GL_VERSION: " << version.constData(); |
164 | QSet<QByteArray> exts = context.extensions(); |
165 | QByteArray all; |
166 | foreach (const QByteArray &e, exts) all += ' ' + e; |
167 | qDebug() << "GL_EXTENSIONS: " << all.constData(); |
168 | |
169 | m_brokenMipmapSupport = version.contains(c: "Mesa 10.1" ) || version.contains(c: "Mesa 9." ); |
170 | qDebug() << "Broken Mipmap: " << m_brokenMipmapSupport; |
171 | |
172 | context.doneCurrent(); |
173 | } |
174 | #endif |
175 | } |
176 | |
177 | QQuickView *tst_SceneGraph::createView(const QString &file, QWindow *parent, int x, int y, int w, int h) |
178 | { |
179 | QQuickView *view = new QQuickView(parent); |
180 | view->setSource(testFileUrl(fileName: file)); |
181 | if (x >= 0 && y >= 0) view->setPosition(posx: x, posy: y); |
182 | if (w >= 0 && h >= 0) view->resize(w, h); |
183 | view->show(); |
184 | return view; |
185 | } |
186 | |
187 | // Assumes the images are opaque white... |
188 | bool containsSomethingOtherThanWhite(const QImage &image) |
189 | { |
190 | QImage img; |
191 | if (image.format() != QImage::Format_ARGB32_Premultiplied |
192 | || image.format() != QImage::Format_RGB32) |
193 | img = image.convertToFormat(f: QImage::Format_ARGB32_Premultiplied); |
194 | else |
195 | img = image; |
196 | |
197 | int w = img.width(); |
198 | int h = img.height(); |
199 | for (int y=0; y<h; ++y) { |
200 | const uint *pixels = (const uint *) img.constScanLine(y); |
201 | for (int x=0; x<w; ++x) |
202 | if (pixels[x] != 0xffffffff) |
203 | return true; |
204 | } |
205 | return false; |
206 | } |
207 | |
208 | void tst_SceneGraph::manyWindows_data() |
209 | { |
210 | QTest::addColumn<QString>(name: "file" ); |
211 | QTest::addColumn<bool>(name: "toplevel" ); |
212 | QTest::addColumn<bool>(name: "shared" ); |
213 | |
214 | QTest::newRow(dataTag: "image,toplevel" ) << QStringLiteral("manyWindows_image.qml" ) << true << false; |
215 | QTest::newRow(dataTag: "image,subwindow" ) << QStringLiteral("manyWindows_image.qml" ) << false << false; |
216 | QTest::newRow(dataTag: "dftext,toplevel" ) << QStringLiteral("manyWindows_dftext.qml" ) << true << false; |
217 | QTest::newRow(dataTag: "dftext,subwindow" ) << QStringLiteral("manyWindows_dftext.qml" ) << false << false; |
218 | QTest::newRow(dataTag: "ntext,toplevel" ) << QStringLiteral("manyWindows_ntext.qml" ) << true << false; |
219 | QTest::newRow(dataTag: "ntext,subwindow" ) << QStringLiteral("manyWindows_ntext.qml" ) << false << false; |
220 | QTest::newRow(dataTag: "rects,toplevel" ) << QStringLiteral("manyWindows_rects.qml" ) << true << false; |
221 | QTest::newRow(dataTag: "rects,subwindow" ) << QStringLiteral("manyWindows_rects.qml" ) << false << false; |
222 | |
223 | QTest::newRow(dataTag: "image,toplevel,sharing" ) << QStringLiteral("manyWindows_image.qml" ) << true << true; |
224 | QTest::newRow(dataTag: "image,subwindow,sharing" ) << QStringLiteral("manyWindows_image.qml" ) << false << true; |
225 | QTest::newRow(dataTag: "dftext,toplevel,sharing" ) << QStringLiteral("manyWindows_dftext.qml" ) << true << true; |
226 | QTest::newRow(dataTag: "dftext,subwindow,sharing" ) << QStringLiteral("manyWindows_dftext.qml" ) << false << true; |
227 | QTest::newRow(dataTag: "ntext,toplevel,sharing" ) << QStringLiteral("manyWindows_ntext.qml" ) << true << true; |
228 | QTest::newRow(dataTag: "ntext,subwindow,sharing" ) << QStringLiteral("manyWindows_ntext.qml" ) << false << true; |
229 | QTest::newRow(dataTag: "rects,toplevel,sharing" ) << QStringLiteral("manyWindows_rects.qml" ) << true << true; |
230 | QTest::newRow(dataTag: "rects,subwindow,sharing" ) << QStringLiteral("manyWindows_rects.qml" ) << false << true; |
231 | } |
232 | |
233 | #if QT_CONFIG(opengl) |
234 | struct ShareContextResetter { |
235 | public: |
236 | ~ShareContextResetter() { qt_gl_set_global_share_context(context: nullptr); } |
237 | }; |
238 | #endif |
239 | |
240 | void tst_SceneGraph::manyWindows() |
241 | { |
242 | if ((QGuiApplication::platformName() == QLatin1String("offscreen" )) |
243 | || (QGuiApplication::platformName() == QLatin1String("minimal" ))) |
244 | QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms" ); |
245 | |
246 | QFETCH(QString, file); |
247 | QFETCH(bool, toplevel); |
248 | QFETCH(bool, shared); |
249 | #if QT_CONFIG(opengl) |
250 | QOpenGLContext sharedGLContext; |
251 | ShareContextResetter cleanup; // To avoid dangling pointer in case of test-failure. |
252 | if (shared) { |
253 | QVERIFY(sharedGLContext.create()); |
254 | qt_gl_set_global_share_context(context: &sharedGLContext); |
255 | } |
256 | #endif |
257 | QScopedPointer<QWindow> parent; |
258 | if (!toplevel) { |
259 | parent.reset(other: new QWindow()); |
260 | parent->resize(w: 200, h: 200); |
261 | parent->show(); |
262 | } |
263 | |
264 | ScopedList <QQuickView *> views; |
265 | |
266 | const int COUNT = 4; |
267 | |
268 | QImage baseLine; |
269 | QString errorMessage; |
270 | for (int i=0; i<COUNT; ++i) { |
271 | views << createView(file, parent: parent.data(), x: (i % 2) * 100, y: (i / 2) * 100, w: 100, h: 100); |
272 | } |
273 | for (int i=0; i<COUNT; ++i) { |
274 | QQuickView *view = views.at(i); |
275 | QVERIFY(QTest::qWaitForWindowExposed(view)); |
276 | QImage content = view->grabWindow(); |
277 | if (i == 0) { |
278 | baseLine = content; |
279 | QVERIFY(containsSomethingOtherThanWhite(baseLine)); |
280 | } else { |
281 | QVERIFY2(compareImages(content, baseLine, &errorMessage), |
282 | qPrintable(errorMessage)); |
283 | } |
284 | } |
285 | |
286 | // Wipe and recreate one (scope pointer delets it...) |
287 | delete views.takeLast(); |
288 | QQuickView *last = createView(file, parent: parent.data(), x: 100, y: 100, w: 100, h: 100); |
289 | QVERIFY(QTest::qWaitForWindowExposed(last)); |
290 | views << last; |
291 | QVERIFY2(compareImages(baseLine, last->grabWindow(), &errorMessage), |
292 | qPrintable(errorMessage)); |
293 | |
294 | // Wipe and recreate all |
295 | qDeleteAll(c: views); |
296 | views.clear(); |
297 | |
298 | for (int i=0; i<COUNT; ++i) { |
299 | views << createView(file, parent: parent.data(), x: (i % 2) * 100, y: (i / 2) * 100, w: 100, h: 100); |
300 | } |
301 | for (int i=0; i<COUNT; ++i) { |
302 | QQuickView *view = views.at(i); |
303 | QVERIFY(QTest::qWaitForWindowExposed(view)); |
304 | QImage content = view->grabWindow(); |
305 | QVERIFY2(compareImages(content, baseLine, &errorMessage), |
306 | qPrintable(errorMessage)); |
307 | } |
308 | } |
309 | |
310 | struct Sample { |
311 | Sample(int xx, int yy, qreal rr, qreal gg, qreal bb, qreal errorMargin = 0.05) |
312 | : x(xx) |
313 | , y(yy) |
314 | , r(rr) |
315 | , g(gg) |
316 | , b(bb) |
317 | , tolerance(errorMargin) |
318 | { |
319 | } |
320 | Sample(const Sample &o) : x(o.x), y(o.y), r(o.r), g(o.g), b(o.b), tolerance(o.tolerance) { } |
321 | Sample() : x(0), y(0), r(0), g(0), b(0), tolerance(0) { } |
322 | |
323 | QString toString(const QImage &image) const { |
324 | QColor color(image.pixel(x,y)); |
325 | return QString::fromLatin1(str: "pixel(%1,%2), rgb(%3,%4,%5), tolerance=%6 -- image(%7,%8,%9)" ) |
326 | .arg(a: x).arg(a: y) |
327 | .arg(a: r).arg(a: g).arg(a: b) |
328 | .arg(a: tolerance) |
329 | .arg(a: color.redF()).arg(a: color.greenF()).arg(a: color.blueF()); |
330 | } |
331 | |
332 | bool check(const QImage &image, qreal scale) { |
333 | QColor color(image.pixel(x: x * scale, y: y * scale)); |
334 | return qAbs(t: color.redF() - r) <= tolerance |
335 | && qAbs(t: color.greenF() - g) <= tolerance |
336 | && qAbs(t: color.blueF() - b) <= tolerance; |
337 | } |
338 | |
339 | |
340 | int x, y; |
341 | qreal r, g, b; |
342 | qreal tolerance; |
343 | }; |
344 | |
345 | static Sample sample_from_regexp(QRegExp *re) { |
346 | return Sample(re->cap(nth: 1).toInt(), |
347 | re->cap(nth: 2).toInt(), |
348 | re->cap(nth: 3).toFloat(), |
349 | re->cap(nth: 4).toFloat(), |
350 | re->cap(nth: 5).toFloat(), |
351 | re->cap(nth: 6).toFloat() |
352 | ); |
353 | } |
354 | |
355 | Q_DECLARE_METATYPE(Sample); |
356 | |
357 | /* |
358 | The render() test implements a small test framework for itself with |
359 | the purpose of testing odds and ends of the scene graph |
360 | rendering. Each .qml file can consist of one or two stages. The |
361 | first stage is when the file is first displayed. The content is |
362 | grabbed and matched against 'base' samples defined in the .qml file |
363 | itself. The samples contain a pixel position, and RGB value and a |
364 | margin of error. The samples are defined in the .qml file so it is |
365 | easy to make the connection between colors and positions on the screen. |
366 | |
367 | If the base stage samples all succeed, the test emits |
368 | 'enterFinalStage' on the root item and waits for the .qml file to |
369 | update the value of 'finalStageComplete' The test can set this |
370 | directly or run an animation and set it later. Once the |
371 | 'finalStageComplete' variable is true, we grab and match against the |
372 | second set of samples 'final' |
373 | |
374 | The samples in the .qml file are defined in comments on the format: |
375 | #base: x y r g b error-tolerance |
376 | #final: x y r g b error-tolerance |
377 | - x and y are integers |
378 | - r g b are floats in the range of 0.0-1.0 |
379 | - error-tolerance is a float in the range of 0.0-1.0 |
380 | |
381 | We also include a |
382 | #samples: count |
383 | to sanity check that all base/final samples were matched correctly |
384 | as the matching regexp is a bit crude. |
385 | |
386 | To add new tests, add them to the 'files' list and put #base, |
387 | #final, #samples tags into the .qml file |
388 | */ |
389 | |
390 | void tst_SceneGraph::render_data() |
391 | { |
392 | QTest::addColumn<QString>(name: "file" ); |
393 | QTest::addColumn<QList<Sample> >(name: "baseStage" ); |
394 | QTest::addColumn<QList<Sample> >(name: "finalStage" ); |
395 | |
396 | QList<QString> files; |
397 | files << "render_DrawSets.qml" |
398 | << "render_Overlap.qml" |
399 | << "render_MovingOverlap.qml" |
400 | << "render_BreakOpacityBatch.qml" |
401 | << "render_OutOfFloatRange.qml" |
402 | << "render_StackingOrder.qml" |
403 | << "render_ImageFiltering.qml" |
404 | << "render_bug37422.qml" |
405 | << "render_OpacityThroughBatchRoot.qml" |
406 | << "render_AlphaOverlapRebuild.qml" ; |
407 | |
408 | if (!m_brokenMipmapSupport) |
409 | files << "render_Mipmap.qml" ; |
410 | |
411 | QRegExp sampleCount("#samples: *(\\d+)" ); |
412 | // X:int Y:int R:float G:float B:float Error:float |
413 | QRegExp baseSamples("#base: *(\\d+) *(\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+)" ); |
414 | QRegExp finalSamples("#final: *(\\d+) *(\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+)" ); |
415 | |
416 | foreach (QString fileName, files) { |
417 | QFile file(testFile(fileName)); |
418 | if (!file.open(flags: QFile::ReadOnly)) { |
419 | qFatal(msg: "render_data: QFile::open failed! file=%s, error=%s" , |
420 | qPrintable(fileName), qPrintable(file.errorString())); |
421 | } |
422 | QStringList contents = QString::fromLatin1(str: file.readAll()).split(sep: QLatin1Char('\n')); |
423 | |
424 | int samples = -1; |
425 | foreach (QString line, contents) { |
426 | if (sampleCount.indexIn(str: line) >= 0) { |
427 | samples = sampleCount.cap(nth: 1).toInt(); |
428 | break; |
429 | } |
430 | } |
431 | if (samples == -1) |
432 | qFatal(msg: "render_data: failed to find string '#samples: [count], file=%s" , qPrintable(fileName)); |
433 | |
434 | QList<Sample> baseStage, finalStage; |
435 | foreach (QString line, contents) { |
436 | if (baseSamples.indexIn(str: line) >= 0) |
437 | baseStage << sample_from_regexp(re: &baseSamples); |
438 | else if (finalSamples.indexIn(str: line) >= 0) |
439 | finalStage << sample_from_regexp(re: &finalSamples); |
440 | } |
441 | |
442 | if (baseStage.size() + finalStage.size() != samples) |
443 | qFatal(msg: "render_data: #samples does not add up to number of counted samples, file=%s" , qPrintable(fileName)); |
444 | |
445 | QTest::newRow(qPrintable(fileName)) << fileName << baseStage << finalStage; |
446 | } |
447 | } |
448 | |
449 | void tst_SceneGraph::render() |
450 | { |
451 | if (!isRunningOnOpenGLDirectly() && !isRunningOnRhi()) |
452 | QSKIP("Skipping complex rendering tests due to not running with OpenGL or QRhi" ); |
453 | |
454 | QFETCH(QString, file); |
455 | QFETCH(QList<Sample>, baseStage); |
456 | QFETCH(QList<Sample>, finalStage); |
457 | |
458 | QObject suite; |
459 | suite.setObjectName("The Suite" ); |
460 | |
461 | QQuickView view; |
462 | view.rootContext()->setContextProperty("suite" , &suite); |
463 | view.setSource(testFileUrl(fileName: file)); |
464 | view.setResizeMode(QQuickView::SizeViewToRootObject); |
465 | view.show(); |
466 | QVERIFY(QTest::qWaitForWindowExposed(&view)); |
467 | |
468 | // We're checking actual pixels, so scale up the sample point to the top-left of the |
469 | // 2x2 pixel block and hope that this is good enough. Ideally, view and content |
470 | // would be in identical coordinate space, meaning pixels, but we're not in an |
471 | // ideal world. |
472 | // Just keep this in mind when writing tests. |
473 | qreal scale = view.devicePixelRatio(); |
474 | |
475 | // Grab the window and check all our base stage samples |
476 | QImage content = view.grabWindow(); |
477 | for (int i=0; i<baseStage.size(); ++i) { |
478 | Sample sample = baseStage.at(i); |
479 | QVERIFY2(sample.check(content, scale), qPrintable(sample.toString(content))); |
480 | } |
481 | |
482 | // Put the qml file into the final stage and wait for it to |
483 | // complete it. |
484 | QQuickItem *rootItem = view.rootObject(); |
485 | QMetaObject::invokeMethod(obj: rootItem, member: "enterFinalStage" ); |
486 | QTRY_VERIFY(rootItem->property("finalStageComplete" ).toBool()); |
487 | |
488 | // The grab the results and verify the samples in the end state. |
489 | content = view.grabWindow(); |
490 | for (int i=0; i<finalStage.size(); ++i) { |
491 | Sample sample = finalStage.at(i); |
492 | QVERIFY2(sample.check(content, scale), qPrintable(sample.toString(content))); |
493 | } |
494 | } |
495 | |
496 | #if QT_CONFIG(opengl) |
497 | // Testcase for QTBUG-34898. We make another context current on another surface |
498 | // in the GUI thread and hide the QQuickWindow while the other context is |
499 | // current on the other window. |
500 | void tst_SceneGraph::hideWithOtherContext() |
501 | { |
502 | if (!isRunningOnOpenGLDirectly()) |
503 | QSKIP("Skipping OpenGL context test due to not running with OpenGL" ); |
504 | |
505 | QWindow window; |
506 | window.setSurfaceType(QWindow::OpenGLSurface); |
507 | window.resize(w: 100, h: 100); |
508 | window.create(); |
509 | QOpenGLContext context; |
510 | QVERIFY(context.create()); |
511 | bool renderingOnMainThread = false; |
512 | |
513 | { |
514 | QQuickView view; |
515 | view.setSource(testFileUrl(fileName: "simple.qml" )); |
516 | view.setResizeMode(QQuickView::SizeViewToRootObject); |
517 | view.show(); |
518 | QVERIFY(QTest::qWaitForWindowExposed(&view)); |
519 | |
520 | renderingOnMainThread = view.openglContext()->thread() == QGuiApplication::instance()->thread(); |
521 | |
522 | // Make the local context current on the local window... |
523 | context.makeCurrent(surface: &window); |
524 | } |
525 | |
526 | // The local context should no longer be the current one. It is not |
527 | // rebound because all well behaving Qt/OpenGL applications are |
528 | // required to makeCurrent their context again before making any |
529 | // GL calls to a new frame (see QOpenGLContext docs). |
530 | QVERIFY(!renderingOnMainThread || QOpenGLContext::currentContext() != &context); |
531 | } |
532 | #endif |
533 | |
534 | void tst_SceneGraph::createTextureFromImage_data() |
535 | { |
536 | QImage rgba(64, 64, QImage::Format_ARGB32_Premultiplied); |
537 | QImage rgb(64, 64, QImage::Format_RGB32); |
538 | |
539 | QTest::addColumn<QImage>(name: "image" ); |
540 | QTest::addColumn<uint>(name: "flags" ); |
541 | QTest::addColumn<bool>(name: "expectedAlpha" ); |
542 | |
543 | QTest::newRow(dataTag: "rgb" ) << rgb << uint(0) << false; |
544 | QTest::newRow(dataTag: "argb" ) << rgba << uint(0) << true; |
545 | QTest::newRow(dataTag: "rgb,alpha" ) << rgb << uint(QQuickWindow::TextureHasAlphaChannel) << false; |
546 | QTest::newRow(dataTag: "argb,alpha" ) << rgba << uint(QQuickWindow::TextureHasAlphaChannel) << true; |
547 | QTest::newRow(dataTag: "rgb,!alpha" ) << rgb << uint(QQuickWindow::TextureIsOpaque) << false; |
548 | QTest::newRow(dataTag: "argb,!alpha" ) << rgba << uint(QQuickWindow::TextureIsOpaque) << false; |
549 | } |
550 | |
551 | void tst_SceneGraph::createTextureFromImage() |
552 | { |
553 | QFETCH(QImage, image); |
554 | QFETCH(uint, flags); |
555 | QFETCH(bool, expectedAlpha); |
556 | |
557 | QQuickView view; |
558 | view.show(); |
559 | QVERIFY(QTest::qWaitForWindowExposed(&view)); |
560 | QTRY_VERIFY(view.isSceneGraphInitialized()); |
561 | |
562 | QScopedPointer<QSGTexture> texture(view.createTextureFromImage(image, options: (QQuickWindow::CreateTextureOptions) flags)); |
563 | QCOMPARE(texture->hasAlphaChannel(), expectedAlpha); |
564 | } |
565 | |
566 | bool tst_SceneGraph::isRunningOnOpenGLDirectly() |
567 | { |
568 | static bool retval = false; |
569 | static bool decided = false; |
570 | if (!decided) { |
571 | decided = true; |
572 | QQuickView dummy; |
573 | dummy.show(); |
574 | if (QTest::qWaitForWindowExposed(window: &dummy)) |
575 | retval = dummy.rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL; |
576 | dummy.hide(); |
577 | } |
578 | return retval; |
579 | } |
580 | |
581 | bool tst_SceneGraph::isRunningOnRhi() |
582 | { |
583 | static bool retval = false; |
584 | static bool decided = false; |
585 | if (!decided) { |
586 | decided = true; |
587 | QQuickView dummy; |
588 | dummy.show(); |
589 | if (!QTest::qWaitForWindowExposed(window: &dummy)) { |
590 | [](){ QFAIL("Could not show a QQuickView" ); }(); |
591 | return false; |
592 | } |
593 | QSGRendererInterface::GraphicsApi api = dummy.rendererInterface()->graphicsApi(); |
594 | retval = QSGRendererInterface::isApiRhiBased(api); |
595 | dummy.hide(); |
596 | } |
597 | return retval; |
598 | } |
599 | |
600 | #include "tst_scenegraph.moc" |
601 | |
602 | QTEST_MAIN(tst_SceneGraph) |
603 | |
604 | |