| 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 |  |