summaryrefslogtreecommitdiffstats
path: root/tests/auto/gui/rhi
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2023-12-05 15:34:56 +0100
committerLaszlo Agocs <laszlo.agocs@qt.io>2024-01-15 17:48:45 +0100
commitcf6e018a173cacedd0c7da391159c93898e23bcb (patch)
tree6c43c094e440eb43726ac128c7db8ec37a94e525 /tests/auto/gui/rhi
parent75f5eec2c904b5a3a274d9f1c70e14e687e6fa29 (diff)
rhi: Add an autotest for multiview
Fixes: QTBUG-119742 Change-Id: Id4dba72eadfac74e1dd9ef57d90774c6a8bf8bdd Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Andy Nichols <andy.nichols@qt.io>
Diffstat (limited to 'tests/auto/gui/rhi')
-rw-r--r--tests/auto/gui/rhi/qrhi/data/buildshaders.bat2
-rw-r--r--tests/auto/gui/rhi/qrhi/data/multiview.frag10
-rw-r--r--tests/auto/gui/rhi/qrhi/data/multiview.frag.qsbbin0 -> 2425 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/multiview.vert18
-rw-r--r--tests/auto/gui/rhi/qrhi/data/multiview.vert.qsbbin0 -> 3644 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp181
6 files changed, 210 insertions, 1 deletions
diff --git a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
index 5b07c7bf2b..be36cf6162 100644
--- a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
+++ b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
@@ -26,3 +26,5 @@ qsb --glsl 320es,430 --msl 21 --msltess tessinterfaceblocks.vert -o tessinterfac
qsb --glsl 320es,430 --msl 21 --tess-mode triangles tessinterfaceblocks.tesc -o tessinterfaceblocks.tesc.qsb
qsb --glsl 320es,430 --msl 21 --tess-vertex-count 3 tessinterfaceblocks.tese -o tessinterfaceblocks.tese.qsb
qsb --glsl 320es,430 --msl 21 simpletess.frag -o tessinterfaceblocks.frag.qsb
+qsb --view-count 2 --glsl "300 es,330" --hlsl 61 -c --msl 12 multiview.vert -o multiview.vert.qsb
+qsb --glsl "300 es,330" --hlsl 61 -c --msl 12 multiview.frag -o multiview.frag.qsb
diff --git a/tests/auto/gui/rhi/qrhi/data/multiview.frag b/tests/auto/gui/rhi/qrhi/data/multiview.frag
new file mode 100644
index 0000000000..375587662f
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/multiview.frag
@@ -0,0 +1,10 @@
+#version 440
+
+layout(location = 0) in vec3 v_color;
+
+layout(location = 0) out vec4 fragColor;
+
+void main()
+{
+ fragColor = vec4(v_color, 1.0);
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/multiview.frag.qsb b/tests/auto/gui/rhi/qrhi/data/multiview.frag.qsb
new file mode 100644
index 0000000000..db8133f12e
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/multiview.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/multiview.vert b/tests/auto/gui/rhi/qrhi/data/multiview.vert
new file mode 100644
index 0000000000..b9c9e5a704
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/multiview.vert
@@ -0,0 +1,18 @@
+#version 440
+#extension GL_EXT_multiview : require
+
+layout(location = 0) in vec4 pos;
+layout(location = 1) in vec3 color;
+
+layout(location = 0) out vec3 v_color;
+
+layout(std140, binding = 0) uniform buf
+{
+ mat4 mvp[2];
+};
+
+void main()
+{
+ v_color = color;
+ gl_Position = mvp[gl_ViewIndex] * pos;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/multiview.vert.qsb b/tests/auto/gui/rhi/qrhi/data/multiview.vert.qsb
new file mode 100644
index 0000000000..cf1f67f58f
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/multiview.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
index 1c3da1ea42..d8a8ea2b63 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -115,6 +115,8 @@ private slots:
void renderToTextureSrbReuse();
void renderToTextureIndexedDraw_data();
void renderToTextureIndexedDraw();
+ void renderToTextureArrayMultiView_data();
+ void renderToTextureArrayMultiView();
void renderToWindowSimple_data();
void renderToWindowSimple();
void finishWithinSwapchainFrame_data();
@@ -421,7 +423,8 @@ void tst_QRhi::create()
QRhi::OneDimensionalTextureMipmaps,
QRhi::HalfAttributes,
QRhi::RenderToOneDimensionalTexture,
- QRhi::ThreeDimensionalTextureMipmaps
+ QRhi::ThreeDimensionalTextureMipmaps,
+ QRhi::MultiView
};
for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i)
rhi->isFeatureSupported(features[i]);
@@ -3703,6 +3706,182 @@ void tst_QRhi::renderToTextureIndexedDraw()
QVERIFY(redCount > blueCount);
}
+void tst_QRhi::renderToTextureArrayMultiView_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::renderToTextureArrayMultiView()
+{
+ 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");
+
+ if (!rhi->isFeatureSupported(QRhi::MultiView))
+ QSKIP("Multiview not supported, skipping testing on this backend");
+
+ if (rhi->backend() == QRhi::Vulkan && rhi->driverInfo().deviceType == QRhiDriverInfo::CpuDevice)
+ QSKIP("lavapipe does not like multiview, skip for now");
+
+ for (int sampleCount : rhi->supportedSampleCounts()) {
+ const QSize outputSize(1920, 1080);
+ QRhiTexture::Flags textureFlags = QRhiTexture::RenderTarget;
+ if (sampleCount <= 1)
+ textureFlags |= QRhiTexture::UsedAsTransferSource;
+ QScopedPointer<QRhiTexture> texture(rhi->newTextureArray(QRhiTexture::RGBA8, 2, outputSize, sampleCount, textureFlags));
+ QVERIFY(texture->create());
+
+ // exercise a depth-stencil buffer as well, not that the triangle needs it; note that this also needs to be a two-layer texture array
+ QScopedPointer<QRhiTexture> ds(rhi->newTextureArray(QRhiTexture::D24S8, 2, outputSize, sampleCount, QRhiTexture::RenderTarget));
+ QVERIFY(ds->create());
+
+ QScopedPointer<QRhiTexture> resolveTexture;
+ if (sampleCount > 1) {
+ resolveTexture.reset(rhi->newTextureArray(QRhiTexture::RGBA8, 2, outputSize, 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(resolveTexture->create());
+ }
+
+ QRhiColorAttachment multiViewAtt(texture.get());
+ multiViewAtt.setMultiViewCount(2);
+ if (sampleCount > 1)
+ multiViewAtt.setResolveTexture(resolveTexture.get());
+
+ QRhiTextureRenderTargetDescription rtDesc(multiViewAtt);
+ rtDesc.setDepthTexture(ds.get());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
+
+ static float triangleData[] = {
+ 0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
+ -0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
+ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f
+ };
+
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleData)));
+ QVERIFY(vbuf->create());
+ updates->uploadStaticBuffer(vbuf.data(), triangleData);
+
+ QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 128)); // mat4 mvp[2]
+ QVERIFY(ubuf->create());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf.get())
+ });
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> ps(rhi->newGraphicsPipeline());
+ ps->setShaderStages({
+ { QRhiShaderStage::Vertex, loadShader(":/data/multiview.vert.qsb") },
+ { QRhiShaderStage::Fragment, loadShader(":/data/multiview.frag.qsb") }
+ });
+ ps->setMultiViewCount(2); // the view count must be set both on the render target and the pipeline
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 5 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float3, quint32(2 * sizeof(float)) }
+ });
+ ps->setDepthTest(true);
+ ps->setDepthWrite(true);
+ ps->setSampleCount(sampleCount);
+ ps->setVertexInputLayout(inputLayout);
+ ps->setShaderResourceBindings(srb.get());
+ ps->setRenderPassDescriptor(rpDesc.get());
+ QVERIFY(ps->create());
+
+ QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
+ mvp.perspective(45.0f, outputSize.width() / float(outputSize.height()), 0.01f, 1000.0f);
+ mvp.translate(0, 0, -2);
+ mvp.rotate(90, 0, 0, 1); // point left
+ updates->updateDynamicBuffer(ubuf.get(), 0, 64, mvp.constData());
+ mvp.rotate(-180, 0, 0, 1); // point right
+ updates->updateDynamicBuffer(ubuf.get(), 64, 64, mvp.constData());
+
+ cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates);
+ cb->setGraphicsPipeline(ps.data());
+ cb->setShaderResources();
+ cb->setViewport({ 0, 0, float(outputSize.width()), float(outputSize.height()) });
+ QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbindings);
+ cb->draw(3);
+
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ QRhiReadbackResult readResult[2];
+ QRhiReadbackDescription readbackDesc;
+ if (sampleCount > 1)
+ readbackDesc.setTexture(resolveTexture.get());
+ else
+ readbackDesc.setTexture(texture.get());
+ readbackDesc.setLayer(0);
+ readbackBatch->readBackTexture(readbackDesc, &readResult[0]);
+ readbackDesc.setLayer(1);
+ readbackBatch->readBackTexture(readbackDesc, &readResult[1]);
+
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ if (rhi->backend() == QRhi::Null)
+ QSKIP("No real content with Null backend, skipping multiview content check");
+
+ // both readbacks should be finished now due to using offscreen frames
+
+ QImage image0 = QImage(reinterpret_cast<const uchar *>(readResult[0].data.constData()),
+ readResult[0].pixelSize.width(), readResult[0].pixelSize.height(),
+ QImage::Format_RGBA8888);
+ if (rhi->isYUpInFramebuffer()) // note that we used clipSpaceCorrMatrix
+ image0 = image0.mirrored();
+
+ QImage image1 = QImage(reinterpret_cast<const uchar *>(readResult[1].data.constData()),
+ readResult[1].pixelSize.width(), readResult[1].pixelSize.height(),
+ QImage::Format_RGBA8888);
+ if (rhi->isYUpInFramebuffer())
+ image1 = image1.mirrored();
+
+ QVERIFY(!image0.isNull());
+ QVERIFY(!image1.isNull());
+
+ // image0 should have a triangle rotated so that it points left with the red
+ // tip. image1 should have a triangle rotated so that it points right with
+ // the red tip. Both are centered, so we will check in range 0..width/2 for
+ // image0 and width/2..width-1 for image1 to see if the red-enough pixels
+ // are present.
+
+ int y = image0.height() / 2;
+ int n = 0;
+ for (int x = 0; x < image0.width() / 2; ++x) {
+ QRgb c = image0.pixel(x, y);
+ if (qRed(c) > 250 && qGreen(c) < 10 && qBlue(c) < 10)
+ ++n;
+ }
+ QVERIFY(n >= 10);
+
+ y = image1.height() / 2;
+ n = 0;
+ for (int x = image1.width() / 2; x < image1.width(); ++x) {
+ QRgb c = image1.pixel(x, y);
+ if (qRed(c) > 250 && qGreen(c) < 10 && qBlue(c) < 10)
+ ++n;
+ }
+ QVERIFY(n >= 10);
+ }
+}
+
void tst_QRhi::renderToWindowSimple_data()
{
rhiTestData();