From cf6e018a173cacedd0c7da391159c93898e23bcb Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Tue, 5 Dec 2023 15:34:56 +0100 Subject: [PATCH] rhi: Add an autotest for multiview Fixes: QTBUG-119742 Change-Id: Id4dba72eadfac74e1dd9ef57d90774c6a8bf8bdd Reviewed-by: Qt CI Bot Reviewed-by: Andy Nichols --- tests/auto/gui/rhi/qrhi/data/buildshaders.bat | 2 + tests/auto/gui/rhi/qrhi/data/multiview.frag | 10 + .../auto/gui/rhi/qrhi/data/multiview.frag.qsb | Bin 0 -> 2425 bytes tests/auto/gui/rhi/qrhi/data/multiview.vert | 18 ++ .../auto/gui/rhi/qrhi/data/multiview.vert.qsb | Bin 0 -> 3644 bytes tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 181 +++++++++++++++++- 6 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 tests/auto/gui/rhi/qrhi/data/multiview.frag create mode 100644 tests/auto/gui/rhi/qrhi/data/multiview.frag.qsb create mode 100644 tests/auto/gui/rhi/qrhi/data/multiview.vert create mode 100644 tests/auto/gui/rhi/qrhi/data/multiview.vert.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat index 5b07c7bf2b4..be36cf6162e 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 00000000000..375587662f3 --- /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 0000000000000000000000000000000000000000..db8133f12ed1f6fb8cf116e29af9cab155b4a297 GIT binary patch literal 2425 zcmV-<35NCn01Qa}HPBcRGLC|4@?jeX zZIE}=UjTao*wRoILVe^%z^z5YngoG_BRrEE3potwi#UCZ(-(6wi#UBT7bn7=gJS`^ zH&i^|Qo(fbxwQc2;|qwOBxZBI=W+JM5EZbw2w)%tqd1Hgf<>z*bZQEdoGIER5p*G+2$;f z*IJ7$WYs`V8T18j03{< zaPnRFvVH-jB(H!WLoQQ6$=cLZ?v(^_Wyn0nwFD7pOV^ZX*RS7@3)Vx!w0~fj6Ji+7 zwI!Rjl^PvY78lT$Y+`&6VZhT%kl=Yd^!H?u{9PP#51b0=^Wbj>#-Fp{fm{mX=0ka_ zq*PtH6(-mS`!B-D+_C~4S`_)pg}6pL{81cS@Y@g*dui4d{SFXK347I}c!7XQTeXkp zlO%?b(Xsd*lSj6Ouh}Qo3{q*bm885-l(wNQzf(qQ^V*Jp=rqm!d}-(2_;Y%7aZ|_L z=1jyq`UL=OMxJl_oJVl;}kKpu!W%4^Wju-y%-7}%!8=L_kI7Q8Ed z&>JckISC*U$ST1!Ic#_hB%h~!GR<3Z54Axs(Bv`VruTKd@#}hC zR?WZuhX?U%_r22H8~a_&nMHZ>){(*F!@dhuCEItsn&Noo(}jKhUj6t<4_sg781}mi zepR)~(Q9cjdzO>jLS#*T1 zvqPA)_B^hQPh|LdN!SKKi(Z;oEdQjKDqUy7`^k&_IijXX2^fk7jpq4S1c}@D=JBI)m&?s!XC`*yxvsv#olflF_932nK%CjPbMF$<5>fAxKlKhZHKZ0g#pc2( z3Xj4~leA<6Z=zM?8a2;m^XAADtypvtDOs+jB}udF5QTr-LsR7bvn!7rxy-KUsQKD+EK3Cxr z+&C^X=LjqYK^4=lE*4mi$LX=8*>@Y|)AZ7^D|nMZMP5?Wl!;Vyvx;d>li%uBVk;-9 zb7xwO^X`L5eT|7Bz;OJf+SkyAGcu-sdHE-kqt#|%qLz6$EPd2}276ni=qv9l zT(tS1;&wNERMYyv^kUMA1HYeECCYm{?+#72oS52wp-9;NLdA|0XSK1y;wY-Cu+)_B z2P2~oiOXZRZtm>6sVJA1-{`w}OBVOrhsfy2{@aOrizZqo_7@3<`=!x`3J)sY*cy9v zORwVgt!{(#1E|#T;}3@>_FuSEB%EQ-w*a1RI_ZfycQ>X|#>;NguJny`Xgoy24#L^9 zT`~>Ae?>`u=aHsMG_hyTEWSJtQ^|y}YR!r1UnNyG*!^3BNZZozY9vsdI}zii??yTYT0STgmLsJl&i*u3dEs%R1eh z<-;<^W@K&yv9p?3QSEoY+T*u(`t1h7tt8xEgh972dHmZ*|8}|G(c^FEXOEcGd-$b$3*y^J zJix!I$M|;`pX1->|5^T}0{rWnPzdjws zApUKS_N?MFJqbDOskx=OneHE}bi1$)(~ax%65@(0 z1Nlj0%gMOvB=TozY9%C4qmF7bH|K0ryneo+KY!6%f|DwIVONl1KM-mrAoW#wb7%$L ze)D+7)SGL@k1yB17dNAlOcFK91p*1^|E476eG_uep7QTZl}Ap=AV*Dg)8E&&KG-mw z_v``U^Vg^IR;)-wpGg1bpZ_fX7@^H#^mcLeQp73fTe>tcr&QF!U^(e|z)QEiiu4i$ zLf|zR^cep9*)}!bL(Ap!Bi5W}Or3xx1+5$NBefm!X8`HtnHFEUFA1o7%6^^^$EnlT rQj3Z>^|pFll$H*@so+0L!OzZ+f0)?v*ylZ4cwd1(%?J1!pIn2{tf|`K literal 0 HcmV?d00001 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 00000000000..b9c9e5a704b --- /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 0000000000000000000000000000000000000000..cf1f67f58f3b0b68ad3affe302acadfaeb66de0a GIT binary patch literal 3644 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 O%ZDEfr2hl(3{BGO-8fisFeatureSupported(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 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 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 ds(rhi->newTextureArray(QRhiTexture::D24S8, 2, outputSize, sampleCount, QRhiTexture::RenderTarget)); + QVERIFY(ds->create()); + + QScopedPointer 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 rt(rhi->newTextureRenderTarget(rtDesc)); + QScopedPointer 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 vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleData))); + QVERIFY(vbuf->create()); + updates->uploadStaticBuffer(vbuf.data(), triangleData); + + QScopedPointer ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 128)); // mat4 mvp[2] + QVERIFY(ubuf->create()); + + QScopedPointer srb(rhi->newShaderResourceBindings()); + srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf.get()) + }); + QVERIFY(srb->create()); + + QScopedPointer 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(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(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();