diff options
Diffstat (limited to 'tests/auto/quick/scenegraph/tst_scenegraph.cpp')
-rw-r--r-- | tests/auto/quick/scenegraph/tst_scenegraph.cpp | 387 |
1 files changed, 347 insertions, 40 deletions
diff --git a/tests/auto/quick/scenegraph/tst_scenegraph.cpp b/tests/auto/quick/scenegraph/tst_scenegraph.cpp index 0cb3ff428a..1d76fe3181 100644 --- a/tests/auto/quick/scenegraph/tst_scenegraph.cpp +++ b/tests/auto/quick/scenegraph/tst_scenegraph.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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 General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** 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-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qtest.h> @@ -44,14 +19,15 @@ #include <private/qsgcontext_p.h> #include <private/qsgrenderloop_p.h> #include <private/qsgrhisupport_p.h> +#include <private/qsgplaintexture_p.h> -#include "../../shared/util.h" -#include "../shared/visualtestutil.h" +#include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtGui/private/qguiapplication_p.h> #include <QtGui/qpa/qplatformintegration.h> -using namespace QQuickVisualTestUtil; +using namespace QQuickVisualTestUtils; class PerPixelRect : public QQuickItem { @@ -71,7 +47,7 @@ public: QColor color() const { return m_color; } - QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *) + QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *) override { delete node; node = new QSGNode; @@ -102,6 +78,9 @@ class tst_SceneGraph : public QQmlDataTest { Q_OBJECT +public: + tst_SceneGraph(); + private slots: void initTestCase() override; @@ -115,6 +94,9 @@ private slots: #endif void createTextureFromImage_data(); void createTextureFromImage(); + void withAdoptedRhi(); + void resizeTextureFromImage(); + void textureNativeInterface(); private: QQuickView *createView(const QString &file, QWindow *parent = nullptr, int x = -1, int y = -1, int w = -1, int h = -1); @@ -126,6 +108,11 @@ public: ~ScopedList() { qDeleteAll(*this); } }; +tst_SceneGraph::tst_SceneGraph() + : QQmlDataTest(QT_QMLTEST_DATADIR) +{ +} + void tst_SceneGraph::initTestCase() { qmlRegisterType<PerPixelRect>("SceneGraphTest", 1, 0, "PerPixelRect"); @@ -271,7 +258,7 @@ void tst_SceneGraph::manyWindows() } struct Sample { - Sample(int xx, int yy, qreal rr, qreal gg, qreal bb, qreal errorMargin = 0.05) + constexpr Sample(int xx, int yy, qreal rr, qreal gg, qreal bb, qreal errorMargin = 0.05) : x(xx) , y(yy) , r(rr) @@ -280,8 +267,18 @@ struct Sample { , tolerance(errorMargin) { } - Sample(const Sample &o) : x(o.x), y(o.y), r(o.r), g(o.g), b(o.b), tolerance(o.tolerance) { } - Sample() : x(0), y(0), r(0), g(0), b(0), tolerance(0) { } + constexpr Sample(const Sample &o) : x(o.x), y(o.y), r(o.r), g(o.g), b(o.b), tolerance(o.tolerance) { } + constexpr Sample() : x(0), y(0), r(0), g(0), b(0), tolerance(0) { } + constexpr Sample &operator=(const Sample &o) + { + x = o.x; + y = o.y; + r = o.r; + g = o.g; + b = o.b; + tolerance = o.tolerance; + return *this; + } QString toString(const QImage &image) const { QColor color(image.pixel(x,y)); @@ -293,7 +290,9 @@ struct Sample { } bool check(const QImage &image, qreal scale) { - QColor color(image.pixel(x * scale, y * scale)); + const int scaledX = qRound(x * scale); + const int scaledY = qRound(y * scale); + const QColor color(image.pixel(scaledX, scaledY)); return qAbs(color.redF() - r) <= tolerance && qAbs(color.greenF() - g) <= tolerance && qAbs(color.blueF() - b) <= tolerance; @@ -366,23 +365,24 @@ void tst_SceneGraph::render_data() << "render_ImageFiltering.qml" << "render_bug37422.qml" << "render_OpacityThroughBatchRoot.qml" - << "render_Mipmap.qml"; + << "render_Mipmap.qml" + << "render_AlphaOverlapRebuild.qml"; QRegularExpression sampleCount("#samples: *(\\d+)"); // X:int Y:int R:float G:float B:float Error:float QRegularExpression baseSamples("#base: *(\\d+) *(\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+)"); QRegularExpression finalSamples("#final: *(\\d+) *(\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+) *(\\d\\.\\d+)"); - foreach (QString fileName, files) { + for (QString fileName : std::as_const(files)) { QFile file(testFile(fileName)); if (!file.open(QFile::ReadOnly)) { qFatal("render_data: QFile::open failed! file=%s, error=%s", qPrintable(fileName), qPrintable(file.errorString())); } - QStringList contents = QString::fromLatin1(file.readAll()).split(QLatin1Char('\n')); + const QStringList contents = QString::fromLatin1(file.readAll()).split(QLatin1Char('\n')); int samples = -1; - foreach (QString line, contents) { + for (const QString &line : contents) { auto match = sampleCount.match(line); if (match.hasMatch()) { samples = match.captured(1).toInt(); @@ -393,7 +393,7 @@ void tst_SceneGraph::render_data() qFatal("render_data: failed to find string '#samples: [count], file=%s", qPrintable(fileName)); QList<Sample> baseStage, finalStage; - foreach (QString line, contents) { + for (const QString &line : contents) { auto match = baseSamples.match(line); if (match.hasMatch()) baseStage << sample_from_regexp(&match); @@ -433,6 +433,10 @@ void tst_SceneGraph::render() // ideal world. // Just keep this in mind when writing tests. qreal scale = view.devicePixelRatio(); + const bool isIntegerScale = qFuzzyIsNull(qreal(qFloor(scale)) - scale); + + if (file == "render_OutOfFloatRange.qml" && !isIntegerScale) + QSKIP("render_OutOfFloatRange doesn't work with non-integer scaling factors"); // Grab the window and check all our base stage samples QImage content = view.grabWindow(); @@ -530,6 +534,309 @@ void tst_SceneGraph::createTextureFromImage() QCOMPARE(texture->hasAlphaChannel(), expectedAlpha); } +#if QT_CONFIG(vulkan) +static QVulkanInstance *TestOffscreenScene_vkinst = nullptr; +#endif + +struct TestOffscreenScene +{ + QQuickRenderControl *renderControl = nullptr; + QQuickWindow *window = nullptr; + QQmlEngine *engine = nullptr; + QQmlComponent *component = nullptr; + QQuickItem *rootItem = nullptr; + + // to be called at the end of each test case once all TestOffscreenScene instances are destroyed + static void cleanup() + { +#if QT_CONFIG(vulkan) + delete TestOffscreenScene_vkinst; + TestOffscreenScene_vkinst = nullptr; +#endif + } + + ~TestOffscreenScene() + { + delete component; + delete engine; + delete window; + delete renderControl; + } +}; + +static TestOffscreenScene *createOffscreenScene(const QUrl &url, QQuickWindow *compatibleWindow = nullptr) +{ + std::unique_ptr<TestOffscreenScene> scene(new TestOffscreenScene); + scene->renderControl = new QQuickRenderControl; + scene->window = new QQuickWindow(scene->renderControl); + + if (compatibleWindow) { + scene->window->setGraphicsApi(compatibleWindow->rendererInterface()->graphicsApi()); + +#if QT_CONFIG(vulkan) + if (compatibleWindow->rendererInterface()->graphicsApi() == QSGRendererInterface::VulkanRhi) + scene->window->setVulkanInstance(compatibleWindow->vulkanInstance()); +#endif + + QRhi *rhi = static_cast<QRhi *>(compatibleWindow->rendererInterface()->getResource(compatibleWindow, QSGRendererInterface::RhiResource)); + if (rhi) { + // make it so that the rendercontrol will not create a new QRhi, but rather use what we specify here + scene->window->setGraphicsDevice(QQuickGraphicsDevice::fromRhi(rhi)); + } else { + qWarning("No QRhi from the specified compatibleWindow, this is unexpected"); + } + } else { +#if QT_CONFIG(vulkan) + if (QQuickWindow::graphicsApi() == QSGRendererInterface::Vulkan) { // honor what QSG_RHI_BACKEND says + if (!TestOffscreenScene_vkinst) { + TestOffscreenScene_vkinst = new QVulkanInstance; + TestOffscreenScene_vkinst->setExtensions(QQuickGraphicsConfiguration::preferredInstanceExtensions()); + TestOffscreenScene_vkinst->create(); + } + scene->window->setVulkanInstance(TestOffscreenScene_vkinst); + } +#endif + } + + scene->engine = new QQmlEngine; + scene->component = new QQmlComponent(scene->engine, url); + if (scene->component->isError()) { + for (const QQmlError &error : scene->component->errors()) + qWarning() << error.url() << error.line() << error; + return nullptr; + } + + QObject *rootObject = scene->component->create(); + if (scene->component->isError()) { + for (const QQmlError &error : scene->component->errors()) + qWarning() << error.url() << error.line() << error; + } + + scene->rootItem = qobject_cast<QQuickItem *>(rootObject); + if (!scene->rootItem) { + qWarning("No root QQuickItem"); + return nullptr; + } + + scene->window->contentItem()->setSize(scene->rootItem->size()); + scene->window->setGeometry(0, 0, scene->rootItem->width(), scene->rootItem->height()); + scene->rootItem->setParentItem(scene->window->contentItem()); + + if (!scene->renderControl->initialize()) { + qWarning("Failed to initialize rendercontrol"); + return nullptr; + } + + return scene.release(); +} + +void tst_SceneGraph::withAdoptedRhi() +{ + if (!isRunningOnRhi()) + QSKIP("Skipping test due to not running with QRhi"); + + // Use only QQuickRenderControl-based, single-threaded scenes for this + // test. QQuickView would not be suitable because it it uses the threaded + // render loop, then we end up in threading issues with graphics resources. + + TestOffscreenScene *scene1 = createOffscreenScene(testFileUrl(QLatin1String("renderControl_rect.qml"))); + QVERIFY(scene1->renderControl && scene1->window && scene1->rootItem); + + // Now another one, but this time sharing the same QRhi as the first one. + TestOffscreenScene *scene2 = createOffscreenScene(testFileUrl(QLatin1String("renderControl_rect.qml")), scene1->window); + QVERIFY(scene2->renderControl && scene2->window && scene2->rootItem); + + QRhi *rhi = static_cast<QRhi *>(scene1->window->rendererInterface()->getResource(scene1->window, QSGRendererInterface::RhiResource)); + QCOMPARE(rhi, static_cast<QRhi *>(scene2->window->rendererInterface()->getResource(scene2->window, QSGRendererInterface::RhiResource))); + + { // scope to get resources destroyed before the QRhi + const QSize size = scene1->rootItem->size().toSize(); + QCOMPARE(size, scene2->rootItem->size().toSize()); + QScopedPointer<QRhiRenderBuffer> ds(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size, 1)); + QVERIFY(ds->create()); + + // texture for scene1 + QScopedPointer<QRhiTexture> tex1(rhi->newTexture(QRhiTexture::RGBA8, size, 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(tex1->create()); + QRhiTextureRenderTargetDescription rtDesc1(QRhiColorAttachment(tex1.data())); + rtDesc1.setDepthStencilBuffer(ds.data()); + QScopedPointer<QRhiTextureRenderTarget> texRt1(rhi->newTextureRenderTarget(rtDesc1)); + QScopedPointer<QRhiRenderPassDescriptor> rp1(texRt1->newCompatibleRenderPassDescriptor()); + texRt1->setRenderPassDescriptor(rp1.data()); + QVERIFY(texRt1->create()); + scene1->window->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(texRt1.data())); + + // for scene2 + QScopedPointer<QRhiTexture> tex2(rhi->newTexture(QRhiTexture::RGBA8, size, 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(tex2->create()); + QRhiTextureRenderTargetDescription rtDesc2(QRhiColorAttachment(tex2.data())); + rtDesc2.setDepthStencilBuffer(ds.data()); + QScopedPointer<QRhiTextureRenderTarget> texRt2(rhi->newTextureRenderTarget(rtDesc2)); + QScopedPointer<QRhiRenderPassDescriptor> rp2(texRt2->newCompatibleRenderPassDescriptor()); + texRt2->setRenderPassDescriptor(rp2.data()); + QVERIFY(texRt2->create()); + scene2->window->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(texRt2.data())); + + // render a frame, first with scene1, then with scene2, targeting their respective textures + scene1->renderControl->polishItems(); + scene1->renderControl->beginFrame(); + scene1->renderControl->sync(); + scene1->renderControl->render(); + scene1->renderControl->endFrame(); + + scene2->renderControl->polishItems(); + scene2->renderControl->beginFrame(); + scene2->renderControl->sync(); + scene2->renderControl->render(); + scene2->renderControl->endFrame(); + + // Both tex1 and tex2 belong to the same one QRhi. Read back the + // contents. In a real world application one could now render with the + // QRhi to some other window, using both textures. + for (int stage = 0; stage < 2; ++stage) { + QRhiCommandBuffer *cb = nullptr; + rhi->beginOffscreenFrame(&cb); + bool readCompleted = false; + QRhiReadbackResult readResult; + QImage result; + 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_RGBA8888_Premultiplied); + if (rhi->isYUpInFramebuffer()) + result = wrapperImage.mirrored(); + else + result = wrapperImage.copy(); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture(stage == 0 ? tex1.data() : tex2.data(), &readResult); + cb->resourceUpdate(readbackBatch); + rhi->endOffscreenFrame(); + + QVERIFY(readCompleted); + QCOMPARE(result.size(), QSize(200, 200)); + const int maxFuzz = 2; + // red + QVERIFY(qAbs(qRed(result.pixel(100, 100)) - 255) < maxFuzz); + QVERIFY(qAbs(qGreen(result.pixel(100, 100))) < maxFuzz); + QVERIFY(qAbs(qBlue(result.pixel(100, 100))) < maxFuzz); + } + } + + delete scene2; // this does not destroy the QRhi + // call anything on the QRhi just to test if it is still valid + QVERIFY(!rhi->isDeviceLost()); + delete scene1; // this does + + TestOffscreenScene::cleanup(); +} + +static inline void commitTexture(QRhi *rhi, QSGTexture *texture) +{ + QRhiResourceUpdateBatch *rub = rhi->nextResourceUpdateBatch(); + texture->commitTextureOperations(rhi, rub); + QRhiCommandBuffer *cb = nullptr; + rhi->beginOffscreenFrame(&cb); + cb->resourceUpdate(rub); + rhi->endOffscreenFrame(); +} + +void tst_SceneGraph::resizeTextureFromImage() +{ + if (!isRunningOnRhi()) + QSKIP("Skipping test due to not running with QRhi"); + + // We will need to directly work with QSGTexture and QRhi so have + // to be on the same thread as the scene graph. Hence using the + // offscreen infrastructure from other tests. + + // note the lifetimes: (vulkan instance) > scenegraph(incl. QRhi) > QSGTexture + { + QScopedPointer<TestOffscreenScene> scene(createOffscreenScene(testFileUrl(QLatin1String("renderControl_rect.qml")))); + QVERIFY(scene->renderControl && scene->window && scene->rootItem); + + { + QImage image(256, 128, QImage::Format_RGBA8888); + QScopedPointer<QSGTexture> texture(scene->window->createTextureFromImage(image, QQuickWindow::TextureHasAlphaChannel)); + QRhi *rhi = static_cast<QRhi *>(scene->window->rendererInterface()->getResource(scene->window, QSGRendererInterface::RhiResource)); + QVERIFY(rhi); + commitTexture(rhi, texture.data()); + // neither is too big nor relies on optional features like NPoT repeat so the size should match always + QCOMPARE(texture->textureSize(), image.size()); + QCOMPARE(texture->rhiTexture()->pixelSize(), image.size()); + + QSGPlainTexture *plainTex = qobject_cast<QSGPlainTexture *>(texture.data()); + QVERIFY(plainTex); + + // QTBUG-96190 - the commitTexture call here used to crash due to not + // updating the QRhiTexture size correctly in QSGPlainTexture. + image = QImage(512, 256, QImage::Format_RGBA8888); + plainTex->setImage(image); + commitTexture(rhi, texture.data()); + QCOMPARE(texture->textureSize(), image.size()); + QCOMPARE(texture->rhiTexture()->pixelSize(), image.size()); + } + } + + TestOffscreenScene::cleanup(); +} + +void tst_SceneGraph::textureNativeInterface() +{ + if (!isRunningOnRhi()) + QSKIP("Skipping texture native interface tests due to not running with QRhi"); + + // test it offscreen because we want to do QSG stuff here on the main thread + QScopedPointer<TestOffscreenScene> scene(createOffscreenScene(testFileUrl(QLatin1String("renderControl_rect.qml")))); + QVERIFY(scene->renderControl && scene->window && scene->rootItem); + + QImage image(512, 512, QImage::Format_RGBA8888); + + QScopedPointer<QSGTexture> texture(scene->window->createTextureFromImage(image, QQuickWindow::TextureHasAlphaChannel)); + QVERIFY(texture.data()); + + commitTexture(scene->window->rhi(), texture.data()); + +#if QT_CONFIG(metal) + if (scene->window->graphicsApi() == QSGRendererInterface::Metal) { + auto texNatIf = texture->nativeInterface<QNativeInterface::QSGMetalTexture>(); + QVERIFY(texNatIf); + QVERIFY(texNatIf->nativeTexture()); + } +#endif + +#if defined(Q_OS_WIN) + if (scene->window->graphicsApi() == QSGRendererInterface::Direct3D11) { + auto texNatIf = texture->nativeInterface<QNativeInterface::QSGD3D11Texture>(); + QVERIFY(texNatIf); + QVERIFY(texNatIf->nativeTexture()); + } else if (scene->window->graphicsApi() == QSGRendererInterface::Direct3D12) { + auto texNatIf = texture->nativeInterface<QNativeInterface::QSGD3D12Texture>(); + QVERIFY(texNatIf); + QVERIFY(texNatIf->nativeTexture()); + } +#endif + +#if QT_CONFIG(opengl) + if (scene->window->graphicsApi() == QSGRendererInterface::OpenGL) { + auto texNatIf = texture->nativeInterface<QNativeInterface::QSGOpenGLTexture>(); + QVERIFY(texNatIf); + QVERIFY(texNatIf->nativeTexture()); + } +#endif + +#if QT_CONFIG(vulkan) + if (scene->window->graphicsApi() == QSGRendererInterface::Vulkan) { + auto texNatIf = texture->nativeInterface<QNativeInterface::QSGVulkanTexture>(); + QVERIFY(texNatIf); + QVERIFY(texNatIf->nativeImage()); + } +#endif +} + bool tst_SceneGraph::isRunningOnRhi() { static bool retval = false; |