From dc0b2466f8dda3a0858e01c9d63098c1d9b638f2 Mon Sep 17 00:00:00 2001 From: Ben Fletcher Date: Tue, 13 Dec 2022 15:47:19 -0800 Subject: 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 --- tests/auto/gui/rhi/qrhi/data/buildshaders.bat | 6 +- .../gui/rhi/qrhi/data/storagebuffer_runtime.comp | 25 ++ .../rhi/qrhi/data/storagebuffer_runtime.comp.qsb | Bin 0 -> 1415 bytes .../gui/rhi/qrhi/data/storagebuffer_runtime.frag | 33 +++ .../rhi/qrhi/data/storagebuffer_runtime.frag.qsb | Bin 0 -> 1259 bytes .../gui/rhi/qrhi/data/storagebuffer_runtime.tesc | 42 +++ .../rhi/qrhi/data/storagebuffer_runtime.tesc.qsb | Bin 0 -> 2024 bytes .../gui/rhi/qrhi/data/storagebuffer_runtime.tese | 39 +++ .../rhi/qrhi/data/storagebuffer_runtime.tese.qsb | Bin 0 -> 1854 bytes .../gui/rhi/qrhi/data/storagebuffer_runtime.vert | 48 ++++ .../rhi/qrhi/data/storagebuffer_runtime.vert.qsb | Bin 0 -> 2047 bytes tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 297 +++++++++++++++++++++ 12 files changed, 489 insertions(+), 1 deletion(-) create mode 100644 tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp create mode 100644 tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp.qsb create mode 100644 tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag create mode 100644 tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag.qsb create mode 100644 tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc create mode 100644 tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc.qsb create mode 100644 tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese create mode 100644 tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese.qsb create mode 100644 tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert create mode 100644 tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert.qsb (limited to 'tests/auto/gui/rhi/qrhi') 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 Binary files /dev/null and b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.comp.qsb 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 Binary files /dev/null and b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.frag.qsb 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 Binary files /dev/null and b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tesc.qsb 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 Binary files /dev/null and b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.tese.qsb 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 Binary files /dev/null and b/tests/auto/gui/rhi/qrhi/data/storagebuffer_runtime.vert.qsb 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 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 blocks; + for (const QShaderDescription::StorageBlock &block : s.description().storageBlocks()) + blocks[block.blockName] = block; + + QMap toGpuMembers; + for (const QShaderDescription::BlockVariable &member : blocks["toGpu"].members) + toGpuMembers[member.name] = member; + + QMap 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 toGpuBuffer( + rhi->newBuffer(type, QRhiBuffer::UsageFlag::StorageBuffer, + blocks["toGpu"].knownSize + length * stride430)); + QVERIFY(toGpuBuffer->create()); + + QScopedPointer 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(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 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 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(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 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 texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(64, 64), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->create()); + + QScopedPointer rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer 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 vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(triangleVertices))); + QVERIFY(vbuf->create()); + u->uploadStaticBuffer(vbuf.data(), triangleVertices); + + QScopedPointer ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64)); + QVERIFY(ubuf->create()); + + QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix(); + u->updateDynamicBuffer(ubuf.data(), 0, 64, mvp.constData()); + + QScopedPointer ssbo5(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 256)); + QVERIFY(ssbo5->create()); + + QScopedPointer ssbo3(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 16)); + QVERIFY(ssbo3->create()); + + u->uploadStaticBuffer(ssbo3.data(), QVector({ 1.0f, 1.0f, 1.0f, 1.0f }).constData()); + + QScopedPointer ssbo4(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 128)); + QVERIFY(ssbo4->create()); + + const int red = 79; + const int green = 43; + const int blue = 251; + + QScopedPointer ssboR(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, red * sizeof(float))); + QVERIFY(ssboR->create()); + + QScopedPointer ssboG(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, green * sizeof(float))); + QVERIFY(ssboG->create()); + + QScopedPointer 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 ssboTessOuter0(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter0 * sizeof(float))); + QVERIFY(ssboTessOuter0->create()); + + QScopedPointer ssboTessOuter1(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter1 * sizeof(float))); + QVERIFY(ssboTessOuter1->create()); + + QScopedPointer ssboTessOuter2(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessOuter2 * sizeof(float))); + QVERIFY(ssboTessOuter2->create()); + + QScopedPointer ssboTessInner0(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, tessInner0 * sizeof(float))); + QVERIFY(ssboTessInner0->create()); + + + QScopedPointer 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 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(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 QTEST_MAIN(tst_QRhi) -- cgit v1.2.3