summaryrefslogtreecommitdiffstats
path: root/tests/auto/gui/rhi
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2019-10-06 17:25:06 +0200
committerLaszlo Agocs <laszlo.agocs@qt.io>2019-10-09 17:15:05 +0200
commitdd105fab8d8b4bd39654e7e268e6782e53cce2de (patch)
tree928e7ae7ca67c135e92f9b759b2efbccacf5c7ba /tests/auto/gui/rhi
parent9baf69c765c446b7c8f069ebdd50910877a7d0f8 (diff)
rhi: Autotest rendering a triangle
Also improve (docs and runtime checks) and test the minimum set of required data to create a graphics pipeline. Task-number: QTBUG-78971 Change-Id: If5c14f1ab1ff3cf70f168fde585f05fc9d28ec91 Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
Diffstat (limited to 'tests/auto/gui/rhi')
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.frag8
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.frag.qsbbin0 -> 773 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.vert10
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.vert.qsbbin0 -> 851 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/texture.frag12
-rw-r--r--tests/auto/gui/rhi/qrhi/data/texture.vert18
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp221
7 files changed, 239 insertions, 30 deletions
diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag b/tests/auto/gui/rhi/qrhi/data/simple.frag
new file mode 100644
index 0000000000..2aa500e09a
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simple.frag
@@ -0,0 +1,8 @@
+#version 440
+
+layout(location = 0) out vec4 fragColor;
+
+void main()
+{
+ fragColor = vec4(1.0, 0.0, 0.0, 1.0);
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb
new file mode 100644
index 0000000000..bf0c2692af
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/simple.vert b/tests/auto/gui/rhi/qrhi/data/simple.vert
new file mode 100644
index 0000000000..16ee61beca
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simple.vert
@@ -0,0 +1,10 @@
+#version 440
+
+layout(location = 0) in vec4 position;
+
+out gl_PerVertex { vec4 gl_Position; };
+
+void main()
+{
+ gl_Position = position;
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb
new file mode 100644
index 0000000000..1e010ebac3
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/texture.frag b/tests/auto/gui/rhi/qrhi/data/texture.frag
deleted file mode 100644
index e6021fe905..0000000000
--- a/tests/auto/gui/rhi/qrhi/data/texture.frag
+++ /dev/null
@@ -1,12 +0,0 @@
-#version 440
-
-layout(location = 0) in vec2 v_texcoord;
-
-layout(location = 0) out vec4 fragColor;
-
-layout(binding = 1) uniform sampler2D tex;
-
-void main()
-{
- fragColor = texture(tex, v_texcoord);
-}
diff --git a/tests/auto/gui/rhi/qrhi/data/texture.vert b/tests/auto/gui/rhi/qrhi/data/texture.vert
deleted file mode 100644
index de486cb772..0000000000
--- a/tests/auto/gui/rhi/qrhi/data/texture.vert
+++ /dev/null
@@ -1,18 +0,0 @@
-#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;
-} ubuf;
-
-out gl_PerVertex { vec4 gl_Position; };
-
-void main()
-{
- v_texcoord = texcoord;
- gl_Position = ubuf.mvp * position;
-}
diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
index aa20b0fd4a..65561595a1 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -81,6 +81,10 @@ private slots:
void resourceUpdateBatchRGBATextureCopy();
void resourceUpdateBatchRGBATextureMip_data();
void resourceUpdateBatchRGBATextureMip();
+ void invalidPipeline_data();
+ void invalidPipeline();
+ void renderToTextureSimple_data();
+ void renderToTextureSimple();
private:
struct {
@@ -980,5 +984,222 @@ void tst_QRhi::resourceUpdateBatchRGBATextureMip()
}
}
+static QShader loadShader(const char *name)
+{
+ QFile f(QString::fromUtf8(name));
+ if (f.open(QIODevice::ReadOnly)) {
+ const QByteArray contents = f.readAll();
+ return QShader::fromSerialized(contents);
+ }
+ return QShader();
+}
+
+void tst_QRhi::invalidPipeline_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::invalidPipeline()
+{
+ 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 empty shader");
+
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(256, 256), 1, QRhiTexture::RenderTarget));
+ QVERIFY(texture->build());
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->build());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ QVERIFY(srb->build());
+
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({ { 2 * sizeof(float) } });
+ inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } });
+
+ // no stages
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(!pipeline->build());
+
+ QShader vs;
+ QShader fs;
+
+ // no shaders in the stages
+ pipeline.reset(rhi->newGraphicsPipeline());
+ pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(!pipeline->build());
+
+ vs = loadShader(":/data/simple.vert.qsb");
+ QVERIFY(vs.isValid());
+ fs = loadShader(":/data/simple.frag.qsb");
+ QVERIFY(fs.isValid());
+
+ // no vertex stage
+ pipeline.reset(rhi->newGraphicsPipeline());
+ pipeline->setShaderStages({ { QRhiShaderStage::Fragment, fs } });
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(!pipeline->build());
+
+ // no vertex inputs
+ pipeline.reset(rhi->newGraphicsPipeline());
+ pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+ pipeline->setShaderResourceBindings(srb.data());
+ QVERIFY(!pipeline->build());
+
+ // no renderpass descriptor
+ pipeline.reset(rhi->newGraphicsPipeline());
+ pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ QVERIFY(!pipeline->build());
+
+ // no shader resource bindings
+ pipeline.reset(rhi->newGraphicsPipeline());
+ pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(!pipeline->build());
+
+ // correct
+ pipeline.reset(rhi->newGraphicsPipeline());
+ pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+ pipeline->setShaderResourceBindings(srb.data());
+ QVERIFY(pipeline->build());
+}
+
+void tst_QRhi::renderToTextureSimple_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::renderToTextureSimple()
+{
+ 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");
+
+ const QSize outputSize(1920, 1080);
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, outputSize, 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->build());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->build());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch();
+
+ static const float vertices[] = {
+ -1.0f, -1.0f,
+ 1.0f, -1.0f,
+ 0.0f, 1.0f
+ };
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices)));
+ QVERIFY(vbuf->build());
+ updates->uploadStaticBuffer(vbuf.data(), vertices);
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ QVERIFY(srb->build());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+ QShader vs = loadShader(":/data/simple.vert.qsb");
+ QVERIFY(vs.isValid());
+ QShader fs = loadShader(":/data/simple.frag.qsb");
+ QVERIFY(fs.isValid());
+ pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } });
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({ { 2 * sizeof(float) } });
+ inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } });
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+
+ QVERIFY(pipeline->build());
+
+ cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates);
+ cb->setGraphicsPipeline(pipeline.data());
+ cb->setViewport({ 0, 0, float(outputSize.width()), float(outputSize.height()) });
+ QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbindings);
+ cb->draw(3);
+
+ QRhiReadbackResult readResult;
+ QImage result;
+ readResult.completed = [&readResult, &result] {
+ result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ QImage::Format_RGBA8888_Premultiplied); // non-owning, no copy needed because readResult outlives result
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture({ texture.data() }, &readResult);
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+ // Offscreen frames are synchronous, so the readback is guaranteed to
+ // complete at this point. This would not be the case with swapchain-based
+ // frames.
+ QCOMPARE(result.size(), texture->pixelSize());
+
+ if (impl == QRhi::Null)
+ return;
+
+ // Now we have a red rectangle on blue background.
+ const int y = 100;
+ const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y));
+ int x = result.width() - 1;
+ int redCount = 0;
+ int blueCount = 0;
+ const int maxFuzz = 1;
+ while (x-- >= 0) {
+ const QRgb c(*p++);
+ if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0)
+ ++redCount;
+ else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz))
+ ++blueCount;
+ else
+ QFAIL("Encountered a pixel that is neither red or blue");
+ }
+
+ QCOMPARE(redCount + blueCount, texture->pixelSize().width());
+
+ // The triangle is "pointing up" in the resulting image with OpenGL
+ // (because Y is up both in normalized device coordinates and in images)
+ // and Vulkan (because Y is down in both and the vertex data was specified
+ // with Y up in mind), but "pointing down" with D3D (because Y is up in NDC
+ // but down in images).
+ if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC())
+ QVERIFY(redCount < blueCount);
+ else
+ QVERIFY(redCount > blueCount);
+}
+
#include <tst_qrhi.moc>
QTEST_MAIN(tst_QRhi)