diff options
Diffstat (limited to 'tests/auto/quick/qquickrhiitem')
-rw-r--r-- | tests/auto/quick/qquickrhiitem/BLACKLIST | 3 | ||||
-rw-r--r-- | tests/auto/quick/qquickrhiitem/CMakeLists.txt | 52 | ||||
-rw-r--r-- | tests/auto/quick/qquickrhiitem/data/test.qml | 41 | ||||
-rw-r--r-- | tests/auto/quick/qquickrhiitem/testrhiitem.cpp | 157 | ||||
-rw-r--r-- | tests/auto/quick/qquickrhiitem/testrhiitem.h | 61 | ||||
-rw-r--r-- | tests/auto/quick/qquickrhiitem/texture.frag | 18 | ||||
-rw-r--r-- | tests/auto/quick/qquickrhiitem/texture.vert | 19 | ||||
-rw-r--r-- | tests/auto/quick/qquickrhiitem/tst_qquickrhiitem.cpp | 150 |
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) |