aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/quick/qquickrhiitem
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/quick/qquickrhiitem')
-rw-r--r--tests/auto/quick/qquickrhiitem/BLACKLIST3
-rw-r--r--tests/auto/quick/qquickrhiitem/CMakeLists.txt52
-rw-r--r--tests/auto/quick/qquickrhiitem/data/test.qml41
-rw-r--r--tests/auto/quick/qquickrhiitem/testrhiitem.cpp157
-rw-r--r--tests/auto/quick/qquickrhiitem/testrhiitem.h61
-rw-r--r--tests/auto/quick/qquickrhiitem/texture.frag18
-rw-r--r--tests/auto/quick/qquickrhiitem/texture.vert19
-rw-r--r--tests/auto/quick/qquickrhiitem/tst_qquickrhiitem.cpp150
8 files changed, 501 insertions, 0 deletions
diff --git a/tests/auto/quick/qquickrhiitem/BLACKLIST b/tests/auto/quick/qquickrhiitem/BLACKLIST
new file mode 100644
index 0000000000..03e5bc8c1d
--- /dev/null
+++ b/tests/auto/quick/qquickrhiitem/BLACKLIST
@@ -0,0 +1,3 @@
+# QTBUG-102721
+[rhiItem]
+android
diff --git a/tests/auto/quick/qquickrhiitem/CMakeLists.txt b/tests/auto/quick/qquickrhiitem/CMakeLists.txt
new file mode 100644
index 0000000000..ef9858ea6d
--- /dev/null
+++ b/tests/auto/quick/qquickrhiitem/CMakeLists.txt
@@ -0,0 +1,52 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qquickrhiitem LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ data/*)
+list(APPEND test_data ${test_data_glob})
+
+qt_internal_add_test(tst_qquickrhiitem
+ SOURCES
+ tst_qquickrhiitem.cpp
+ testrhiitem.cpp testrhiitem.h
+ LIBRARIES
+ Qt::CorePrivate
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::QmlPrivate
+ Qt::QuickPrivate
+ Qt::QuickTestUtilsPrivate
+ TESTDATA ${test_data}
+)
+
+qt_internal_extend_target(tst_qquickrhiitem CONDITION ANDROID OR IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=":/data"
+)
+
+qt_internal_extend_target(tst_qquickrhiitem CONDITION NOT ANDROID AND NOT IOS
+ DEFINES
+ QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data"
+)
+
+qt_internal_add_shaders(tst_qquickrhiitem "shaders"
+ PREFIX
+ "/"
+ FILES
+ "texture.vert"
+ "texture.frag"
+)
+
+qt_policy(SET QTP0001 NEW)
+
+qt_add_qml_module(tst_qquickrhiitem
+ URI Testqquickrhiitem
+)
diff --git a/tests/auto/quick/qquickrhiitem/data/test.qml b/tests/auto/quick/qquickrhiitem/data/test.qml
new file mode 100644
index 0000000000..92fbfdb016
--- /dev/null
+++ b/tests/auto/quick/qquickrhiitem/data/test.qml
@@ -0,0 +1,41 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import Testqquickrhiitem
+
+Item {
+ width: 640
+ height: 480
+
+ Text {
+ id: apiInfo
+ color: "black"
+ font.pixelSize: 16
+ property int api: GraphicsInfo.api
+ text: {
+ if (GraphicsInfo.api === GraphicsInfo.OpenGL)
+ "OpenGL on QRhi";
+ else if (GraphicsInfo.api === GraphicsInfo.Direct3D11)
+ "D3D11 on QRhi";
+ else if (GraphicsInfo.api === GraphicsInfo.Direct3D12)
+ "D3D12 on QRhi";
+ else if (GraphicsInfo.api === GraphicsInfo.Vulkan)
+ "Vulkan on QRhi";
+ else if (GraphicsInfo.api === GraphicsInfo.Metal)
+ "Metal on QRhi";
+ else if (GraphicsInfo.api === GraphicsInfo.Null)
+ "Null on QRhi";
+ else
+ "Unknown API";
+ }
+ }
+
+ TestRhiItem {
+ anchors.centerIn: parent
+ width: 400
+ height: 400
+ color: "red"
+ objectName: "rhiitem"
+ }
+}
diff --git a/tests/auto/quick/qquickrhiitem/testrhiitem.cpp b/tests/auto/quick/qquickrhiitem/testrhiitem.cpp
new file mode 100644
index 0000000000..65fc329630
--- /dev/null
+++ b/tests/auto/quick/qquickrhiitem/testrhiitem.cpp
@@ -0,0 +1,157 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "testrhiitem.h"
+#include <QFile>
+
+static const QSize TEX_SIZE(512, 512);
+
+void TestRenderer::initialize(QRhiCommandBuffer *)
+{
+ if (m_rhi != rhi()) {
+ m_rhi = rhi();
+ scene = {};
+ }
+
+ if (m_sampleCount != renderTarget()->sampleCount()) {
+ m_sampleCount = renderTarget()->sampleCount();
+ scene = {};
+ }
+
+ if (!scene.vbuf)
+ initScene();
+
+ const QSize outputSize = renderTarget()->pixelSize();
+ scene.mvp = m_rhi->clipSpaceCorrMatrix();
+ scene.mvp.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f);
+ scene.mvp.translate(0, 0, -4);
+ if (!scene.resourceUpdates)
+ scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
+ scene.resourceUpdates->updateDynamicBuffer(scene.ubuf.get(), 0, 64, scene.mvp.constData());
+}
+
+static QShader getShader(const QString &name)
+{
+ QFile f(name);
+ if (f.open(QIODevice::ReadOnly))
+ return QShader::fromSerialized(f.readAll());
+
+ return QShader();
+}
+
+void TestRenderer::updateTexture()
+{
+ QImage img(TEX_SIZE, QImage::Format_RGBA8888);
+ img.fill(itemData.color);
+
+ if (!scene.resourceUpdates)
+ scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
+
+ scene.resourceUpdates->uploadTexture(scene.tex.get(), img);
+}
+
+void TestRenderer::initScene()
+{
+ static const float tri[] = {
+ 0.0f, 0.5f, 0.0f, 1.0f,
+ -0.5f, -0.5f, 0.0f, 0.0f,
+ 0.5f, -0.5f, 1.0f, 0.0f
+ };
+
+ scene.vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(tri)));
+ scene.vbuf->create();
+
+ scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
+ scene.resourceUpdates->uploadStaticBuffer(scene.vbuf.get(), tri);
+
+ scene.ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68));
+ scene.ubuf->create();
+
+ const qint32 flip = m_rhi->isYUpInFramebuffer() ? 1 : 0;
+ scene.resourceUpdates->updateDynamicBuffer(scene.ubuf.get(), 64, 4, &flip);
+
+ scene.tex.reset(m_rhi->newTexture(QRhiTexture::RGBA8, TEX_SIZE));
+ scene.tex->create();
+
+ scene.sampler.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
+ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
+ scene.sampler->create();
+
+ scene.srb.reset(m_rhi->newShaderResourceBindings());
+ scene.srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, scene.ubuf.get()),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, scene.tex.get(), scene.sampler.get())
+ });
+ scene.srb->create();
+
+ scene.ps.reset(m_rhi->newGraphicsPipeline());
+ scene.ps->setDepthTest(true);
+ scene.ps->setDepthWrite(true);
+ scene.ps->setDepthOp(QRhiGraphicsPipeline::Less);
+ scene.ps->setCullMode(QRhiGraphicsPipeline::Back);
+ scene.ps->setFrontFace(QRhiGraphicsPipeline::CCW);
+ QShader vs = getShader(QLatin1String(":/texture.vert.qsb"));
+ Q_ASSERT(vs.isValid());
+ QShader fs = getShader(QLatin1String(":/texture.frag.qsb"));
+ Q_ASSERT(fs.isValid());
+ scene.ps->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) }
+ });
+ scene.ps->setVertexInputLayout(inputLayout);
+ scene.ps->setSampleCount(m_sampleCount);
+ scene.ps->setShaderResourceBindings(scene.srb.get());
+ scene.ps->setRenderPassDescriptor(renderTarget()->renderPassDescriptor());
+ scene.ps->create();
+
+ updateTexture();
+}
+
+void TestRenderer::synchronize(QQuickRhiItem *rhiItem)
+{
+ TestRhiItem *item = static_cast<TestRhiItem *>(rhiItem);
+ if (item->color() != itemData.color) {
+ itemData.color = item->color();
+ updateTexture();
+ }
+}
+
+void TestRenderer::render(QRhiCommandBuffer *cb)
+{
+ QRhiResourceUpdateBatch *rub = scene.resourceUpdates;
+ if (rub)
+ scene.resourceUpdates = nullptr;
+
+ const QColor clearColor = QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f);
+ cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, rub);
+
+ cb->setGraphicsPipeline(scene.ps.get());
+ const QSize outputSize = renderTarget()->pixelSize();
+ cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height()));
+ cb->setShaderResources();
+ const QRhiCommandBuffer::VertexInput vbufBindings[] = {
+ { scene.vbuf.get(), 0 },
+ };
+ cb->setVertexInput(0, 1, vbufBindings);
+ cb->draw(3);
+
+ cb->endPass();
+}
+
+void TestRhiItem::setColor(QColor c)
+{
+ if (m_color == c)
+ return;
+
+ m_color = c;
+ emit colorChanged();
+ update();
+}
diff --git a/tests/auto/quick/qquickrhiitem/testrhiitem.h b/tests/auto/quick/qquickrhiitem/testrhiitem.h
new file mode 100644
index 0000000000..6778f96d7b
--- /dev/null
+++ b/tests/auto/quick/qquickrhiitem/testrhiitem.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef TESTRHIITEM_H
+#define TESTRHIITEM_H
+
+#include <QQuickRhiItem>
+#include <rhi/qrhi.h>
+
+class TestRenderer : public QQuickRhiItemRenderer
+{
+protected:
+ void initialize(QRhiCommandBuffer *cb) override;
+ void synchronize(QQuickRhiItem *item) override;
+ void render(QRhiCommandBuffer *cb) override;
+
+private:
+ QRhi *m_rhi = nullptr;
+ int m_sampleCount = 1;
+
+ struct {
+ QRhiResourceUpdateBatch *resourceUpdates = nullptr;
+ std::unique_ptr<QRhiBuffer> vbuf;
+ std::unique_ptr<QRhiBuffer> ubuf;
+ std::unique_ptr<QRhiShaderResourceBindings> srb;
+ std::unique_ptr<QRhiGraphicsPipeline> ps;
+ std::unique_ptr<QRhiSampler> sampler;
+ std::unique_ptr<QRhiTexture> tex;
+ QMatrix4x4 mvp;
+ } scene;
+
+ struct {
+ QColor color;
+ } itemData;
+
+ void initScene();
+ void updateTexture();
+
+ friend class tst_QQuickRhiItem;
+};
+
+class TestRhiItem : public QQuickRhiItem
+{
+ Q_OBJECT
+ QML_NAMED_ELEMENT(TestRhiItem)
+ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
+
+public:
+ QQuickRhiItemRenderer *createRenderer() override { return new TestRenderer; }
+
+ QColor color() const { return m_color; }
+ void setColor(QColor c);
+
+signals:
+ void colorChanged();
+
+private:
+ QColor m_color;
+};
+
+#endif
diff --git a/tests/auto/quick/qquickrhiitem/texture.frag b/tests/auto/quick/qquickrhiitem/texture.frag
new file mode 100644
index 0000000000..a7b8bce0ad
--- /dev/null
+++ b/tests/auto/quick/qquickrhiitem/texture.frag
@@ -0,0 +1,18 @@
+#version 440
+
+layout(location = 0) in vec2 v_texcoord;
+
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+ int flip;
+};
+
+layout(binding = 1) uniform sampler2D tex;
+
+void main()
+{
+ vec4 c = texture(tex, v_texcoord);
+ fragColor = vec4(c.rgb * c.a, c.a);
+}
diff --git a/tests/auto/quick/qquickrhiitem/texture.vert b/tests/auto/quick/qquickrhiitem/texture.vert
new file mode 100644
index 0000000000..bf678d1e9b
--- /dev/null
+++ b/tests/auto/quick/qquickrhiitem/texture.vert
@@ -0,0 +1,19 @@
+#version 440
+
+layout(location = 0) in vec4 position;
+layout(location = 1) in vec2 texcoord;
+
+layout(location = 0) out vec2 v_texcoord;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+ int flip;
+};
+
+void main()
+{
+ v_texcoord = texcoord;
+ if (flip != 0)
+ v_texcoord.y = 1.0 - v_texcoord.y;
+ gl_Position = mvp * position;
+}
diff --git a/tests/auto/quick/qquickrhiitem/tst_qquickrhiitem.cpp b/tests/auto/quick/qquickrhiitem/tst_qquickrhiitem.cpp
new file mode 100644
index 0000000000..e6c5b079ca
--- /dev/null
+++ b/tests/auto/quick/qquickrhiitem/tst_qquickrhiitem.cpp
@@ -0,0 +1,150 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <qtest.h>
+#include <QtQuickTestUtils/private/qmlutils_p.h>
+#include <QQuickView>
+#include <QSGRendererInterface>
+#include <private/qsgrhisupport_p.h>
+#include <private/qquickrhiitem_p.h>
+#include "testrhiitem.h"
+
+class tst_QQuickRhiItem : public QQmlDataTest
+{
+ Q_OBJECT
+
+public:
+ tst_QQuickRhiItem();
+
+private slots:
+ void initTestCase() override;
+ void cleanupTestCase();
+ void rhiItem();
+ void properties();
+};
+
+tst_QQuickRhiItem::tst_QQuickRhiItem()
+ : QQmlDataTest(QT_QMLTEST_DATADIR)
+{
+}
+
+void tst_QQuickRhiItem::initTestCase()
+{
+ QQmlDataTest::initTestCase();
+ qDebug() << "RHI backend:" << QSGRhiSupport::instance()->rhiBackendName();
+}
+
+void tst_QQuickRhiItem::cleanupTestCase()
+{
+}
+
+void tst_QQuickRhiItem::rhiItem()
+{
+ if (QGuiApplication::platformName() == QLatin1String("offscreen")
+ || QGuiApplication::platformName() == QLatin1String("minimal"))
+ {
+ QSKIP("Skipping on offscreen/minimal");
+ }
+
+ // Load up a scene that instantiates a TestRhiItem, which in turn
+ // renders a triangle with QRhi into a QRhiTexture and then draws
+ // a quad textured with it.
+
+ QQuickView view;
+ view.setSource(testFileUrl(QLatin1String("test.qml")));
+ view.setResizeMode(QQuickView::SizeViewToRootObject);
+ view.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
+
+ if (!QSGRendererInterface::isApiRhiBased(view.rendererInterface()->graphicsApi()))
+ QSKIP("Scenegraph does not use QRhi, test is pointless");
+
+ QImage result = view.grabWindow();
+ QVERIFY(!result.isNull());
+
+ const int tolerance = 5;
+
+ // The result is a red triangle in the center of the view, confirm at least one pixel.
+ QRgb a = result.pixel(result.width() / 2, result.height() / 2);
+ QRgb b = QColor(Qt::red).rgb();
+ QVERIFY(qAbs(qRed(a) - qRed(b)) <= tolerance
+ && qAbs(qGreen(a) - qGreen(b)) <= tolerance
+ && qAbs(qBlue(a) - qBlue(b)) <= tolerance);
+}
+
+void tst_QQuickRhiItem::properties()
+{
+ if (QGuiApplication::platformName() == QLatin1String("offscreen")
+ || QGuiApplication::platformName() == QLatin1String("minimal"))
+ {
+ QSKIP("Skipping on offscreen/minimal");
+ }
+
+ QQuickView view;
+ view.setSource(testFileUrl(QLatin1String("test.qml")));
+ view.setResizeMode(QQuickView::SizeViewToRootObject);
+ view.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
+
+ if (!QSGRendererInterface::isApiRhiBased(view.rendererInterface()->graphicsApi()))
+ QSKIP("Scenegraph does not use QRhi, test is pointless");
+
+ QQuickRhiItem *item = view.rootObject()->findChild<QQuickRhiItem *>("rhiitem");
+ QVERIFY(item);
+
+ view.grabWindow(); // to ensure there's a frame
+ // not quite safe in theory (threads etc.) but we know it works in practice
+ QQuickRhiItemPrivate *d = QQuickRhiItemPrivate::get(item);
+ QVERIFY(d->node);
+ TestRenderer *r = static_cast<TestRenderer *>(d->node->m_renderer.get());
+ QVERIFY(r);
+ QRhi *rhi = r->rhi();
+ QVERIFY(rhi);
+
+ QVERIFY(r->colorTexture());
+ QVERIFY(!r->msaaColorBuffer());
+ QVERIFY(r->depthStencilBuffer());
+ QVERIFY(r->renderTarget());
+ QCOMPARE(item->effectiveColorBufferSize(), r->colorTexture()->pixelSize());
+
+ QCOMPARE(item->sampleCount(), 1);
+ item->setSampleCount(4);
+
+ view.grabWindow(); // just to ensure the render thread rendered with the changed properties
+
+ if (rhi->supportedSampleCounts().contains(4)) {
+ QVERIFY(!r->colorTexture());
+ QVERIFY(r->msaaColorBuffer());
+ QCOMPARE(r->msaaColorBuffer()->sampleCount(), 4);
+ QCOMPARE(r->depthStencilBuffer()->sampleCount(), 4);
+ QCOMPARE(item->effectiveColorBufferSize(), r->msaaColorBuffer()->pixelSize());
+ }
+
+ QCOMPARE(item->alphaBlending(), false);
+ item->setAlphaBlending(true);
+
+ QCOMPARE(item->isMirrorVerticallyEnabled(), false);
+ item->setMirrorVertically(true);
+
+ item->setSampleCount(1);
+ item->setFixedColorBufferWidth(123);
+ item->setFixedColorBufferHeight(456);
+ view.grabWindow();
+ QCOMPARE(r->colorTexture()->pixelSize(), QSize(123, 456));
+ QCOMPARE(item->fixedColorBufferWidth(), 123);
+ QCOMPARE(item->fixedColorBufferHeight(), 456);
+ QCOMPARE(item->effectiveColorBufferSize(), QSize(123, 456));
+
+ QImage result = view.grabWindow();
+ QVERIFY(!result.isNull());
+ const int tolerance = 5;
+ QRgb a = result.pixel(result.width() / 2, result.height() / 2);
+ QRgb b = QColor(Qt::red).rgb();
+ QVERIFY(qAbs(qRed(a) - qRed(b)) <= tolerance
+ && qAbs(qGreen(a) - qGreen(b)) <= tolerance
+ && qAbs(qBlue(a) - qBlue(b)) <= tolerance);
+}
+
+#include "tst_qquickrhiitem.moc"
+
+QTEST_MAIN(tst_QQuickRhiItem)