// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #include class tst_drawingmodes : public QQmlDataTest { Q_OBJECT public: tst_drawingmodes(); bool hasPixelAround(const QImage &fb, int centerX, int centerY); QImage runTest(const QString &fileName) { QQuickView view(&outerWindow); view.setResizeMode(QQuickView::SizeViewToRootObject); view.setSource(testFileUrl(fileName)); view.setVisible(true); bool exposed = QTest::qWaitForWindowExposed(&view); return exposed ? view.grabWindow() : QImage(); } //It is important for platforms that only are able to show fullscreen windows //to have a container for the window that is painted on. QQuickWindow outerWindow; const QRgb black; const QRgb red; private slots: void points(); void lines(); void lineStrip(); void lineLoop(); void triangles(); void triangleStrip(); void triangleFan(); private: bool isRunningOnRhi() const; }; class DrawingModeItem : public QQuickItem { Q_OBJECT public: static QSGGeometry::DrawingMode drawingMode; DrawingModeItem() : first(QSGGeometry::defaultAttributes_Point2D(), 5), second(QSGGeometry::defaultAttributes_Point2D(), 5) { setFlag(ItemHasContents, true); material.setColor(Qt::red); } protected: QSGGeometry first; QSGGeometry second; QSGFlatColorMaterial material; QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *) override { if (!node) { QRect bounds(0, 0, 200, 200); first.setDrawingMode(drawingMode); second.setDrawingMode(drawingMode); QSGGeometry::Point2D *v = first.vertexDataAsPoint2D(); v[0].set(bounds.width() * 2 / 8, bounds.height() / 2); v[1].set(bounds.width() / 8, bounds.height() / 4); v[2].set(bounds.width() * 3 / 8, bounds.height() / 4); v[3].set(bounds.width() * 3 / 8, bounds.height() * 3 / 4); v[4].set(bounds.width() / 8, bounds.height() * 3 / 4); v = second.vertexDataAsPoint2D(); v[0].set(bounds.width() * 6 / 8, bounds.height() / 2); v[1].set(bounds.width() * 5 / 8, bounds.height() / 4); v[2].set(bounds.width() * 7 / 8, bounds.height() / 4); v[3].set(bounds.width() * 7 / 8, bounds.height() * 3 / 4); v[4].set(bounds.width() * 5 / 8, bounds.height() * 3 / 4); node = new QSGNode; QSGGeometryNode *child = new QSGGeometryNode; child->setGeometry(&first); child->setMaterial(&material); node->appendChildNode(child); child = new QSGGeometryNode; child->setGeometry(&second); child->setMaterial(&material); node->appendChildNode(child); } return node; } }; QSGGeometry::DrawingMode DrawingModeItem::drawingMode; bool tst_drawingmodes::hasPixelAround(const QImage &fb, int centerX, int centerY) { for (int x = centerX - 2; x <= centerX + 2; ++x) { for (int y = centerY - 2; y <= centerY + 2; ++y) { if (fb.pixel(x, y) == red) return true; } } return false; } tst_drawingmodes::tst_drawingmodes() : QQmlDataTest(QT_QMLTEST_DATADIR) , black(qRgb(0, 0, 0)) , red(qRgb(0xff, 0, 0)) { qmlRegisterType("Test", 1, 0, "DrawingModeItem"); outerWindow.showNormal(); outerWindow.setGeometry(0,0,400,400); } void tst_drawingmodes::points() { DrawingModeItem::drawingMode = QSGGeometry::DrawPoints; if (QGuiApplication::primaryScreen()->depth() < 24) QSKIP("This test does not work at display depths < 24"); // Contrary to what b1d0136192b7451788dee79c865dede02438f96e says, // this test fails when running with the offscreen platform plugin, // so we can't use SKIP_IF_NO_WINDOW_GRAB; here. if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms"); #if QT_CONFIG(metal) QSKIP("Skipping points test due to unexpected failures in M1 CI VM"); #endif QImage fb = runTest("DrawingModes.qml"); QVERIFY(hasPixelAround(fb, 50, 100)); QVERIFY(hasPixelAround(fb, 25, 50)); QVERIFY(hasPixelAround(fb, 75, 50)); QVERIFY(hasPixelAround(fb, 75, 150)); QVERIFY(hasPixelAround(fb, 25, 150)); QVERIFY(hasPixelAround(fb, 150, 100)); QVERIFY(hasPixelAround(fb, 125, 50)); QVERIFY(hasPixelAround(fb, 175, 50)); QVERIFY(hasPixelAround(fb, 175, 150)); QVERIFY(hasPixelAround(fb, 125, 150)); QVERIFY(!hasPixelAround(fb, 135, 70)); QVERIFY(!hasPixelAround(fb, 175, 100)); QVERIFY(!hasPixelAround(fb, 110, 140)); QVERIFY(!hasPixelAround(fb, 50, 50)); QVERIFY(!hasPixelAround(fb, 50, 150)); QVERIFY(!hasPixelAround(fb, 25, 100)); QVERIFY(!hasPixelAround(fb, 75, 100)); QVERIFY(!hasPixelAround(fb, 125, 100)); QVERIFY(!hasPixelAround(fb, 150, 50)); QVERIFY(!hasPixelAround(fb, 150, 150)); QVERIFY(!hasPixelAround(fb, 135, 130)); QVERIFY(!hasPixelAround(fb, 35, 130)); } void tst_drawingmodes::lines() { DrawingModeItem::drawingMode = QSGGeometry::DrawLines; if (QGuiApplication::primaryScreen()->depth() < 24) QSKIP("This test does not work at display depths < 24"); if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms"); QImage fb = runTest("DrawingModes.qml"); QCOMPARE(fb.width(), 200); QCOMPARE(fb.height(), 200); QVERIFY(hasPixelAround(fb, 135, 70)); QVERIFY(hasPixelAround(fb, 175, 100)); QVERIFY(!hasPixelAround(fb, 110, 140)); QVERIFY(!hasPixelAround(fb, 50, 50)); QVERIFY(!hasPixelAround(fb, 50, 150)); QVERIFY(hasPixelAround(fb, 35, 70)); QVERIFY(hasPixelAround(fb, 75, 100)); QVERIFY(!hasPixelAround(fb, 25, 100)); QVERIFY(!hasPixelAround(fb, 125, 100)); QVERIFY(!hasPixelAround(fb, 150, 50)); QVERIFY(!hasPixelAround(fb, 150, 150)); QVERIFY(!hasPixelAround(fb, 135, 130)); QVERIFY(!hasPixelAround(fb, 35, 130)); } void tst_drawingmodes::lineStrip() { DrawingModeItem::drawingMode = QSGGeometry::DrawLineStrip; if (QGuiApplication::primaryScreen()->depth() < 24) QSKIP("This test does not work at display depths < 24"); if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms"); QImage fb = runTest("DrawingModes.qml"); QCOMPARE(fb.width(), 200); QCOMPARE(fb.height(), 200); QVERIFY(hasPixelAround(fb, 135, 70)); QVERIFY(hasPixelAround(fb, 150, 50)); QVERIFY(hasPixelAround(fb, 175, 100)); QVERIFY(hasPixelAround(fb, 150, 150)); QVERIFY(hasPixelAround(fb, 35, 70)); QVERIFY(hasPixelAround(fb, 50, 50)); QVERIFY(hasPixelAround(fb, 75, 100)); QVERIFY(hasPixelAround(fb, 50, 150)); QVERIFY(!hasPixelAround(fb, 110, 140)); // bad line not there => line strip unbatched QVERIFY(!hasPixelAround(fb, 25, 100)); QVERIFY(!hasPixelAround(fb, 125, 100)); QVERIFY(!hasPixelAround(fb, 135, 130)); QVERIFY(!hasPixelAround(fb, 35, 130)); } void tst_drawingmodes::lineLoop() { DrawingModeItem::drawingMode = QSGGeometry::DrawLineLoop; if (QGuiApplication::primaryScreen()->depth() < 24) QSKIP("This test does not work at display depths < 24"); if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms"); if (isRunningOnRhi()) QSKIP("Line loops are not supported by some modern graphics APIs - skipping test"); QImage fb = runTest("DrawingModes.qml"); QCOMPARE(fb.width(), 200); QCOMPARE(fb.height(), 200); QVERIFY(hasPixelAround(fb, 135, 70)); QVERIFY(hasPixelAround(fb, 135, 130)); QVERIFY(hasPixelAround(fb, 150, 50)); QVERIFY(hasPixelAround(fb, 175, 100)); QVERIFY(hasPixelAround(fb, 150, 150)); QVERIFY(hasPixelAround(fb, 35, 70)); QVERIFY(hasPixelAround(fb, 35, 130)); QVERIFY(hasPixelAround(fb, 50, 50)); QVERIFY(hasPixelAround(fb, 75, 100)); QVERIFY(hasPixelAround(fb, 50, 150)); QVERIFY(!hasPixelAround(fb, 110, 140)); // bad line not there => line loop unbatched QVERIFY(!hasPixelAround(fb, 25, 100)); QVERIFY(!hasPixelAround(fb, 125, 100)); } void tst_drawingmodes::triangles() { DrawingModeItem::drawingMode = QSGGeometry::DrawTriangles; if (QGuiApplication::primaryScreen()->depth() < 24) QSKIP("This test does not work at display depths < 24"); if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms"); QImage fb = runTest("DrawingModes.qml"); QCOMPARE(fb.width(), 200); QCOMPARE(fb.height(), 200); QVERIFY(hasPixelAround(fb, 150, 75)); QVERIFY(!hasPixelAround(fb, 162, 100)); QVERIFY(!hasPixelAround(fb, 150, 125)); QVERIFY(!hasPixelAround(fb, 137, 100)); QVERIFY(!hasPixelAround(fb, 100, 125)); QVERIFY(hasPixelAround(fb, 50, 75)); QVERIFY(!hasPixelAround(fb, 62, 100)); QVERIFY(!hasPixelAround(fb, 50, 125)); QVERIFY(!hasPixelAround(fb, 37, 100)); } void tst_drawingmodes::triangleStrip() { DrawingModeItem::drawingMode = QSGGeometry::DrawTriangleStrip; if (QGuiApplication::primaryScreen()->depth() < 24) QSKIP("This test does not work at display depths < 24"); if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms"); QImage fb = runTest("DrawingModes.qml"); QCOMPARE(fb.width(), 200); QCOMPARE(fb.height(), 200); QVERIFY(hasPixelAround(fb, 150, 75)); QVERIFY(hasPixelAround(fb, 162, 100)); QVERIFY(hasPixelAround(fb, 150, 125)); QVERIFY(!hasPixelAround(fb, 137, 100)); QVERIFY(!hasPixelAround(fb, 100, 125)); // batching avoids extra triangle by duplicating vertices. QVERIFY(hasPixelAround(fb, 50, 75)); QVERIFY(hasPixelAround(fb, 62, 100)); QVERIFY(hasPixelAround(fb, 50, 125)); QVERIFY(!hasPixelAround(fb, 37, 100)); } void tst_drawingmodes::triangleFan() { DrawingModeItem::drawingMode = QSGGeometry::DrawTriangleFan; if (QGuiApplication::primaryScreen()->depth() < 24) QSKIP("This test does not work at display depths < 24"); if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms"); if (isRunningOnRhi()) QSKIP("Triangle fans are not supported by some modern graphics APIs - skipping test"); QImage fb = runTest("DrawingModes.qml"); QCOMPARE(fb.width(), 200); QCOMPARE(fb.height(), 200); QVERIFY(hasPixelAround(fb, 150, 75)); QVERIFY(hasPixelAround(fb, 162, 100)); QVERIFY(hasPixelAround(fb, 150, 125)); QVERIFY(!hasPixelAround(fb, 137, 100)); QVERIFY(!hasPixelAround(fb, 100, 125)); // no extra triangle; triangle fan is not batched QVERIFY(hasPixelAround(fb, 50, 75)); QVERIFY(hasPixelAround(fb, 62, 100)); QVERIFY(hasPixelAround(fb, 50, 125)); QVERIFY(!hasPixelAround(fb, 37, 100)); } bool tst_drawingmodes::isRunningOnRhi() const { static bool retval = false; static bool decided = false; if (!decided) { decided = true; QQuickView dummy; dummy.show(); if (QTest::qWaitForWindowExposed(&dummy)) { QSGRendererInterface::GraphicsApi api = dummy.rendererInterface()->graphicsApi(); retval = QSGRendererInterface::isApiRhiBased(api); } dummy.hide(); } return retval; } QTEST_MAIN(tst_drawingmodes) #include "tst_drawingmodes.moc"