summaryrefslogtreecommitdiffstats
path: root/tests/auto/gui/rhi
diff options
context:
space:
mode:
authorBen Fletcher <ben.fletcher@me.com>2022-12-13 15:47:19 -0800
committerLaszlo Agocs <laszlo.agocs@qt.io>2023-02-14 17:48:09 +0100
commitdc0b2466f8dda3a0858e01c9d63098c1d9b638f2 (patch)
treed47317b1669f4ba7c36f57074a3b8b1b81a373e9 /tests/auto/gui/rhi
parentd21e9a432661b161d94cd4ee90804a4a9b122fe0 (diff)
RHI: Metal SPIRV-Cross buffer size buffers
When SPIRV-Cross encounters a GLSL storage buffer runtime sized array, it generates MSL code which expects a "buffer size buffer" containing a list of storage buffer sizes to be bound. This patch adds RHI backend support for Metal "buffer size buffers" on compute and graphics (including tessellation) pipelines. Includes unit tests. An accompanying patch to qtshadertools is required. Change-Id: I9392bfb21803e1a868d7de420fedc097a8452429 Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
Diffstat (limited to 'tests/auto/gui/rhi')
-rw-r--r--tests/auto/gui/rhi/qrhi/data/buildshaders.bat6
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp25
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp.qsbbin0 -> 1415 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag33
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag.qsbbin0 -> 1259 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc42
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc.qsbbin0 -> 2024 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese39
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese.qsbbin0 -> 1854 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert48
-rw-r--r--tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert.qsbbin0 -> 2047 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp297
12 files changed, 489 insertions, 1 deletions
diff --git a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
index 1b5d4f28a6..37050fe80f 100644
--- a/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
+++ b/tests/auto/gui/rhi/qrhi/data/buildshaders.bat
@@ -16,4 +16,8 @@ qsb --glsl 320es,410 --msl 12 --tess-mode triangles simpletess.tesc -o simpletes
qsb --glsl 320es,410 --msl 12 --tess-vertex-count 3 simpletess.tese -o simpletess.tese.qsb
qsb --glsl 320es,410 --msl 12 simpletess.frag -o simpletess.frag.qsb
qsb --glsl 310es,430 --msl 12 --hlsl 50 storagebuffer.comp -o storagebuffer.comp.qsb
-
+qsb --glsl 320es,430 --msl 12 --msltess storagebuffer_runtime.vert -o storagebuffer_runtime.vert.qsb
+qsb --glsl 320es,430 --msl 12 --tess-mode triangles storagebuffer_runtime.tesc -o storagebuffer_runtime.tesc.qsb
+qsb --glsl 320es,430 --msl 12 --tess-vertex-count 3 storagebuffer_runtime.tese -o storagebuffer_runtime.tese.qsb
+qsb --glsl 320es,430 --msl 12 storagebuffer_runtime.frag -o storagebuffer_runtime.frag.qsb
+qsb --glsl 320es,430 --hlsl 50 -c --msl 12 storagebuffer_runtime.comp -o storagebuffer_runtime.comp.qsb
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp
new file mode 100644
index 0000000000..d36f5426bc
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp
@@ -0,0 +1,25 @@
+#version 430
+
+layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+
+layout (binding = 0, std430) buffer toGpu
+{
+ float _float[];
+};
+
+
+layout (binding = 1, std140) buffer fromGpu
+{
+ int _int[];
+};
+
+void main()
+{
+ int length = min(_float.length(), _int.length());
+
+ for (int i = 0; i < length; ++i)
+ _int[i] = int(_float[i]);
+
+}
+
+
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp.qsb
new file mode 100644
index 0000000000..b4c43ecc9b
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag
new file mode 100644
index 0000000000..2e45a5f62a
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag
@@ -0,0 +1,33 @@
+#version 450
+
+layout (location = 0) out vec4 fragColor;
+
+layout (std430, binding = 1) readonly buffer ssboG
+{
+ float g[];
+};
+
+layout (std430, binding = 2) readonly buffer ssboB
+{
+ float b[];
+};
+
+layout (std430, binding = 6) readonly buffer ssboR
+{
+ float r[];
+};
+
+layout (std430, binding = 3) readonly buffer ssbo3
+{
+ vec4 _vec4;
+};
+
+void main()
+{
+
+ // some OpenGL implementations will optimize out the buffer variables if we don't use them
+ // resulting in a .length() of 0.
+ float a = (r[0]+g[0]+b[0])>0?1:1;
+
+ fragColor = a * vec4(r.length(), g.length(), b.length(), 255)/vec4(255);
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag.qsb
new file mode 100644
index 0000000000..53fc9a1906
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc
new file mode 100644
index 0000000000..56060285d2
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc
@@ -0,0 +1,42 @@
+#version 450
+
+layout(vertices = 3) out;
+
+
+layout (std430, binding = 7) readonly buffer ssbo7
+{
+ float float7[];
+};
+
+layout (std430, binding = 8) readonly buffer ssbo8
+{
+ float float8[];
+};
+
+layout (std430, binding = 9) readonly buffer ssbo9
+{
+ float float9[];
+};
+
+layout (std430, binding = 10) readonly buffer ssbo10
+{
+ float float10[];
+};
+
+void main()
+{
+
+ // some OpenGL implementations will optimize out the buffer variables if we don't use them
+ // resulting in a .length() of 0
+ float a = float7[0] == 0 && float8[0] == 0 && float9[0] == 0 && float10[0] == 0 ? 1 : 1;
+
+ if (gl_InvocationID == 0) {
+ gl_TessLevelOuter[0] = float7.length() * a;
+ gl_TessLevelOuter[1] = float8.length() * a;
+ gl_TessLevelOuter[2] = float9.length() * a;
+ gl_TessLevelInner[0] = float10.length() * a;
+ }
+
+ gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
+
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc.qsb
new file mode 100644
index 0000000000..e48aa0269c
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese
new file mode 100644
index 0000000000..a8bec13561
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese
@@ -0,0 +1,39 @@
+#version 450
+
+layout(triangles, fractional_odd_spacing, ccw) in;
+
+layout (std140, binding = 6) uniform unused0
+{
+ int unused;
+}u0;
+
+layout (binding = 0) uniform u
+{
+ mat4 matrix;
+};
+
+layout (std430, binding = 5) readonly buffer ssbo5
+{
+ float _float[];
+};
+
+layout (std430, binding = 8) readonly buffer ssbo8
+{
+ float float8[];
+};
+
+layout (std430, binding = 1) readonly buffer unused1
+{
+ int unused[];
+}u1;
+
+
+void main()
+{
+ // some OpenGL implementations will optimize out the buffer variables if we don't use them
+ // resulting in a .length() of 0
+ float a = _float[0] == 0 && float8[0] == 1 ? 1 : 1;
+
+ if(_float.length() == 64)
+ gl_Position = a * matrix * ((gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position)) * (float8.length()==2?1:0);
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese.qsb
new file mode 100644
index 0000000000..23a433b5ae
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese.qsb
Binary files differ
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert
new file mode 100644
index 0000000000..b3ac10efea
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert
@@ -0,0 +1,48 @@
+#version 450
+
+layout (location = 0) in vec3 position;
+
+layout (std140, binding = 6) uniform unused0
+{
+ int unused;
+}u0;
+
+layout (binding = 0) uniform u
+{
+ mat4 matrix;
+};
+
+layout (std430, binding = 5) readonly buffer ssbo5
+{
+ float _float[];
+};
+
+layout (std140, binding = 3) readonly buffer ssbo3
+{
+ vec4 _vec4;
+};
+
+layout (std430, binding = 4) readonly buffer ssbo1
+{
+ bool _bool[];
+};
+
+layout (std430, binding = 1) readonly buffer unused1
+{
+ int unused[];
+}u1;
+
+
+void main()
+{
+
+ // some OpenGL implementations will optimize out the buffer variables if we don't use them
+ // resulting in a .length() of 0
+ float a = _float[0] == 0 && _bool[0] ? 1 : 1;
+
+ gl_Position = vec4(0);
+
+ if(_bool.length() == 32)
+ gl_Position = a * matrix * vec4(position*_vec4.xyz, _float.length() == 64 ? 1.0 : 0.0);
+
+}
diff --git a/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert.qsb b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert.qsb
new file mode 100644
index 0000000000..8b1cff52fd
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.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 a298a9f545..225226eccf 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -147,6 +147,10 @@ private slots:
void storageBuffer_data();
void storageBuffer();
+ void storageBufferRuntimeSizeCompute_data();
+ void storageBufferRuntimeSizeCompute();
+ void storageBufferRuntimeSizeGraphics_data();
+ void storageBufferRuntimeSizeGraphics();
private:
void setWindowType(QWindow *window, QRhi::Implementation impl);
@@ -5889,5 +5893,298 @@ void tst_QRhi::storageBuffer()
}
}
+ void tst_QRhi::storageBufferRuntimeSizeCompute_data()
+{
+ rhiTestData();
+}
+
+ void tst_QRhi::storageBufferRuntimeSizeCompute()
+{
+ // Use a compute shader to copy from one storage buffer with std430 runtime
+ // float array to another with std140 runtime int array. We fill the
+ // "toGpu" buffer with known float data generated and uploaded from the
+ // CPU, then dispatch a compute shader to copy from the "toGpu" buffer to
+ // the "fromGpu" buffer. We then readback the "fromGpu" buffer and verify
+ // that the results are as expected. This is primarily to test Metal
+ // SPIRV-Cross buffer size buffers.
+
+ QFETCH(QRhi::Implementation, impl);
+ QFETCH(QRhiInitParams *, initParams);
+
+ // we can't test with Null as there is no compute
+ if (impl == QRhi::Null)
+ return;
+
+ QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr));
+ if (!rhi)
+ QSKIP("QRhi could not be created, skipping testing");
+
+ if (!rhi->isFeatureSupported(QRhi::Feature::Compute))
+ QSKIP("Compute is not supported with this graphics API, skipping test");
+
+ QShader s = loadShader(":/data/storagebuffer_runtime.comp.qsb");
+ QVERIFY(s.isValid());
+ QCOMPARE(s.description().storageBlocks().size(), 2);
+
+ QMap<QByteArray, QShaderDescription::StorageBlock> blocks;
+ for (const QShaderDescription::StorageBlock &block : s.description().storageBlocks())
+ blocks[block.blockName] = block;
+
+ QMap<QByteArray, QShaderDescription::BlockVariable> toGpuMembers;
+ for (const QShaderDescription::BlockVariable &member : blocks["toGpu"].members)
+ toGpuMembers[member.name] = member;
+
+ QMap<QByteArray, QShaderDescription::BlockVariable> fromGpuMembers;
+ for (const QShaderDescription::BlockVariable &member : blocks["fromGpu"].members)
+ fromGpuMembers[member.name] = member;
+
+ for (QRhiBuffer::Type type : { QRhiBuffer::Type::Immutable, QRhiBuffer::Type::Static }) {
+ QRhiCommandBuffer *cb = nullptr;
+
+ rhi->beginOffscreenFrame(&cb);
+ QVERIFY(cb);
+
+ QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
+ QVERIFY(u);
+
+ const int stride430 = sizeof(float);
+ const int stride140 = 4 * sizeof(float);
+ const int length = 32;
+
+ QScopedPointer<QRhiBuffer> toGpuBuffer(
+ rhi->newBuffer(type, QRhiBuffer::UsageFlag::StorageBuffer,
+ blocks["toGpu"].knownSize + length * stride430));
+ QVERIFY(toGpuBuffer->create());
+
+ QScopedPointer<QRhiBuffer> fromGpuBuffer(
+ rhi->newBuffer(type, QRhiBuffer::UsageFlag::StorageBuffer,
+ blocks["fromGpu"].knownSize + length * stride140));
+ QVERIFY(fromGpuBuffer->create());
+
+ QByteArray toGpuData(toGpuBuffer->size(), 0);
+ for (int i = 0; i < length; ++i)
+ reinterpret_cast<float &>(toGpuData.data()[toGpuMembers["_float"].offset + i * stride430]) = float(i);
+
+ u->uploadStaticBuffer(toGpuBuffer.data(), 0, toGpuData.size(), toGpuData.constData());
+ u->uploadStaticBuffer(fromGpuBuffer.data(), 0, blocks["fromGpu"].knownSize,
+ QByteArray(fromGpuBuffer->size(), 0).constData());
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings(
+ { QRhiShaderResourceBinding::bufferLoadStore(
+ blocks["toGpu"].binding, QRhiShaderResourceBinding::ComputeStage,
+ toGpuBuffer.data()),
+ QRhiShaderResourceBinding::bufferLoadStore(
+ blocks["fromGpu"].binding, QRhiShaderResourceBinding::ComputeStage,
+ fromGpuBuffer.data()) });
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiComputePipeline> pipeline(rhi->newComputePipeline());
+ pipeline->setShaderStage({ QRhiShaderStage::Compute, s });
+ pipeline->setShaderResourceBindings(srb.data());
+ QVERIFY(pipeline->create());
+
+ cb->beginComputePass(u);
+
+ cb->setComputePipeline(pipeline.data());
+ cb->setShaderResources();
+ cb->dispatch(1, 1, 1);
+
+ u = rhi->nextResourceUpdateBatch();
+ QVERIFY(u);
+ int readbackCompleted = 0;
+ QRhiBufferReadbackResult result;
+ result.completed = [&readbackCompleted]() { readbackCompleted++; };
+ u->readBackBuffer(fromGpuBuffer.data(), 0, fromGpuBuffer->size(), &result);
+
+ cb->endComputePass(u);
+
+ rhi->endOffscreenFrame();
+
+ QVERIFY(readbackCompleted > 0);
+ QCOMPARE(result.data.size(), fromGpuBuffer->size());
+
+ for (int i = 0; i < length; ++i)
+ QCOMPARE(reinterpret_cast<const int &>(result.data.constData()[fromGpuMembers["_int"].offset + i * stride140]), i);
+
+ QCOMPARE(readbackCompleted, 1);
+
+ }
+
+}
+
+void tst_QRhi::storageBufferRuntimeSizeGraphics_data()
+{
+ rhiTestData();
+}
+
+void tst_QRhi::storageBufferRuntimeSizeGraphics()
+{
+ // Draws a tessellated triangle with color determined by the length of
+ // buffers bound to shader stages. This is primarily to test Metal
+ // SPIRV-Cross buffer size buffers.
+
+ 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::Tessellation)) {
+ // From a Vulkan or Metal implementation we expect tessellation to work,
+ // even though it is optional (as per spec) for Vulkan.
+ QVERIFY(rhi->backend() != QRhi::Vulkan);
+ QVERIFY(rhi->backend() != QRhi::Metal);
+ QSKIP("Tessellation is not supported with this graphics API, skipping test");
+ }
+
+ if (rhi->backend() == QRhi::D3D11)
+ QSKIP("Skipping tessellation test on D3D for now, test assets not prepared for HLSL yet");
+
+ QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(64, 64), 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(texture->create());
+
+ QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() }));
+ QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
+ rt->setRenderPassDescriptor(rpDesc.data());
+ QVERIFY(rt->create());
+
+ static const float triangleVertices[] = {
+ 0.0f, 0.5f, 0.0f,
+ -0.5f, -0.5f, 0.0f,
+ 0.5f, -0.5f, 0.0f,
+ };
+
+ QRhiResourceUpdateBatch *u = rhi->nextResourceUpdateBatch();
+ QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices)));
+ QVERIFY(vbuf->create());
+ u->uploadStaticBuffer(vbuf.data(), triangleVertices);
+
+ QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
+ QVERIFY(ubuf->create());
+
+ QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
+ u->updateDynamicBuffer(ubuf.data(), 0, 64, mvp.constData());
+
+ QScopedPointer<QRhiBuffer> ssbo5(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 256));
+ QVERIFY(ssbo5->create());
+
+ QScopedPointer<QRhiBuffer> ssbo3(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 16));
+ QVERIFY(ssbo3->create());
+
+ u->uploadStaticBuffer(ssbo3.data(), QVector<float>({ 1.0f, 1.0f, 1.0f, 1.0f }).constData());
+
+ QScopedPointer<QRhiBuffer> ssbo4(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 128));
+ QVERIFY(ssbo4->create());
+
+ const int red = 79;
+ const int green = 43;
+ const int blue = 251;
+
+ QScopedPointer<QRhiBuffer> ssboR(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, red * sizeof(float)));
+ QVERIFY(ssboR->create());
+
+ QScopedPointer<QRhiBuffer> ssboG(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, green * sizeof(float)));
+ QVERIFY(ssboG->create());
+
+ QScopedPointer<QRhiBuffer> ssboB(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, blue * sizeof(float)));
+ QVERIFY(ssboB->create());
+
+ const int tessOuter0 = 1;
+ const int tessOuter1 = 2;
+ const int tessOuter2 = 3;
+ const int tessInner0 = 4;
+
+ QScopedPointer<QRhiBuffer> ssboTessOuter0(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter0 * sizeof(float)));
+ QVERIFY(ssboTessOuter0->create());
+
+ QScopedPointer<QRhiBuffer> ssboTessOuter1(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter1 * sizeof(float)));
+ QVERIFY(ssboTessOuter1->create());
+
+ QScopedPointer<QRhiBuffer> ssboTessOuter2(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter2 * sizeof(float)));
+ QVERIFY(ssboTessOuter2->create());
+
+ QScopedPointer<QRhiBuffer> ssboTessInner0(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessInner0 * sizeof(float)));
+ QVERIFY(ssboTessInner0->create());
+
+
+ QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings());
+ srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ubuf.data()),
+ QRhiShaderResourceBinding::bufferLoad(5, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ssbo5.data()),
+ QRhiShaderResourceBinding::bufferLoad(3, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage | QRhiShaderResourceBinding::FragmentStage, ssbo3.data()),
+ QRhiShaderResourceBinding::bufferLoad(4, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ssbo4.data()),
+ QRhiShaderResourceBinding::bufferLoad(7, QRhiShaderResourceBinding::TessellationControlStage, ssboTessOuter0.data()),
+ QRhiShaderResourceBinding::bufferLoad(8, QRhiShaderResourceBinding::TessellationControlStage | QRhiShaderResourceBinding::TessellationEvaluationStage, ssboTessOuter1.data()),
+ QRhiShaderResourceBinding::bufferLoad(9, QRhiShaderResourceBinding::TessellationControlStage, ssboTessOuter2.data()),
+ QRhiShaderResourceBinding::bufferLoad(10, QRhiShaderResourceBinding::TessellationControlStage, ssboTessInner0.data()),
+ QRhiShaderResourceBinding::bufferLoad(1, QRhiShaderResourceBinding::FragmentStage, ssboG.data()),
+ QRhiShaderResourceBinding::bufferLoad(2, QRhiShaderResourceBinding::FragmentStage, ssboB.data()),
+ QRhiShaderResourceBinding::bufferLoad(6, QRhiShaderResourceBinding::FragmentStage, ssboR.data()) });
+
+ QVERIFY(srb->create());
+
+ QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline());
+
+ pipeline->setTopology(QRhiGraphicsPipeline::Patches);
+ pipeline->setPatchControlPointCount(3);
+
+ pipeline->setShaderStages({
+ { QRhiShaderStage::Vertex, loadShader(":/data/storagebuffer_runtime.vert.qsb") },
+ { QRhiShaderStage::TessellationControl, loadShader(":/data/storagebuffer_runtime.tesc.qsb") },
+ { QRhiShaderStage::TessellationEvaluation, loadShader(":/data/storagebuffer_runtime.tese.qsb") },
+ { QRhiShaderStage::Fragment, loadShader(":/data/storagebuffer_runtime.frag.qsb") }
+ });
+
+ pipeline->setCullMode(QRhiGraphicsPipeline::None);
+
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 3 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ });
+
+ pipeline->setVertexInputLayout(inputLayout);
+ pipeline->setShaderResourceBindings(srb.data());
+ pipeline->setRenderPassDescriptor(rpDesc.data());
+
+ QVERIFY(pipeline->create());
+
+ QRhiCommandBuffer *cb = nullptr;
+ QCOMPARE(rhi->beginOffscreenFrame(&cb), QRhi::FrameOpSuccess);
+
+ cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, u);
+ cb->setGraphicsPipeline(pipeline.data());
+ cb->setViewport({ 0, 0, float(rt->pixelSize().width()), float(rt->pixelSize().height()) });
+ cb->setShaderResources();
+ QRhiCommandBuffer::VertexInput vbufBinding(vbuf.data(), 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ 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);
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture({ texture.data() }, &readResult);
+ cb->endPass(readbackBatch);
+
+ rhi->endOffscreenFrame();
+
+ QCOMPARE(result.size(), rt->pixelSize());
+
+ // cannot check rendering results with Null, because there is no rendering there
+ if (impl == QRhi::Null)
+ return;
+
+ QCOMPARE(result.pixel(32, 32), qRgb(red, green, blue));
+}
+
#include <tst_qrhi.moc>
QTEST_MAIN(tst_QRhi)