diff options
Diffstat (limited to 'tests/auto/gui')
27 files changed, 1785 insertions, 150 deletions
diff --git a/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp b/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp index f6ffd7b7c5..22d96e76f9 100644 --- a/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp +++ b/tests/auto/gui/image/qimagereader/tst_qimagereader.cpp @@ -1890,9 +1890,8 @@ void tst_QImageReader::saveFormat() void tst_QImageReader::saveColorSpace_data() { - QTest::addColumn<QColorSpace::ColorSpaceId>("colorspaceId"); + QTest::addColumn<QColorSpace::NamedColorSpace>("namedColorSpace"); - QTest::newRow("Undefined") << QColorSpace::Undefined; QTest::newRow("sRGB") << QColorSpace::SRgb; QTest::newRow("sRGB(linear)") << QColorSpace::SRgbLinear; QTest::newRow("AdobeRGB") << QColorSpace::AdobeRgb; @@ -1902,11 +1901,11 @@ void tst_QImageReader::saveColorSpace_data() void tst_QImageReader::saveColorSpace() { - QFETCH(QColorSpace::ColorSpaceId, colorspaceId); + QFETCH(QColorSpace::NamedColorSpace, namedColorSpace); QImage orig(":/images/kollada.png"); - orig.setColorSpace(colorspaceId); + orig.setColorSpace(namedColorSpace); QBuffer buf; buf.open(QIODevice::WriteOnly); QVERIFY(orig.save(&buf, "png")); diff --git a/tests/auto/gui/image/qmovie/tst_qmovie.cpp b/tests/auto/gui/image/qmovie/tst_qmovie.cpp index 4e9e9b8115..c8217b2cec 100644 --- a/tests/auto/gui/image/qmovie/tst_qmovie.cpp +++ b/tests/auto/gui/image/qmovie/tst_qmovie.cpp @@ -62,6 +62,7 @@ private slots: #ifndef QT_NO_WIDGETS void infiniteLoop(); #endif + void emptyMovie(); }; // Testing get/set functions @@ -220,5 +221,13 @@ void tst_QMovie::infiniteLoop() } #endif +void tst_QMovie::emptyMovie() +{ + QMovie movie; + movie.setCacheMode(QMovie::CacheAll); + movie.jumpToFrame(100); + QCOMPARE(movie.currentFrameNumber(), -1); +} + QTEST_MAIN(tst_QMovie) #include "tst_qmovie.moc" diff --git a/tests/auto/gui/kernel/qopenglwindow/BLACKLIST b/tests/auto/gui/kernel/qopenglwindow/BLACKLIST new file mode 100644 index 0000000000..3ce78abee8 --- /dev/null +++ b/tests/auto/gui/kernel/qopenglwindow/BLACKLIST @@ -0,0 +1,6 @@ +[basic] +winrt +[resize] +winrt +[painter] +winrt diff --git a/tests/auto/gui/kernel/qopenglwindow/qopenglwindow.pro b/tests/auto/gui/kernel/qopenglwindow/qopenglwindow.pro index d09af5084b..759b608c93 100644 --- a/tests/auto/gui/kernel/qopenglwindow/qopenglwindow.pro +++ b/tests/auto/gui/kernel/qopenglwindow/qopenglwindow.pro @@ -4,5 +4,3 @@ TARGET = tst_qopenglwindow QT += core-private gui-private testlib SOURCES += tst_qopenglwindow.cpp - -win32:CONFIG+=insignificant_test # QTBUG-46452, QTBUG-49630 diff --git a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp index 4f26950192..34de756ab5 100644 --- a/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp +++ b/tests/auto/gui/kernel/qwindow/tst_qwindow.cpp @@ -893,7 +893,13 @@ void tst_QWindow::isActive() QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QCoreApplication::processEvents(); QTRY_COMPARE(dialog.received(QEvent::FocusOut), 1); - QTRY_COMPARE(window.received(QEvent::FocusIn), 3); + // We should be checking for exactly three, but since this is a try-compare _loop_, we might + // loose and regain focus multiple times in the event of a system popup. This has been observed + // to fail on Windows, see QTBUG-77769. + QTRY_VERIFY2(window.received(QEvent::FocusIn) >= 3, + qPrintable( + QStringLiteral("Expected more than three focus in events, received: %1") + .arg(window.received(QEvent::FocusIn)))); QVERIFY(window.isActive()); @@ -1025,6 +1031,23 @@ public: Qt::MouseButtons buttonStateInGeneratedMove; }; +static void simulateMouseClick(QWindow *target, const QPointF &local, const QPointF &global) +{ + QWindowSystemInterface::handleMouseEvent(target, local, global, + {}, Qt::LeftButton, QEvent::MouseButtonPress); + QWindowSystemInterface::handleMouseEvent(target, local, global, + Qt::LeftButton, Qt::LeftButton, QEvent::MouseButtonRelease); +} + +static void simulateMouseClick(QWindow *target, ulong &timeStamp, + const QPointF &local, const QPointF &global) +{ + QWindowSystemInterface::handleMouseEvent(target, timeStamp++, local, global, + {}, Qt::LeftButton, QEvent::MouseButtonPress); + QWindowSystemInterface::handleMouseEvent(target, timeStamp++, local, global, + Qt::LeftButton, Qt::LeftButton, QEvent::MouseButtonRelease); +} + void tst_QWindow::testInputEvents() { InputTestWindow window; @@ -1066,15 +1089,13 @@ void tst_QWindow::testInputEvents() window.mousePressButton = window.mouseReleaseButton = 0; const QPointF nonWindowGlobal(window.geometry().topRight() + QPoint(200, 50)); // not inside the window const QPointF deviceNonWindowGlobal = QHighDpi::toNativePixels(nonWindowGlobal, window.screen()); - QWindowSystemInterface::handleMouseEvent(nullptr, deviceNonWindowGlobal, deviceNonWindowGlobal, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(nullptr, deviceNonWindowGlobal, deviceNonWindowGlobal, Qt::NoButton); + simulateMouseClick(nullptr, deviceNonWindowGlobal, deviceNonWindowGlobal); QCoreApplication::processEvents(); QCOMPARE(window.mousePressButton, 0); QCOMPARE(window.mouseReleaseButton, 0); const QPointF windowGlobal = window.mapToGlobal(local.toPoint()); const QPointF deviceWindowGlobal = QHighDpi::toNativePixels(windowGlobal, window.screen()); - QWindowSystemInterface::handleMouseEvent(nullptr, deviceWindowGlobal, deviceWindowGlobal, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(nullptr, deviceWindowGlobal, deviceWindowGlobal, Qt::NoButton); + simulateMouseClick(nullptr, deviceWindowGlobal, deviceWindowGlobal); QCoreApplication::processEvents(); QCOMPARE(window.mousePressButton, int(Qt::LeftButton)); QCOMPARE(window.mouseReleaseButton, int(Qt::LeftButton)); @@ -1198,8 +1219,7 @@ void tst_QWindow::mouseToTouchTranslation() window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); - QWindowSystemInterface::handleMouseEvent(&window, QPoint(10, 10), window.mapToGlobal(QPoint(10, 10)), Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, QPoint(10, 10), window.mapToGlobal(QPoint(10, 10)), Qt::NoButton); + QTest::mouseClick(&window, Qt::LeftButton, {}, QPoint(10, 10)); QCoreApplication::processEvents(); QCoreApplication::setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, false); @@ -1211,8 +1231,7 @@ void tst_QWindow::mouseToTouchTranslation() window.ignoreMouse = false; - QWindowSystemInterface::handleMouseEvent(&window, QPoint(10, 10), window.mapToGlobal(QPoint(10, 10)), Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, QPoint(10, 10), window.mapToGlobal(QPoint(10, 10)), Qt::NoButton); + QTest::mouseClick(&window, Qt::LeftButton, {}, QPoint(10, 10)); QCoreApplication::processEvents(); QCoreApplication::setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, false); @@ -1223,8 +1242,7 @@ void tst_QWindow::mouseToTouchTranslation() window.ignoreMouse = true; - QWindowSystemInterface::handleMouseEvent(&window, QPoint(10, 10), window.mapToGlobal(QPoint(10, 10)), Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, QPoint(10, 10), window.mapToGlobal(QPoint(10, 10)), Qt::NoButton); + QTest::mouseClick(&window, Qt::LeftButton, {}, QPoint(10, 10)); QCoreApplication::processEvents(); // touch event synthesis disabled @@ -1249,8 +1267,7 @@ void tst_QWindow::mouseToTouchLoop() window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); - QWindowSystemInterface::handleMouseEvent(&window, QPoint(10, 10), window.mapToGlobal(QPoint(10, 10)), Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, QPoint(10, 10), window.mapToGlobal(QPoint(10, 10)), Qt::NoButton); + QTest::mouseClick(&window, Qt::LeftButton, {}, QPoint(10, 10)); QCoreApplication::processEvents(); QCoreApplication::setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, false); @@ -1545,8 +1562,8 @@ void tst_QWindow::mouseEventSequence() ulong timestamp = 0; QPointF local(12, 34); const QPointF deviceLocal = QHighDpi::toNativePixels(local, &window); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, deviceLocal, deviceLocal, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, deviceLocal, deviceLocal, Qt::NoButton); + + simulateMouseClick(&window, timestamp, deviceLocal, deviceLocal); QCoreApplication::processEvents(); QCOMPARE(window.mousePressedCount, 1); QCOMPARE(window.mouseReleasedCount, 1); @@ -1558,10 +1575,8 @@ void tst_QWindow::mouseEventSequence() // A double click must result in press, release, press, doubleclick, release. // Check that no unexpected event suppression occurs and that the order is correct. - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); + simulateMouseClick(&window, timestamp, local, local); + simulateMouseClick(&window, timestamp, local, local); QCoreApplication::processEvents(); QCOMPARE(window.mousePressedCount, 2); QCOMPARE(window.mouseReleasedCount, 2); @@ -1572,12 +1587,9 @@ void tst_QWindow::mouseEventSequence() window.resetCounters(); // Triple click = double + single click - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); + simulateMouseClick(&window, timestamp, local, local); + simulateMouseClick(&window, timestamp, local, local); + simulateMouseClick(&window, timestamp, local, local); QCoreApplication::processEvents(); QCOMPARE(window.mousePressedCount, 3); QCOMPARE(window.mouseReleasedCount, 3); @@ -1588,14 +1600,10 @@ void tst_QWindow::mouseEventSequence() window.resetCounters(); // Two double clicks. - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); + simulateMouseClick(&window, timestamp, local, local); + simulateMouseClick(&window, timestamp, local, local); + simulateMouseClick(&window, timestamp, local, local); + simulateMouseClick(&window, timestamp, local, local); QCoreApplication::processEvents(); QCOMPARE(window.mousePressedCount, 4); QCOMPARE(window.mouseReleasedCount, 4); @@ -1606,17 +1614,13 @@ void tst_QWindow::mouseEventSequence() window.resetCounters(); // Four clicks, none of which qualifies as a double click. - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); + simulateMouseClick(&window, timestamp, local, local); timestamp += doubleClickInterval; - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); + simulateMouseClick(&window, timestamp, local, local); timestamp += doubleClickInterval; - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); + simulateMouseClick(&window, timestamp, local, local); timestamp += doubleClickInterval; - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, timestamp++, local, local, Qt::NoButton); + simulateMouseClick(&window, timestamp, local, local); timestamp += doubleClickInterval; QCoreApplication::processEvents(); QCOMPARE(window.mousePressedCount, 4); @@ -1668,10 +1672,13 @@ void tst_QWindow::inputReentrancy() // Queue three events. QPointF local(12, 34); - QWindowSystemInterface::handleMouseEvent(&window, local, local, Qt::LeftButton); + QWindowSystemInterface::handleMouseEvent(&window, local, local, {}, + Qt::LeftButton, QEvent::MouseButtonPress); local += QPointF(2, 2); - QWindowSystemInterface::handleMouseEvent(&window, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&window, local, local, Qt::NoButton); + QWindowSystemInterface::handleMouseEvent(&window, local, local, + Qt::LeftButton, Qt::LeftButton, QEvent::MouseMove); + QWindowSystemInterface::handleMouseEvent(&window, local, local, Qt::LeftButton, + Qt::LeftButton, QEvent::MouseButtonRelease); // Process them. However, the event handler for the press will also call // processEvents() so the move and release will be delivered before returning // from mousePressEvent(). The point is that no events should get lost. @@ -1741,12 +1748,14 @@ void tst_QWindow::tabletEvents() const QPoint global = window.mapToGlobal(local); const QPoint deviceLocal = QHighDpi::toNativeLocalPosition(local, &window); const QPoint deviceGlobal = QHighDpi::toNativePixels(global, window.screen()); - QWindowSystemInterface::handleTabletEvent(&window, true, deviceLocal, deviceGlobal, 1, 2, 0.5, 1, 2, 0.1, 0, 0, 0); + QWindowSystemInterface::handleTabletEvent(&window, deviceLocal, deviceGlobal, + 1, 2, Qt::LeftButton, 0.5, 1, 2, 0.1, 0, 0, 0); QCoreApplication::processEvents(); QTRY_VERIFY(window.eventType == QEvent::TabletPress); QTRY_COMPARE(window.eventGlobal.toPoint(), global); QTRY_COMPARE(window.eventLocal.toPoint(), local); - QWindowSystemInterface::handleTabletEvent(&window, false, deviceLocal, deviceGlobal, 1, 2, 0.5, 1, 2, 0.1, 0, 0, 0); + QWindowSystemInterface::handleTabletEvent(&window, deviceLocal, deviceGlobal, 1, 2, + {}, 0.5, 1, 2, 0.1, 0, 0, 0); QCoreApplication::processEvents(); QTRY_COMPARE(window.eventType, QEvent::TabletRelease); @@ -1782,11 +1791,9 @@ void tst_QWindow::windowModality_QTBUG27039() modalB.setModality(Qt::ApplicationModal); modalB.show(); - QPointF local(5, 5); - QWindowSystemInterface::handleMouseEvent(&modalA, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&modalA, local, local, Qt::NoButton); - QWindowSystemInterface::handleMouseEvent(&modalB, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&modalB, local, local, Qt::NoButton); + QPoint local(5, 5); + QTest::mouseClick(&modalA, Qt::LeftButton, {}, local); + QTest::mouseClick(&modalB, Qt::LeftButton, {}, local); QCoreApplication::processEvents(); // modal A should be blocked since it was shown first, but modal B should not be blocked @@ -1794,8 +1801,7 @@ void tst_QWindow::windowModality_QTBUG27039() QCOMPARE(modalA.mousePressedCount, 0); modalB.hide(); - QWindowSystemInterface::handleMouseEvent(&modalA, local, local, Qt::LeftButton); - QWindowSystemInterface::handleMouseEvent(&modalA, local, local, Qt::NoButton); + QTest::mouseClick(&modalA, Qt::LeftButton, {}, local); QCoreApplication::processEvents(); // modal B has been hidden, modal A should be unblocked again diff --git a/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp b/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp index 9e13dc80b4..531e14d25b 100644 --- a/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp +++ b/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp @@ -35,7 +35,7 @@ #include <private/qcolorspace_p.h> -Q_DECLARE_METATYPE(QColorSpace::ColorSpaceId) +Q_DECLARE_METATYPE(QColorSpace::NamedColorSpace) Q_DECLARE_METATYPE(QColorSpace::Primaries) Q_DECLARE_METATYPE(QColorSpace::TransferFunction) @@ -64,8 +64,12 @@ private slots: void primaries(); void primariesXyz(); + +#ifdef QT_BUILD_INTERNAL void primaries2_data(); void primaries2(); +#endif + void invalidPrimaries(); void changeTransferFunction(); @@ -82,25 +86,25 @@ void tst_QColorSpace::movable() QColorSpace cs2 = QColorSpace::SRgbLinear; QVERIFY(cs1.isValid()); QVERIFY(cs2.isValid()); - QCOMPARE(cs1.colorSpaceId(), QColorSpace::SRgb); + QCOMPARE(cs1, QColorSpace::SRgb); cs2 = std::move(cs1); QVERIFY(!cs1.isValid()); QVERIFY(cs2.isValid()); - QCOMPARE(cs2.colorSpaceId(), QColorSpace::SRgb); - QCOMPARE(cs1.colorSpaceId(), QColorSpace::Undefined); + QCOMPARE(cs2, QColorSpace::SRgb); + QVERIFY(cs1 != QColorSpace::SRgb); QCOMPARE(cs1, QColorSpace()); QColorSpace cs3(std::move(cs2)); QVERIFY(!cs2.isValid()); QVERIFY(cs3.isValid()); - QCOMPARE(cs3.colorSpaceId(), QColorSpace::SRgb); - QCOMPARE(cs2.colorSpaceId(), QColorSpace::Undefined); + QCOMPARE(cs3, QColorSpace::SRgb); + QCOMPARE(cs2, QColorSpace()); } void tst_QColorSpace::namedColorSpaces_data() { - QTest::addColumn<QColorSpace::ColorSpaceId>("colorSpaceId"); + QTest::addColumn<QColorSpace::NamedColorSpace>("namedColorSpace"); QTest::addColumn<QColorSpace::Primaries>("primariesId"); QTest::addColumn<QColorSpace::TransferFunction>("transferFunctionId"); @@ -119,22 +123,19 @@ void tst_QColorSpace::namedColorSpaces_data() QTest::newRow("ProPhoto RGB") << QColorSpace::ProPhotoRgb << QColorSpace::Primaries::ProPhotoRgb << QColorSpace::TransferFunction::ProPhotoRgb; - QTest::newRow("BT.2020") << QColorSpace::Bt2020 - << QColorSpace::Primaries::Bt2020 - << QColorSpace::TransferFunction::Bt2020; } void tst_QColorSpace::namedColorSpaces() { - QFETCH(QColorSpace::ColorSpaceId, colorSpaceId); + QFETCH(QColorSpace::NamedColorSpace, namedColorSpace); QFETCH(QColorSpace::Primaries, primariesId); QFETCH(QColorSpace::TransferFunction, transferFunctionId); - QColorSpace colorSpace = colorSpaceId; + QColorSpace colorSpace = namedColorSpace; QVERIFY(colorSpace.isValid()); - QCOMPARE(colorSpace.colorSpaceId(), colorSpaceId); + QCOMPARE(colorSpace, namedColorSpace); QCOMPARE(colorSpace.primaries(), primariesId); QCOMPARE(colorSpace.transferFunction(), transferFunctionId); } @@ -147,14 +148,14 @@ void tst_QColorSpace::toIccProfile_data() void tst_QColorSpace::toIccProfile() { - QFETCH(QColorSpace::ColorSpaceId, colorSpaceId); + QFETCH(QColorSpace::NamedColorSpace, namedColorSpace); QFETCH(QColorSpace::Primaries, primariesId); QFETCH(QColorSpace::TransferFunction, transferFunctionId); Q_UNUSED(primariesId); Q_UNUSED(transferFunctionId); - QColorSpace colorSpace = colorSpaceId; + QColorSpace colorSpace = namedColorSpace; QByteArray iccProfile = colorSpace.iccProfile(); QVERIFY(!iccProfile.isEmpty()); @@ -172,7 +173,7 @@ void tst_QColorSpace::toIccProfile() void tst_QColorSpace::fromIccProfile_data() { QTest::addColumn<QString>("testProfile"); - QTest::addColumn<QColorSpace::ColorSpaceId>("colorSpaceId"); + QTest::addColumn<QColorSpace::NamedColorSpace>("namedColorSpace"); QTest::addColumn<QColorSpace::TransferFunction>("transferFunction"); QTest::addColumn<QString>("description"); @@ -181,14 +182,14 @@ void tst_QColorSpace::fromIccProfile_data() QTest::newRow("sRGB2014 (ICCv2)") << prefix + "sRGB2014.icc" << QColorSpace::SRgb << QColorSpace::TransferFunction::SRgb << QString("sRGB2014"); // My monitor's profile: - QTest::newRow("HP ZR30w (ICCv4)") << prefix + "HP_ZR30w.icc" << QColorSpace::Unknown + QTest::newRow("HP ZR30w (ICCv4)") << prefix + "HP_ZR30w.icc" << QColorSpace::NamedColorSpace(0) << QColorSpace::TransferFunction::Gamma << QString("HP Z30i"); } void tst_QColorSpace::fromIccProfile() { QFETCH(QString, testProfile); - QFETCH(QColorSpace::ColorSpaceId, colorSpaceId); + QFETCH(QColorSpace::NamedColorSpace, namedColorSpace); QFETCH(QColorSpace::TransferFunction, transferFunction); QFETCH(QString, description); @@ -198,15 +199,17 @@ void tst_QColorSpace::fromIccProfile() QColorSpace fileColorSpace = QColorSpace::fromIccProfile(iccProfile); QVERIFY(fileColorSpace.isValid()); - QCOMPARE(fileColorSpace.colorSpaceId(), colorSpaceId); + if (namedColorSpace) + QCOMPARE(fileColorSpace, namedColorSpace); + QCOMPARE(fileColorSpace.transferFunction(), transferFunction); QCOMPARE(QColorSpacePrivate::get(fileColorSpace)->description, description); } void tst_QColorSpace::imageConversion_data() { - QTest::addColumn<QColorSpace::ColorSpaceId>("fromColorSpace"); - QTest::addColumn<QColorSpace::ColorSpaceId>("toColorSpace"); + QTest::addColumn<QColorSpace::NamedColorSpace>("fromColorSpace"); + QTest::addColumn<QColorSpace::NamedColorSpace>("toColorSpace"); QTest::addColumn<int>("tolerance"); QTest::newRow("sRGB -> Display-P3") << QColorSpace::SRgb << QColorSpace::DisplayP3 << 0; @@ -214,14 +217,13 @@ void tst_QColorSpace::imageConversion_data() QTest::newRow("Display-P3 -> sRGB") << QColorSpace::DisplayP3 << QColorSpace::SRgb << 0; QTest::newRow("Adobe RGB -> sRGB") << QColorSpace::AdobeRgb << QColorSpace::SRgb << 2; QTest::newRow("Display-P3 -> Adobe RGB") << QColorSpace::DisplayP3 << QColorSpace::AdobeRgb << 2; - QTest::newRow("Display-P3 -> BT.2020") << QColorSpace::DisplayP3 << QColorSpace::Bt2020 << 4; QTest::newRow("sRGB -> sRGB Linear") << QColorSpace::SRgb << QColorSpace::SRgbLinear << 0; } void tst_QColorSpace::imageConversion() { - QFETCH(QColorSpace::ColorSpaceId, fromColorSpace); - QFETCH(QColorSpace::ColorSpaceId, toColorSpace); + QFETCH(QColorSpace::NamedColorSpace, fromColorSpace); + QFETCH(QColorSpace::NamedColorSpace, toColorSpace); QFETCH(int, tolerance); QImage testImage(256, 1, QImage::Format_RGB32); @@ -275,7 +277,7 @@ void tst_QColorSpace::loadImage() QVERIFY(!image.isNull()); QVERIFY(image.colorSpace().isValid()); - QCOMPARE(image.colorSpace().colorSpaceId(), QColorSpace::ProPhotoRgb); + QCOMPARE(image.colorSpace(), QColorSpace::ProPhotoRgb); QVERIFY(!image.colorSpace().iccProfile().isEmpty()); QColorSpace defaultProPhotoRgb = QColorSpace::ProPhotoRgb; @@ -345,16 +347,15 @@ void tst_QColorSpace::primariesXyz() QColorSpace adobeRgb = QColorSpace::AdobeRgb; QColorSpace displayP3 = QColorSpace::DisplayP3; QColorSpace proPhotoRgb = QColorSpace::ProPhotoRgb; - QColorSpace bt2020 = QColorSpace::Bt2020; // Check if our calculated matrices, match the precalculated ones. QCOMPARE(QColorSpacePrivate::get(sRgb)->toXyz, QColorMatrix::toXyzFromSRgb()); QCOMPARE(QColorSpacePrivate::get(adobeRgb)->toXyz, QColorMatrix::toXyzFromAdobeRgb()); QCOMPARE(QColorSpacePrivate::get(displayP3)->toXyz, QColorMatrix::toXyzFromDciP3D65()); QCOMPARE(QColorSpacePrivate::get(proPhotoRgb)->toXyz, QColorMatrix::toXyzFromProPhotoRgb()); - QCOMPARE(QColorSpacePrivate::get(bt2020)->toXyz, QColorMatrix::toXyzFromBt2020()); } +#ifdef QT_BUILD_INTERNAL void tst_QColorSpace::primaries2_data() { QTest::addColumn<QColorSpace::Primaries>("primariesId"); @@ -363,7 +364,6 @@ void tst_QColorSpace::primaries2_data() QTest::newRow("DCI-P3 (D65)") << QColorSpace::Primaries::DciP3D65; QTest::newRow("Adobe RGB (1998)") << QColorSpace::Primaries::AdobeRgb; QTest::newRow("ProPhoto RGB") << QColorSpace::Primaries::ProPhotoRgb; - QTest::newRow("BT.2020") << QColorSpace::Primaries::Bt2020; } void tst_QColorSpace::primaries2() @@ -393,12 +393,12 @@ void tst_QColorSpace::primaries2() QCOMPARE(color3.blue(), color1.blue()); QCOMPARE(color3.alpha(), color1.alpha()); } +#endif void tst_QColorSpace::invalidPrimaries() { QColorSpace custom(QPointF(), QPointF(), QPointF(), QPointF(), QColorSpace::TransferFunction::Linear); QVERIFY(!custom.isValid()); - QCOMPARE(custom.colorSpaceId(), QColorSpace::Undefined); } void tst_QColorSpace::changeTransferFunction() @@ -409,7 +409,7 @@ void tst_QColorSpace::changeTransferFunction() QCOMPARE(sRgbLinear.transferFunction(), QColorSpace::TransferFunction::Linear); QCOMPARE(sRgbLinear.gamma(), 1.0f); QCOMPARE(sRgbLinear.primaries(), QColorSpace::Primaries::SRgb); - QCOMPARE(sRgbLinear.colorSpaceId(), QColorSpace::SRgbLinear); + QCOMPARE(sRgbLinear, QColorSpace::SRgbLinear); QCOMPARE(sRgbLinear, QColorSpace(QColorSpace::SRgbLinear)); QVERIFY(sRgbLinear != sRgb); QCOMPARE(sRgbLinear.withTransferFunction(QColorSpace::TransferFunction::SRgb), sRgb); @@ -418,7 +418,6 @@ void tst_QColorSpace::changeTransferFunction() aRgb.setTransferFunction(QColorSpace::TransferFunction::SRgb); QCOMPARE(aRgb.transferFunction(), QColorSpace::TransferFunction::SRgb); QCOMPARE(aRgb.primaries(), QColorSpace::Primaries::AdobeRgb); - QCOMPARE(aRgb.colorSpaceId(), QColorSpace::Unknown); QVERIFY(aRgb != QColorSpace(QColorSpace::AdobeRgb)); QVERIFY(aRgb != sRgb); QCOMPARE(aRgb.withTransferFunction(QColorSpace::TransferFunction::Gamma, 2.2f), diff --git a/tests/auto/gui/painting/qpainterpath/tst_qpainterpath.cpp b/tests/auto/gui/painting/qpainterpath/tst_qpainterpath.cpp index 67cf9a321a..86a8965cec 100644 --- a/tests/auto/gui/painting/qpainterpath/tst_qpainterpath.cpp +++ b/tests/auto/gui/painting/qpainterpath/tst_qpainterpath.cpp @@ -88,7 +88,9 @@ private slots: void testToFillPolygons(); +#if QT_CONFIG(signaling_nan) void testNaNandInfinites(); +#endif void closing(); @@ -1228,6 +1230,7 @@ void tst_QPainterPath::testToFillPolygons() QCOMPARE(polygons.first().count(QPointF(70, 50)), 0); } +#if QT_CONFIG(signaling_nan) void tst_QPainterPath::testNaNandInfinites() { QPainterPath path1; @@ -1271,6 +1274,7 @@ void tst_QPainterPath::testNaNandInfinites() path1.lineTo(QPointF(1, 1)); QVERIFY(path1 != path2); } +#endif // signaling_nan void tst_QPainterPath::connectPathDuplicatePoint() { diff --git a/tests/auto/gui/painting/qpdfwriter/tst_qpdfwriter.cpp b/tests/auto/gui/painting/qpdfwriter/tst_qpdfwriter.cpp index 9e9b0db366..b2b2b685ae 100644 --- a/tests/auto/gui/painting/qpdfwriter/tst_qpdfwriter.cpp +++ b/tests/auto/gui/painting/qpdfwriter/tst_qpdfwriter.cpp @@ -73,12 +73,12 @@ void tst_QPdfWriter::basics() QCOMPARE(writer.pageSize(), QPdfWriter::A5); QCOMPARE(writer.pageSizeMM(), QSizeF(148, 210)); - writer.setPageSize(QPdfWriter::A3); + writer.setPageSize(QPageSize(QPageSize::A3)); QCOMPARE(writer.pageLayout().pageSize().id(), QPageSize::A3); QCOMPARE(writer.pageSize(), QPdfWriter::A3); QCOMPARE(writer.pageSizeMM(), QSizeF(297, 420)); - writer.setPageSizeMM(QSize(210, 297)); + writer.setPageSize(QPageSize(QSize(210, 297), QPageSize::Millimeter)); QCOMPARE(writer.pageLayout().pageSize().id(), QPageSize::A4); QCOMPARE(writer.pageSize(), QPdfWriter::A4); QCOMPARE(writer.pageSizeMM(), QSizeF(210, 297)); @@ -101,9 +101,9 @@ void tst_QPdfWriter::basics() QCOMPARE(writer.margins().right, 20.0); QCOMPARE(writer.margins().top, 20.0); QCOMPARE(writer.margins().bottom, 20.0); - QPdfWriter::Margins margins = {50, 50, 50, 50}; - writer.setMargins(margins); - QCOMPARE(writer.pageLayout().margins(), QMarginsF(50, 50, 50, 50)); + const QMarginsF margins = {50, 50, 50, 50}; + writer.setPageMargins(margins, QPageLayout::Millimeter); + QCOMPARE(writer.pageLayout().margins(), margins); QCOMPARE(writer.pageLayout().units(), QPageLayout::Millimeter); QCOMPARE(writer.margins().left, 50.0); QCOMPARE(writer.margins().right, 50.0); @@ -117,7 +117,7 @@ void tst_QPdfWriter::basics() // Test the old page metrics methods, see also QPrinter tests for the same. void tst_QPdfWriter::testPageMetrics_data() { - QTest::addColumn<int>("pageSize"); + QTest::addColumn<QPageSize::PageSizeId>("pageSizeId"); QTest::addColumn<qreal>("widthMMf"); QTest::addColumn<qreal>("heightMMf"); QTest::addColumn<bool>("setMargins"); @@ -126,17 +126,24 @@ void tst_QPdfWriter::testPageMetrics_data() QTest::addColumn<qreal>("topMMf"); QTest::addColumn<qreal>("bottomMMf"); - QTest::newRow("A4") << int(QPdfWriter::A4) << 210.0 << 297.0 << false << 3.53 << 3.53 << 3.53 << 3.53; - QTest::newRow("A4 Margins") << int(QPdfWriter::A4) << 210.0 << 297.0 << true << 20.0 << 30.0 << 40.0 << 50.0; - QTest::newRow("Portrait") << -1 << 345.0 << 678.0 << false << 3.53 << 3.53 << 3.53 << 3.53; - QTest::newRow("Portrait Margins") << -1 << 345.0 << 678.0 << true << 20.0 << 30.0 << 40.0 << 50.0; - QTest::newRow("Landscape") << -1 << 678.0 << 345.0 << false << 3.53 << 3.53 << 3.53 << 3.53; - QTest::newRow("Landscape Margins") << -1 << 678.0 << 345.0 << true << 20.0 << 30.0 << 40.0 << 50.0; + QTest::newRow("A4") << QPageSize::A4 << 210.0 << 297.0 << false + << 3.53 << 3.53 << 3.53 << 3.53; + QTest::newRow("A4 Margins") << QPageSize::A4 << 210.0 << 297.0 << true + << 20.0 << 30.0 << 40.0 << 50.0; + + QTest::newRow("Portrait") << QPageSize::Custom << 345.0 << 678.0 << false + << 3.53 << 3.53 << 3.53 << 3.53; + QTest::newRow("Portrait Margins") << QPageSize::Custom << 345.0 << 678.0 << true + << 20.0 << 30.0 << 40.0 << 50.0; + QTest::newRow("Landscape") << QPageSize::Custom << 678.0 << 345.0 << false + << 3.53 << 3.53 << 3.53 << 3.53; + QTest::newRow("Landscape Margins") << QPageSize::Custom << 678.0 << 345.0 << true + << 20.0 << 30.0 << 40.0 << 50.0; } void tst_QPdfWriter::testPageMetrics() { - QFETCH(int, pageSize); + QFETCH(QPageSize::PageSizeId, pageSizeId); QFETCH(qreal, widthMMf); QFETCH(qreal, heightMMf); QFETCH(bool, setMargins); @@ -161,17 +168,13 @@ void tst_QPdfWriter::testPageMetrics() QCOMPARE(writer.margins().bottom, bottomMMf); } - // Set the given size, in Portrait mode - if (pageSize < 0) { - writer.setPageSize(QPageSize(sizeMMf, QPageSize::Millimeter)); - QCOMPARE(writer.pageSize(), QPdfWriter::Custom); - QCOMPARE(writer.pageLayout().pageSize().id(), QPageSize::Custom); - } else { - writer.setPageSize(QPdfWriter::PageSize(pageSize)); - QCOMPARE(writer.pageSize(), QPdfWriter::PageSize(pageSize)); - QCOMPARE(writer.pageLayout().pageSize().id(), QPageSize::PageSizeId(pageSize)); - } + const QPageSize pageSize = pageSizeId == QPageSize::Custom + ? QPageSize(sizeMMf, QPageSize::Millimeter) : QPageSize(pageSizeId); + writer.setPageSize(pageSize); + QCOMPARE(writer.pageLayout().pageSize().id(), pageSizeId); + QCOMPARE(int(writer.pageSize()), int(pageSizeId)); + QCOMPARE(writer.pageLayout().orientation(), QPageLayout::Portrait); QCOMPARE(writer.margins().left, leftMMf); QCOMPARE(writer.margins().right, rightMMf); @@ -187,13 +190,8 @@ void tst_QPdfWriter::testPageMetrics() // Now switch to Landscape mode, size should be unchanged, but rect and metrics should change writer.setPageOrientation(QPageLayout::Landscape); - if (pageSize < 0) { - QCOMPARE(writer.pageSize(), QPdfWriter::Custom); - QCOMPARE(writer.pageLayout().pageSize().id(), QPageSize::Custom); - } else { - QCOMPARE(writer.pageSize(), QPdfWriter::PageSize(pageSize)); - QCOMPARE(writer.pageLayout().pageSize().id(), QPageSize::PageSizeId(pageSize)); - } + QCOMPARE(writer.pageLayout().pageSize().id(), pageSizeId); + QCOMPARE(int(writer.pageSize()), int(pageSizeId)); QCOMPARE(writer.pageLayout().orientation(), QPageLayout::Landscape); QCOMPARE(writer.margins().left, leftMMf); QCOMPARE(writer.margins().right, rightMMf); @@ -215,15 +213,9 @@ void tst_QPdfWriter::testPageMetrics() // Now while in Landscape mode, set the size again, results should be the same - if (pageSize < 0) { - writer.setPageSize(QPageSize(sizeMMf, QPageSize::Millimeter)); - QCOMPARE(writer.pageSize(), QPdfWriter::Custom); - QCOMPARE(writer.pageLayout().pageSize().id(), QPageSize::Custom); - } else { - writer.setPageSize(QPdfWriter::PageSize(pageSize)); - QCOMPARE(writer.pageSize(), QPdfWriter::PageSize(pageSize)); - QCOMPARE(writer.pageLayout().pageSize().id(), QPageSize::PageSizeId(pageSize)); - } + writer.setPageSize(pageSize); + QCOMPARE(writer.pageLayout().pageSize().id(), pageSizeId); + QCOMPARE(int(writer.pageSize()), int(pageSizeId)); QCOMPARE(writer.pageLayout().orientation(), QPageLayout::Landscape); QCOMPARE(writer.margins().left, leftMMf); QCOMPARE(writer.margins().right, rightMMf); diff --git a/tests/auto/gui/rhi/qrhi/data/compile.bat b/tests/auto/gui/rhi/qrhi/data/compile.bat new file mode 100644 index 0000000000..5b8a77b833 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/compile.bat @@ -0,0 +1,48 @@ +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: Copyright (C) 2019 The Qt Company Ltd. +:: Contact: https://www.qt.io/licensing/ +:: +:: This file is part of the QtQuick module of the Qt Toolkit. +:: +:: $QT_BEGIN_LICENSE:LGPL$ +:: Commercial License Usage +:: Licensees holding valid commercial Qt licenses may use this file in +:: accordance with the commercial license agreement provided with the +:: Software or, alternatively, in accordance with the terms contained in +:: a written agreement between you and The Qt Company. For licensing terms +:: and conditions see https://www.qt.io/terms-conditions. For further +:: information use the contact form at https://www.qt.io/contact-us. +:: +:: GNU Lesser General Public License Usage +:: Alternatively, this file may be used under the terms of the GNU Lesser +:: General Public License version 3 as published by the Free Software +:: Foundation and appearing in the file LICENSE.LGPL3 included in the +:: packaging of this file. Please review the following information to +:: ensure the GNU Lesser General Public License version 3 requirements +:: will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +:: +:: GNU General Public License Usage +:: Alternatively, this file may be used under the terms of the GNU +:: General Public License version 2.0 or (at your option) the GNU General +:: Public license version 3 or any later version approved by the KDE Free +:: Qt Foundation. The licenses are as published by the Free Software +:: Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +:: included in the packaging of this file. Please review the following +:: information to ensure the GNU General Public License requirements will +:: be met: https://www.gnu.org/licenses/gpl-2.0.html and +:: https://www.gnu.org/licenses/gpl-3.0.html. +:: +:: $QT_END_LICENSE$ +:: +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +:: Note the -c argument: we do not want runtime HLSL compilation since that is +:: not an option on UWP (WinRT). This means that running qsb must happen on Windows. + +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.vert.qsb simple.vert +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.frag.qsb simple.frag +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.vert.qsb simpletextured.vert +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.frag.qsb simpletextured.frag +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.vert.qsb textured.vert +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.frag.qsb textured.frag diff --git a/tests/auto/gui/rhi/qrhi/data/qt256.png b/tests/auto/gui/rhi/qrhi/data/qt256.png Binary files differnew file mode 100644 index 0000000000..30c621c9c6 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/qt256.png diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag b/tests/auto/gui/rhi/qrhi/data/simple.frag new file mode 100644 index 0000000000..2aa500e09a --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simple.frag @@ -0,0 +1,8 @@ +#version 440 + +layout(location = 0) out vec4 fragColor; + +void main() +{ + fragColor = vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb Binary files differnew file mode 100644 index 0000000000..264b71ec0f --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/simple.vert b/tests/auto/gui/rhi/qrhi/data/simple.vert new file mode 100644 index 0000000000..16ee61beca --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simple.vert @@ -0,0 +1,10 @@ +#version 440 + +layout(location = 0) in vec4 position; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + gl_Position = position; +} diff --git a/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb Binary files differnew file mode 100644 index 0000000000..59080b60c6 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.frag b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag new file mode 100644 index 0000000000..630df7b807 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag @@ -0,0 +1,13 @@ +#version 440 + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 fragColor; + +layout(binding = 0) uniform sampler2D tex; + +void main() +{ + vec4 c = texture(tex, uv); + c.rgb *= c.a; + fragColor = c; +} diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb Binary files differnew file mode 100644 index 0000000000..f302702aa9 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.vert b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert new file mode 100644 index 0000000000..1dd204f84d --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert @@ -0,0 +1,14 @@ +#version 440 + +layout(location = 0) in vec4 position; +layout(location = 1) in vec2 texcoord; + +layout(location = 0) out vec2 uv; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + uv = texcoord; + gl_Position = position; +} diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb Binary files differnew file mode 100644 index 0000000000..e4f12bfb9e --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/texture.frag b/tests/auto/gui/rhi/qrhi/data/texture.frag deleted file mode 100644 index e6021fe905..0000000000 --- a/tests/auto/gui/rhi/qrhi/data/texture.frag +++ /dev/null @@ -1,12 +0,0 @@ -#version 440 - -layout(location = 0) in vec2 v_texcoord; - -layout(location = 0) out vec4 fragColor; - -layout(binding = 1) uniform sampler2D tex; - -void main() -{ - fragColor = texture(tex, v_texcoord); -} diff --git a/tests/auto/gui/rhi/qrhi/data/textured.frag b/tests/auto/gui/rhi/qrhi/data/textured.frag new file mode 100644 index 0000000000..605410b028 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/textured.frag @@ -0,0 +1,19 @@ +#version 440 + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 matrix; + float opacity; +} ubuf; + +layout(binding = 1) uniform sampler2D tex; + +void main() +{ + vec4 c = texture(tex, uv); + c.a *= ubuf.opacity; + c.rgb *= c.a; + fragColor = c; +} diff --git a/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb b/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb Binary files differnew file mode 100644 index 0000000000..0a039137ec --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/texture.vert b/tests/auto/gui/rhi/qrhi/data/textured.vert index de486cb772..f1ccf2ee50 100644 --- a/tests/auto/gui/rhi/qrhi/data/texture.vert +++ b/tests/auto/gui/rhi/qrhi/data/textured.vert @@ -3,16 +3,17 @@ layout(location = 0) in vec4 position; layout(location = 1) in vec2 texcoord; -layout(location = 0) out vec2 v_texcoord; +layout(location = 0) out vec2 uv; layout(std140, binding = 0) uniform buf { - mat4 mvp; + mat4 matrix; + float opacity; } ubuf; out gl_PerVertex { vec4 gl_Position; }; void main() { - v_texcoord = texcoord; - gl_Position = ubuf.mvp * position; + uv = texcoord; + gl_Position = ubuf.matrix * position; } diff --git a/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb b/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb Binary files differnew file mode 100644 index 0000000000..7853f77943 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 897613d525..768b227ecd 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -30,10 +30,13 @@ #include <QThread> #include <QFile> #include <QOffscreenSurface> +#include <QPainter> + #include <QtGui/private/qrhi_p.h> #include <QtGui/private/qrhinull_p.h> #if QT_CONFIG(opengl) +# include <QOpenGLContext> # include <QtGui/private/qrhigles2_p.h> # define TST_GL #endif @@ -65,8 +68,29 @@ private slots: void initTestCase(); void cleanupTestCase(); + void rhiTestData(); void create_data(); void create(); + void nativeHandles_data(); + void nativeHandles(); + void resourceUpdateBatchBuffer_data(); + void resourceUpdateBatchBuffer(); + void resourceUpdateBatchRGBATextureUpload_data(); + void resourceUpdateBatchRGBATextureUpload(); + void resourceUpdateBatchRGBATextureCopy_data(); + void resourceUpdateBatchRGBATextureCopy(); + void resourceUpdateBatchRGBATextureMip_data(); + void resourceUpdateBatchRGBATextureMip(); + void invalidPipeline_data(); + void invalidPipeline(); + void renderToTextureSimple_data(); + void renderToTextureSimple(); + void renderToTextureTexturedQuad_data(); + void renderToTextureTexturedQuad(); + void renderToTextureTexturedQuadAndUniformBuffer_data(); + void renderToTextureTexturedQuadAndUniformBuffer(); + void renderToWindowSimple_data(); + void renderToWindowSimple(); private: struct { @@ -99,9 +123,26 @@ void tst_QRhi::initTestCase() #endif #ifdef TST_VK +#ifndef Q_OS_ANDROID + vulkanInstance.setLayers({ QByteArrayLiteral("VK_LAYER_LUNARG_standard_validation") }); +#else + vulkanInstance.setLayers({ QByteArrayLiteral("VK_LAYER_GOOGLE_threading"), + QByteArrayLiteral("VK_LAYER_LUNARG_parameter_validation"), + QByteArrayLiteral("VK_LAYER_LUNARG_object_tracker"), + QByteArrayLiteral("VK_LAYER_LUNARG_core_validation"), + QByteArrayLiteral("VK_LAYER_LUNARG_image"), + QByteArrayLiteral("VK_LAYER_LUNARG_swapchain"), + QByteArrayLiteral("VK_LAYER_GOOGLE_unique_objects") }); +#endif + vulkanInstance.setExtensions(QByteArrayList() + << "VK_KHR_get_physical_device_properties2"); vulkanInstance.create(); initParams.vk.inst = &vulkanInstance; #endif + +#ifdef TST_D3D11 + initParams.d3d.enableDebugLayer = true; +#endif } void tst_QRhi::cleanupTestCase() @@ -113,7 +154,7 @@ void tst_QRhi::cleanupTestCase() delete fallbackSurface; } -void tst_QRhi::create_data() +void tst_QRhi::rhiTestData() { QTest::addColumn<QRhi::Implementation>("impl"); QTest::addColumn<QRhiInitParams *>("initParams"); @@ -134,6 +175,11 @@ void tst_QRhi::create_data() #endif } +void tst_QRhi::create_data() +{ + rhiTestData(); +} + static int aligned(int v, int a) { return (v + a - 1) & ~(a - 1); @@ -154,6 +200,8 @@ void tst_QRhi::create() QCOMPARE(rhi->backend(), impl); QCOMPARE(rhi->thread(), QThread::currentThread()); + // do a basic smoke test for the apis that do not directly render anything + int cleanupOk = 0; QRhi *rhiPtr = rhi.data(); auto cleanupFunc = [rhiPtr, &cleanupOk](QRhi *dyingRhi) { @@ -211,9 +259,11 @@ void tst_QRhi::create() const int texMin = rhi->resourceLimit(QRhi::TextureSizeMin); const int texMax = rhi->resourceLimit(QRhi::TextureSizeMax); const int maxAtt = rhi->resourceLimit(QRhi::MaxColorAttachments); + const int framesInFlight = rhi->resourceLimit(QRhi::FramesInFlight); QVERIFY(texMin >= 1); QVERIFY(texMax >= texMin); QVERIFY(maxAtt >= 1); + QVERIFY(framesInFlight >= 1); QVERIFY(rhi->nativeHandles()); QVERIFY(rhi->profiler()); @@ -230,17 +280,1450 @@ void tst_QRhi::create() QRhi::NonFourAlignedEffectiveIndexBufferOffset, QRhi::NPOTTextureRepeat, QRhi::RedOrAlpha8IsRed, - QRhi::ElementIndexUint + QRhi::ElementIndexUint, + QRhi::Compute, + QRhi::WideLines, + QRhi::VertexShaderPointSize, + QRhi::BaseVertex, + QRhi::BaseInstance, + QRhi::TriangleFanTopology, + QRhi::ReadBackNonUniformBuffer, + QRhi::ReadBackNonBaseMipLevel }; for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i) rhi->isFeatureSupported(features[i]); QVERIFY(rhi->isTextureFormatSupported(QRhiTexture::RGBA8)); + rhi->releaseCachedResources(); + + QVERIFY(!rhi->isDeviceLost()); + rhi.reset(); QCOMPARE(cleanupOk, 1); } } +void tst_QRhi::nativeHandles_data() +{ + rhiTestData(); +} + +void tst_QRhi::nativeHandles() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing native handles"); + + // QRhi::nativeHandles() + { + const QRhiNativeHandles *rhiHandles = rhi->nativeHandles(); + Q_ASSERT(rhiHandles); + + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanNativeHandles *vkHandles = static_cast<const QRhiVulkanNativeHandles *>(rhiHandles); + QVERIFY(vkHandles->physDev); + QVERIFY(vkHandles->dev); + QVERIFY(vkHandles->gfxQueueFamilyIdx >= 0); + QVERIFY(vkHandles->gfxQueue); + QVERIFY(vkHandles->cmdPool); + QVERIFY(vkHandles->vmemAllocator); + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + { + const QRhiGles2NativeHandles *glHandles = static_cast<const QRhiGles2NativeHandles *>(rhiHandles); + QVERIFY(glHandles->context); + QVERIFY(glHandles->context->isValid()); + glHandles->context->doneCurrent(); + QVERIFY(!QOpenGLContext::currentContext()); + rhi->makeThreadLocalNativeContextCurrent(); + QVERIFY(QOpenGLContext::currentContext() == glHandles->context); + } + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + { + const QRhiD3D11NativeHandles *d3dHandles = static_cast<const QRhiD3D11NativeHandles *>(rhiHandles); + QVERIFY(d3dHandles->dev); + QVERIFY(d3dHandles->context); + } + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + { + const QRhiMetalNativeHandles *mtlHandles = static_cast<const QRhiMetalNativeHandles *>(rhiHandles); + QVERIFY(mtlHandles->dev); + QVERIFY(mtlHandles->cmdQueue); + } + break; +#endif + default: + Q_ASSERT(false); + } + } + + // QRhiTexture::nativeHandles() + { + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 256))); + QVERIFY(tex->build()); + + const QRhiNativeHandles *texHandles = tex->nativeHandles(); + QVERIFY(texHandles); + + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanTextureNativeHandles *vkHandles = static_cast<const QRhiVulkanTextureNativeHandles *>(texHandles); + QVERIFY(vkHandles->image); + QVERIFY(vkHandles->layout >= 1); // VK_IMAGE_LAYOUT_GENERAL + QVERIFY(vkHandles->layout <= 8); // VK_IMAGE_LAYOUT_PREINITIALIZED + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + { + const QRhiGles2TextureNativeHandles *glHandles = static_cast<const QRhiGles2TextureNativeHandles *>(texHandles); + QVERIFY(glHandles->texture); + } + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + { + const QRhiD3D11TextureNativeHandles *d3dHandles = static_cast<const QRhiD3D11TextureNativeHandles *>(texHandles); + QVERIFY(d3dHandles->texture); + } + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + { + const QRhiMetalTextureNativeHandles *mtlHandles = static_cast<const QRhiMetalTextureNativeHandles *>(texHandles); + QVERIFY(mtlHandles->texture); + } + break; +#endif + default: + Q_ASSERT(false); + } + } + + // QRhiCommandBuffer::nativeHandles() + { + QRhiCommandBuffer *cb = nullptr; + QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb); + QVERIFY(result == QRhi::FrameOpSuccess); + QVERIFY(cb); + + const QRhiNativeHandles *cbHandles = cb->nativeHandles(); + // no null check here, backends where not applicable will return null + + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanCommandBufferNativeHandles *vkHandles = static_cast<const QRhiVulkanCommandBufferNativeHandles *>(cbHandles); + QVERIFY(vkHandles); + QVERIFY(vkHandles->commandBuffer); + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + { + const QRhiMetalCommandBufferNativeHandles *mtlHandles = static_cast<const QRhiMetalCommandBufferNativeHandles *>(cbHandles); + QVERIFY(mtlHandles); + QVERIFY(mtlHandles->commandBuffer); + QVERIFY(!mtlHandles->encoder); + + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget)); + QVERIFY(tex->build()); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ tex.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + QVERIFY(rpDesc); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + cb->beginPass(rt.data(), Qt::red, { 1.0f, 0 }); + QVERIFY(static_cast<const QRhiMetalCommandBufferNativeHandles *>(cb->nativeHandles())->encoder); + cb->endPass(); + } + break; +#endif + default: + Q_ASSERT(false); + } + + rhi->endOffscreenFrame(); + } + + // QRhiRenderPassDescriptor::nativeHandles() + { + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget)); + QVERIFY(tex->build()); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ tex.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + QVERIFY(rpDesc); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + const QRhiNativeHandles *rpHandles = rpDesc->nativeHandles(); + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanRenderPassNativeHandles *vkHandles = static_cast<const QRhiVulkanRenderPassNativeHandles *>(rpHandles); + QVERIFY(vkHandles); + QVERIFY(vkHandles->renderPass); + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + break; +#endif + default: + Q_ASSERT(false); + } + } +} + +static bool submitResourceUpdates(QRhi *rhi, QRhiResourceUpdateBatch *batch) +{ + QRhiCommandBuffer *cb = nullptr; + QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb); + if (result != QRhi::FrameOpSuccess) { + qWarning("beginOffscreenFrame returned %d", result); + return false; + } + if (!cb) { + qWarning("No command buffer from beginOffscreenFrame"); + return false; + } + cb->resourceUpdate(batch); + rhi->endOffscreenFrame(); + return true; +} + +void tst_QRhi::resourceUpdateBatchBuffer_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchBuffer() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing buffer resource updates"); + + const int bufferSize = 23; + const QByteArray a(bufferSize, 'A'); + const QByteArray b(bufferSize, 'B'); + + // dynamic buffer, updates, readback + { + QScopedPointer<QRhiBuffer> dynamicBuffer(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, bufferSize)); + QVERIFY(dynamicBuffer->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + batch->updateDynamicBuffer(dynamicBuffer.data(), 10, bufferSize - 10, a.constData()); + batch->updateDynamicBuffer(dynamicBuffer.data(), 0, 12, b.constData()); + + QRhiBufferReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + + // Offscreen frames are synchronous, so the readback must have + // completed at this point. With swapchain frames this would not be the + // case. + QVERIFY(readCompleted); + QVERIFY(readResult.data.size() == 10); + QCOMPARE(readResult.data.left(7), QByteArrayLiteral("BBBBBBB")); + QCOMPARE(readResult.data.mid(7), QByteArrayLiteral("AAA")); + } + + // static buffer, updates, readback + { + QScopedPointer<QRhiBuffer> dynamicBuffer(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::VertexBuffer, bufferSize)); + QVERIFY(dynamicBuffer->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + batch->uploadStaticBuffer(dynamicBuffer.data(), 10, bufferSize - 10, a.constData()); + batch->uploadStaticBuffer(dynamicBuffer.data(), 0, 12, b.constData()); + + QRhiBufferReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + + if (rhi->isFeatureSupported(QRhi::ReadBackNonUniformBuffer)) + batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + + if (rhi->isFeatureSupported(QRhi::ReadBackNonUniformBuffer)) { + QVERIFY(readCompleted); + QVERIFY(readResult.data.size() == 10); + QCOMPARE(readResult.data.left(7), QByteArrayLiteral("BBBBBBB")); + QCOMPARE(readResult.data.mid(7), QByteArrayLiteral("AAA")); + } else { + qDebug("Skipping verifying buffer contents because readback is not supported"); + } + } +} + +inline bool imageRGBAEquals(const QImage &a, const QImage &b) +{ + const int maxFuzz = 1; + + if (a.size() != b.size()) + return false; + + const QImage image0 = a.convertToFormat(QImage::Format_RGBA8888_Premultiplied); + const QImage image1 = b.convertToFormat(QImage::Format_RGBA8888_Premultiplied); + + const int width = image0.width(); + const int height = image0.height(); + for (int y = 0; y < height; ++y) { + const quint32 *p0 = reinterpret_cast<const quint32 *>(image0.constScanLine(y)); + const quint32 *p1 = reinterpret_cast<const quint32 *>(image1.constScanLine(y)); + int x = width - 1; + while (x-- >= 0) { + const QRgb c0(*p0++); + const QRgb c1(*p1++); + const int red = qAbs(qRed(c0) - qRed(c1)); + const int green = qAbs(qGreen(c0) - qGreen(c1)); + const int blue = qAbs(qBlue(c0) - qBlue(c1)); + const int alpha = qAbs(qAlpha(c0) - qAlpha(c1)); + if (red > maxFuzz || green > maxFuzz || blue > maxFuzz || alpha > maxFuzz) + return false; + } + } + + return true; +} + +void tst_QRhi::resourceUpdateBatchRGBATextureUpload_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchRGBATextureUpload() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing texture resource updates"); + + QImage image(234, 123, QImage::Format_RGBA8888_Premultiplied); + image.fill(Qt::red); + QPainter painter; + const QPoint greenRectPos(35, 50); + const QSize greenRectSize(100, 50); + painter.begin(&image); + painter.fillRect(QRect(greenRectPos, greenRectSize), Qt::green); + painter.end(); + + // simple image upload; uploading and reading back RGBA8 is supported by the Null backend even + { + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, image.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->uploadTexture(texture.data(), image); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + // like with buffers, the readback is now complete due to endOffscreenFrame() + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, image.size()); + + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(imageRGBAEquals(image, wrapperImage)); + } + + // the same with raw data + { + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, image.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QRhiTextureUploadEntry upload(0, 0, { image.constBits(), int(image.sizeInBytes()) }); + QRhiTextureUploadDescription uploadDesc(upload); + batch->uploadTexture(texture.data(), uploadDesc); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, image.size()); + + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(imageRGBAEquals(image, wrapperImage)); + } + + // partial image upload at a non-zero destination position + { + const QSize copySize(30, 40); + const int gap = 10; + const QSize fullSize(copySize.width() + gap, copySize.height() + gap); + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, fullSize, + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QImage clearImage(fullSize, image.format()); + clearImage.fill(Qt::black); + batch->uploadTexture(texture.data(), clearImage); + + // copy green pixels of copySize to (gap, gap), leaving a black bar of + // gap pixels on the left and top + QRhiTextureSubresourceUploadDescription desc; + desc.setImage(image); + desc.setSourceSize(copySize); + desc.setDestinationTopLeft(QPoint(gap, gap)); + desc.setSourceTopLeft(greenRectPos); + + batch->uploadTexture(texture.data(), QRhiTextureUploadDescription({ 0, 0, desc })); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, clearImage.size()); + + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(!imageRGBAEquals(clearImage, wrapperImage)); + + QImage expectedImage = clearImage; + QPainter painter(&expectedImage); + painter.fillRect(QRect(QPoint(gap, gap), QSize(copySize)), Qt::green); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } + + // the same (partial upload) with raw data as source + { + const QSize copySize(30, 40); + const int gap = 10; + const QSize fullSize(copySize.width() + gap, copySize.height() + gap); + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, fullSize, + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QImage clearImage(fullSize, image.format()); + clearImage.fill(Qt::black); + batch->uploadTexture(texture.data(), clearImage); + + // SourceTopLeft is not supported for non-QImage-based uploads. + const QImage im = image.copy(QRect(greenRectPos, copySize)); + QRhiTextureSubresourceUploadDescription desc; + desc.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(im.constBits()), + int(im.sizeInBytes()))); + desc.setSourceSize(copySize); + desc.setDestinationTopLeft(QPoint(gap, gap)); + + batch->uploadTexture(texture.data(), QRhiTextureUploadDescription({ 0, 0, desc })); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, clearImage.size()); + + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(!imageRGBAEquals(clearImage, wrapperImage)); + + QImage expectedImage = clearImage; + QPainter painter(&expectedImage); + painter.fillRect(QRect(QPoint(gap, gap), QSize(copySize)), Qt::green); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } + + // now a QImage from an actual file + { + QImage inputImage; + inputImage.load(QLatin1String(":/data/qt256.png")); + QVERIFY(!inputImage.isNull()); + inputImage = std::move(inputImage).convertToFormat(image.format()); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->uploadTexture(texture.data(), inputImage); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + inputImage.format()); + + QVERIFY(imageRGBAEquals(inputImage, wrapperImage)); + } +} + +void tst_QRhi::resourceUpdateBatchRGBATextureCopy_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchRGBATextureCopy() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing texture resource updates"); + + QImage red(256, 256, QImage::Format_RGBA8888_Premultiplied); + red.fill(Qt::red); + + QImage green(35, 73, red.format()); + green.fill(Qt::green); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QScopedPointer<QRhiTexture> redTexture(rhi->newTexture(QRhiTexture::RGBA8, red.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(redTexture->build()); + batch->uploadTexture(redTexture.data(), red); + + QScopedPointer<QRhiTexture> greenTexture(rhi->newTexture(QRhiTexture::RGBA8, green.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(greenTexture->build()); + batch->uploadTexture(greenTexture.data(), green); + + // 1. simple copy red -> texture; 2. subimage copy green -> texture; 3. partial subimage copy green -> texture + { + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, red.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + // 1. + batch->copyTexture(texture.data(), redTexture.data()); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + QVERIFY(imageRGBAEquals(red, wrapperImage)); + + batch = rhi->nextResourceUpdateBatch(); + readCompleted = false; + + // 2. + QRhiTextureCopyDescription copyDesc; + copyDesc.setDestinationTopLeft(QPoint(15, 23)); + batch->copyTexture(texture.data(), greenTexture.data(), copyDesc); + + batch->readBackTexture(texture.data(), &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + wrapperImage = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + + QImage expectedImage = red; + QPainter painter(&expectedImage); + painter.drawImage(copyDesc.destinationTopLeft(), green); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + + batch = rhi->nextResourceUpdateBatch(); + readCompleted = false; + + // 3. + copyDesc.setDestinationTopLeft(QPoint(125, 89)); + copyDesc.setSourceTopLeft(QPoint(5, 5)); + copyDesc.setPixelSize(QSize(26, 45)); + batch->copyTexture(texture.data(), greenTexture.data(), copyDesc); + + batch->readBackTexture(texture.data(), &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + wrapperImage = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + + painter.begin(&expectedImage); + painter.drawImage(copyDesc.destinationTopLeft(), green, + QRect(copyDesc.sourceTopLeft(), copyDesc.pixelSize())); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } +} + +void tst_QRhi::resourceUpdateBatchRGBATextureMip_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchRGBATextureMip() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing texture resource updates"); + + + QImage red(512, 512, QImage::Format_RGBA8888_Premultiplied); + red.fill(Qt::red); + + const QRhiTexture::Flags textureFlags = + QRhiTexture::UsedAsTransferSource + | QRhiTexture::MipMapped + | QRhiTexture::UsedWithGenerateMips; + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, red.size(), 1, textureFlags)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->uploadTexture(texture.data(), red); + batch->generateMips(texture.data()); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + + const int levelCount = rhi->mipLevelsForSize(red.size()); + QCOMPARE(levelCount, 10); + for (int level = 0; level < levelCount; ++level) { + batch = rhi->nextResourceUpdateBatch(); + + QRhiReadbackDescription readDesc(texture.data()); + readDesc.setLevel(level); + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(readDesc, &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + + const QSize expectedSize = rhi->sizeForMipLevel(level, texture->pixelSize()); + QCOMPARE(readResult.pixelSize, expectedSize); + + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + QImage expectedImage; + if (level == 0 || rhi->isFeatureSupported(QRhi::ReadBackNonBaseMipLevel)) { + // Compare to a scaled version; we can do this safely only because we + // only have plain red pixels in the source image. + expectedImage = red.scaled(expectedSize); + } else { + qDebug("Expecting all-zero image for level %d because reading back a level other than 0 is not supported", level); + expectedImage = QImage(readResult.pixelSize, red.format()); + expectedImage.fill(0); + } + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } +} + +static QShader loadShader(const char *name) +{ + QFile f(QString::fromUtf8(name)); + if (f.open(QIODevice::ReadOnly)) { + const QByteArray contents = f.readAll(); + return QShader::fromSerialized(contents); + } + return QShader(); +} + +void tst_QRhi::invalidPipeline_data() +{ + rhiTestData(); +} + +void tst_QRhi::invalidPipeline() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing empty shader"); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(256, 256), 1, QRhiTexture::RenderTarget)); + QVERIFY(texture->build()); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->build()); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + + // no stages + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + QShader vs; + QShader fs; + + // no shaders in the stages + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + vs = loadShader(":/data/simple.vert.qsb"); + QVERIFY(vs.isValid()); + fs = loadShader(":/data/simple.frag.qsb"); + QVERIFY(fs.isValid()); + + // no vertex stage + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + // no vertex inputs + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setRenderPassDescriptor(rpDesc.data()); + pipeline->setShaderResourceBindings(srb.data()); + QVERIFY(!pipeline->build()); + + // no renderpass descriptor + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + QVERIFY(!pipeline->build()); + + // no shader resource bindings + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + // correct + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setRenderPassDescriptor(rpDesc.data()); + pipeline->setShaderResourceBindings(srb.data()); + QVERIFY(pipeline->build()); +} + +void tst_QRhi::renderToTextureSimple_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureSimple() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + const QSize outputSize(1920, 1080); + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, outputSize, 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float vertices[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + 0.0f, 1.0f + }; + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), vertices); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->build()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + QShader vs = loadShader(":/data/simple.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simple.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->build()); + + cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(pipeline.data()); + cb->setViewport({ 0, 0, float(outputSize.width()), float(outputSize.height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(3); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); // non-owning, no copy needed because readResult outlives result + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + // Offscreen frames are synchronous, so the readback is guaranteed to + // complete at this point. This would not be the case with swapchain-based + // frames. + QCOMPARE(result.size(), texture->pixelSize()); + + if (impl == QRhi::Null) + return; + + // Now we have a red rectangle on blue background. + const int y = 100; + const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y)); + int x = result.width() - 1; + int redCount = 0; + int blueCount = 0; + const int maxFuzz = 1; + while (x-- >= 0) { + const QRgb c(*p++); + if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0) + ++redCount; + else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz)) + ++blueCount; + else + QFAIL("Encountered a pixel that is neither red or blue"); + } + + QCOMPARE(redCount + blueCount, texture->pixelSize().width()); + + // The triangle is "pointing up" in the resulting image with OpenGL + // (because Y is up both in normalized device coordinates and in images) + // and Vulkan (because Y is down in both and the vertex data was specified + // with Y up in mind), but "pointing down" with D3D (because Y is up in NDC + // but down in images). + if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC()) + QVERIFY(redCount < blueCount); + else + QVERIFY(redCount > blueCount); +} + +void tst_QRhi::renderToTextureTexturedQuad_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureTexturedQuad() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + QImage inputImage; + inputImage.load(QLatin1String(":/data/qt256.png")); + QVERIFY(!inputImage.isNull()); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float verticesUvs[] = { + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(verticesUvs))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), verticesUvs); + + QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(inputTexture->build()); + updates->uploadTexture(inputTexture.data(), inputImage); + + QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler->build()); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + srb->setBindings({ + QRhiShaderResourceBinding::sampledTexture(0, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data()) + }); + QVERIFY(srb->build()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); + QShader vs = loadShader(":/data/simpletextured.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simpletextured.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 4 * sizeof(float) } }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } + }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->build()); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(pipeline.data()); + cb->setShaderResources(); + cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(4); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + + QVERIFY(!result.isNull()); + + if (impl == QRhi::Null) + return; + + // Flip with D3D and Metal because these have Y down in images. Vulkan does + // not need this because there Y is down both in images and in NDC, which + // just happens to give correct results with our OpenGL-targeted vertex and + // UV data. + if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) + result = std::move(result).mirrored(); + + // check a few points that are expected to match regardless of the implementation + QRgb white = qRgba(255, 255, 255, 255); + QCOMPARE(result.pixel(79, 77), white); + QCOMPARE(result.pixel(124, 81), white); + QCOMPARE(result.pixel(128, 149), white); + QCOMPARE(result.pixel(120, 189), white); + QCOMPARE(result.pixel(116, 185), white); + + QRgb empty = qRgba(0, 0, 0, 0); + QCOMPARE(result.pixel(11, 45), empty); + QCOMPARE(result.pixel(246, 202), empty); + QCOMPARE(result.pixel(130, 18), empty); + QCOMPARE(result.pixel(4, 227), empty); + + QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qRed(result.pixel(32, 52))); + QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qBlue(result.pixel(32, 52))); + QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qRed(result.pixel(214, 191))); + QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qBlue(result.pixel(214, 191))); +} + +void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + QImage inputImage; + inputImage.load(QLatin1String(":/data/qt256.png")); + QVERIFY(!inputImage.isNull()); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float verticesUvs[] = { + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(verticesUvs))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), verticesUvs); + + // There will be two renderpasses. One renders with no transformation and + // an opacity of 0.5, the second has a rotation. Bake the uniform data for + // both into a single buffer. + + const int UNIFORM_BLOCK_SIZE = 64 + 4; // matrix + opacity + const int secondUbufOffset = rhi->ubufAligned(UNIFORM_BLOCK_SIZE); + const int UBUF_SIZE = secondUbufOffset + UNIFORM_BLOCK_SIZE; + + QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE)); + QVERIFY(ubuf->build()); + + QMatrix4x4 matrix; + updates->updateDynamicBuffer(ubuf.data(), 0, 64, matrix.constData()); + float opacity = 0.5f; + updates->updateDynamicBuffer(ubuf.data(), 64, 4, &opacity); + + // rotation by 45 degrees around the Z axis + matrix.rotate(45, 0, 0, 1); + updates->updateDynamicBuffer(ubuf.data(), secondUbufOffset, 64, matrix.constData()); + updates->updateDynamicBuffer(ubuf.data(), secondUbufOffset + 64, 4, &opacity); + + QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(inputTexture->build()); + updates->uploadTexture(inputTexture.data(), inputImage); + + QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler->build()); + + const QRhiShaderResourceBinding::StageFlags commonVisibility = QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; + QScopedPointer<QRhiShaderResourceBindings> srb0(rhi->newShaderResourceBindings()); + srb0->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data(), 0, UNIFORM_BLOCK_SIZE), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data()) + }); + QVERIFY(srb0->build()); + + QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings()); + srb1->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data(), secondUbufOffset, UNIFORM_BLOCK_SIZE), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data()) + }); + QVERIFY(srb1->build()); + QVERIFY(srb1->isLayoutCompatible(srb0.data())); // hence no need for a second pipeline + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); + QShader vs = loadShader(":/data/textured.vert.qsb"); + QVERIFY(vs.isValid()); + QShaderDescription shaderDesc = vs.description(); + QVERIFY(!shaderDesc.uniformBlocks().isEmpty()); + QCOMPARE(shaderDesc.uniformBlocks().first().size, UNIFORM_BLOCK_SIZE); + + QShader fs = loadShader(":/data/textured.frag.qsb"); + QVERIFY(fs.isValid()); + shaderDesc = fs.description(); + QVERIFY(!shaderDesc.uniformBlocks().isEmpty()); + QCOMPARE(shaderDesc.uniformBlocks().first().size, UNIFORM_BLOCK_SIZE); + + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 4 * sizeof(float) } }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } + }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb0.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->build()); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(pipeline.data()); + cb->setShaderResources(); + cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(4); + + QRhiReadbackResult readResult0; + QImage result0; + readResult0.completed = [&readResult0, &result0] { + result0 = QImage(reinterpret_cast<const uchar *>(readResult0.data.constData()), + readResult0.pixelSize.width(), readResult0.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult0); + cb->endPass(readbackBatch); + + // second pass (rotated) + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }); + cb->setGraphicsPipeline(pipeline.data()); + cb->setShaderResources(srb1.data()); // sources data from a different offset in ubuf + cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(4); + + QRhiReadbackResult readResult1; + QImage result1; + readResult1.completed = [&readResult1, &result1] { + result1 = QImage(reinterpret_cast<const uchar *>(readResult1.data.constData()), + readResult1.pixelSize.width(), readResult1.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); + }; + readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult1); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + + QVERIFY(!result0.isNull()); + QVERIFY(!result1.isNull()); + + if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) { + result0 = std::move(result0).mirrored(); + result1 = std::move(result1).mirrored(); + } + + if (impl == QRhi::Null) + return; + + // opacity 0.5 (premultiplied) + static const auto checkSemiWhite = [](const QRgb &c) { + QRgb semiWhite127 = qPremultiply(qRgba(255, 255, 255, 127)); + QRgb semiWhite128 = qPremultiply(qRgba(255, 255, 255, 128)); + return c == semiWhite127 || c == semiWhite128; + }; + QVERIFY(checkSemiWhite(result0.pixel(79, 77))); + QVERIFY(checkSemiWhite(result0.pixel(124, 81))); + QVERIFY(checkSemiWhite(result0.pixel(128, 149))); + QVERIFY(checkSemiWhite(result0.pixel(120, 189))); + QVERIFY(checkSemiWhite(result0.pixel(116, 185))); + QVERIFY(checkSemiWhite(result0.pixel(191, 172))); + + QRgb empty = qRgba(0, 0, 0, 0); + QCOMPARE(result0.pixel(11, 45), empty); + QCOMPARE(result0.pixel(246, 202), empty); + QCOMPARE(result0.pixel(130, 18), empty); + QCOMPARE(result0.pixel(4, 227), empty); + + // also rotated 45 degrees around Z + QRgb black = qRgba(0, 0, 0, 255); + QCOMPARE(result1.pixel(20, 23), black); + QCOMPARE(result1.pixel(47, 5), black); + QCOMPARE(result1.pixel(238, 22), black); + QCOMPARE(result1.pixel(250, 203), black); + QCOMPARE(result1.pixel(224, 237), black); + QCOMPARE(result1.pixel(12, 221), black); + + QVERIFY(checkSemiWhite(result1.pixel(142, 67))); + QVERIFY(checkSemiWhite(result1.pixel(81, 79))); + QVERIFY(checkSemiWhite(result1.pixel(79, 168))); + QVERIFY(checkSemiWhite(result1.pixel(146, 204))); + QVERIFY(checkSemiWhite(result1.pixel(186, 156))); + + QCOMPARE(result1.pixel(204, 45), empty); + QCOMPARE(result1.pixel(28, 178), empty); +} + +void tst_QRhi::renderToWindowSimple_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToWindowSimple() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + +#ifdef Q_OS_WINRT + if (impl == QRhi::D3D11) + QSKIP("Skipping window-based QRhi rendering on WinRT as the platform and the D3D11 backend are not prepared for this yet"); +#endif + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + QScopedPointer<QWindow> window(new QWindow); + switch (impl) { + case QRhi::OpenGLES2: + Q_FALLTHROUGH(); + case QRhi::D3D11: + window->setSurfaceType(QSurface::OpenGLSurface); + break; + case QRhi::Metal: + window->setSurfaceType(QSurface::MetalSurface); + break; + case QRhi::Vulkan: + window->setSurfaceType(QSurface::VulkanSurface); +#if QT_CONFIG(vulkan) + window->setVulkanInstance(&vulkanInstance); +#endif + break; + default: + break; + } + + window->setGeometry(0, 0, 640, 480); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QScopedPointer<QRhiSwapChain> swapChain(rhi->newSwapChain()); + swapChain->setWindow(window.data()); + swapChain->setFlags(QRhiSwapChain::UsedAsTransferSource); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(swapChain->newCompatibleRenderPassDescriptor()); + swapChain->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(swapChain->buildOrResize()); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float vertices[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + 0.0f, 1.0f + }; + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), vertices); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->build()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + QShader vs = loadShader(":/data/simple.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simple.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->build()); + + const int framesInFlight = rhi->resourceLimit(QRhi::FramesInFlight); + QVERIFY(framesInFlight >= 1); + const int FRAME_COUNT = framesInFlight + 1; + bool readCompleted = false; + QRhiReadbackResult readResult; + QImage result; + int readbackWidth = 0; + + for (int frameNo = 0; frameNo < FRAME_COUNT; ++frameNo) { + QVERIFY(rhi->beginFrame(swapChain.data()) == QRhi::FrameOpSuccess); + QRhiCommandBuffer *cb = swapChain->currentFrameCommandBuffer(); + QRhiRenderTarget *rt = swapChain->currentFrameRenderTarget(); + const QSize outputSize = swapChain->currentPixelSize(); + QCOMPARE(rt->pixelSize(), outputSize); + QRhiViewport viewport(0, 0, float(outputSize.width()), float(outputSize.height())); + + cb->beginPass(rt, Qt::blue, { 1.0f, 0 }, updates); + updates = nullptr; + cb->setGraphicsPipeline(pipeline.data()); + cb->setViewport(viewport); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(3); + + if (frameNo == 0) { + readResult.completed = [&readCompleted, &readResult, &result, &rhi] { + readCompleted = true; + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_ARGB32_Premultiplied); + if (readResult.format == QRhiTexture::RGBA8) + wrapperImage = wrapperImage.rgbSwapped(); + if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC()) + result = wrapperImage.mirrored(); + else + result = wrapperImage.copy(); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({}, &readResult); // read back the current backbuffer + readbackWidth = outputSize.width(); + cb->endPass(readbackBatch); + } else { + cb->endPass(); + } + + rhi->endFrame(swapChain.data()); + } + + // The readback is asynchronous here. However it is guaranteed that it + // finished at latest after rendering QRhi::FramesInFlight frames after the + // one that enqueues the readback. + QVERIFY(readCompleted); + QVERIFY(readbackWidth > 0); + + if (impl == QRhi::Null) + return; + + // Now we have a red rectangle on blue background. + const int y = 50; + const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y)); + int x = result.width() - 1; + int redCount = 0; + int blueCount = 0; + const int maxFuzz = 1; + while (x-- >= 0) { + const QRgb c(*p++); + if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0) + ++redCount; + else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz)) + ++blueCount; + else + QFAIL("Encountered a pixel that is neither red or blue"); + } + + QCOMPARE(redCount + blueCount, readbackWidth); + QVERIFY(redCount < blueCount); +} + #include <tst_qrhi.moc> QTEST_MAIN(tst_QRhi) diff --git a/tests/auto/gui/text/qfont/tst_qfont.cpp b/tests/auto/gui/text/qfont/tst_qfont.cpp index 96f3b1c1d7..d722856366 100644 --- a/tests/auto/gui/text/qfont/tst_qfont.cpp +++ b/tests/auto/gui/text/qfont/tst_qfont.cpp @@ -66,6 +66,8 @@ private slots: void defaultFamily(); void toAndFromString(); void fromStringWithoutStyleName(); + void fromDegenerateString_data(); + void fromDegenerateString(); void sharing(); void familyNameWithCommaQuote_data(); @@ -539,10 +541,10 @@ void tst_QFont::defaultFamily_data() QTest::addColumn<QStringList>("acceptableFamilies"); QTest::newRow("serif") << QFont::Serif << (QStringList() << "Times New Roman" << "Times" << "Droid Serif" << getPlatformGenericFont("serif").split(",")); - QTest::newRow("monospace") << QFont::Monospace << (QStringList() << "Courier New" << "Monaco" << "Droid Sans Mono" << getPlatformGenericFont("monospace").split(",")); + QTest::newRow("monospace") << QFont::Monospace << (QStringList() << "Courier New" << "Monaco" << "Menlo" << "Droid Sans Mono" << getPlatformGenericFont("monospace").split(",")); QTest::newRow("cursive") << QFont::Cursive << (QStringList() << "Comic Sans MS" << "Apple Chancery" << "Roboto" << "Droid Sans" << getPlatformGenericFont("cursive").split(",")); QTest::newRow("fantasy") << QFont::Fantasy << (QStringList() << "Impact" << "Zapfino" << "Roboto" << "Droid Sans" << getPlatformGenericFont("fantasy").split(",")); - QTest::newRow("sans-serif") << QFont::SansSerif << (QStringList() << "Arial" << "Lucida Grande" << "Roboto" << "Droid Sans" << "Segoe UI" << getPlatformGenericFont("sans-serif").split(",")); + QTest::newRow("sans-serif") << QFont::SansSerif << (QStringList() << "Arial" << "Lucida Grande" << "Helvetica" << "Roboto" << "Droid Sans" << "Segoe UI" << getPlatformGenericFont("sans-serif").split(",")); } void tst_QFont::defaultFamily() @@ -604,6 +606,25 @@ void tst_QFont::fromStringWithoutStyleName() QCOMPARE(font2.toString(), str); } +void tst_QFont::fromDegenerateString_data() +{ + QTest::addColumn<QString>("string"); + + QTest::newRow("empty") << QString(); + QTest::newRow("justAComma") << ","; + QTest::newRow("commasAndSpaces") << " , , "; + QTest::newRow("spaces") << " "; + QTest::newRow("spacesTabsAndNewlines") << " \t \n"; +} + +void tst_QFont::fromDegenerateString() +{ + QFETCH(QString, string); + QFont f; + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Invalid description.*")); + QCOMPARE(f.fromString(string), false); + QCOMPARE(f, QFont()); +} void tst_QFont::sharing() { diff --git a/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp b/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp index aee2f970fe..a474acd790 100644 --- a/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp +++ b/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp @@ -139,6 +139,7 @@ private slots: void superscriptCrash_qtbug53911(); void showLineAndParagraphSeparatorsCrash(); void koreanWordWrap(); + void tooManyDirectionalCharctersCrash_qtbug77819(); private: QFont testFont; @@ -2330,5 +2331,21 @@ void tst_QTextLayout::koreanWordWrap() QCOMPARE(layout.lineAt(1).textLength(), 4); } +void tst_QTextLayout::tooManyDirectionalCharctersCrash_qtbug77819() +{ + QString data; + data += QString::fromUtf8("\xe2\x81\xa8"); // U+2068 FSI character + data += QString::fromUtf8("\xe2\x81\xa7"); // U+2067 RLI character + + // duplicating the text + for (int i = 0; i < 10; i++) + data += data; + + // Nothing to test. It must not crash in beginLayout(). + QTextLayout tl(data); + tl.beginLayout(); + tl.endLayout(); +} + QTEST_MAIN(tst_QTextLayout) #include "tst_qtextlayout.moc" diff --git a/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp b/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp index 8d38cbb18a..1e6c354f17 100644 --- a/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp +++ b/tests/auto/gui/text/qtextmarkdownwriter/tst_qtextmarkdownwriter.cpp @@ -159,7 +159,7 @@ void tst_QTextMarkdownWriter::testWriteNestedBulletLists() QTextCursor cursor(document); QTextBlockFormat blockFmt = cursor.blockFormat(); if (checkbox) { - blockFmt.setMarker(checked ? QTextBlockFormat::Checked : QTextBlockFormat::Unchecked); + blockFmt.setMarker(checked ? QTextBlockFormat::MarkerType::Checked : QTextBlockFormat::MarkerType::Unchecked); cursor.setBlockFormat(blockFmt); } |