aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/quick/scenegraph/tst_scenegraph.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/quick/scenegraph/tst_scenegraph.cpp')
-rw-r--r--tests/auto/quick/scenegraph/tst_scenegraph.cpp387
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;