diff options
author | Laszlo Agocs <laszlo.agocs@qt.io> | 2020-07-10 14:50:55 +0200 |
---|---|---|
committer | Laszlo Agocs <laszlo.agocs@qt.io> | 2020-07-13 14:54:30 +0200 |
commit | 351d42175dc6367a711e2cc4ac6aace23cc462b7 (patch) | |
tree | 6896bce75795881d224541183524b6b2adcc8a45 | |
parent | e8d5000026dbad4f48dfed882f4b19d6c4b34c67 (diff) |
rhi: Allow null resources in srb
In this case the srb represents the layout only, and can still be used
to create a pipeline. For setShaderResources() one will then need to use
another, layout compatible, srb that references valid resources.
Change-Id: I3ea5b63df3be8847540ca4c0c40fbd29dbed8fb7
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
-rw-r--r-- | src/gui/rhi/qrhi.cpp | 142 | ||||
-rw-r--r-- | src/gui/rhi/qrhimetal.mm | 50 | ||||
-rw-r--r-- | src/gui/rhi/qrhivulkan.cpp | 7 | ||||
-rw-r--r-- | tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 199 |
4 files changed, 330 insertions, 68 deletions
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index c5fac634be..6627a459e3 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -2854,7 +2854,15 @@ bool QRhiShaderResourceBinding::isLayoutCompatible(const QRhiShaderResourceBindi \return a shader resource binding for the given binding number, pipeline stages, and buffer specified by \a binding, \a stage, and \a buf. - \note \a buf must have been created with QRhiBuffer::UniformBuffer. + \note When \a buf is not null, it must have been created with + QRhiBuffer::UniformBuffer. + + \note \a buf can be null. It is valid to create a + QRhiShaderResourceBindings with unspecified resources, but such an object + cannot be used with QRhiCommandBuffer::setShaderResources(). It is however + suitable for creating pipelines. Such a pipeline must then always be used + together with another, layout compatible QRhiShaderResourceBindings with + resources present passed to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBuffer( int binding, StageFlags stage, QRhiBuffer *buf) @@ -2880,7 +2888,15 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBuffer( \note \a size must be greater than 0. - \note \a buf must have been created with QRhiBuffer::UniformBuffer. + \note When \a buf is not null, it must have been created with + QRhiBuffer::UniformBuffer. + + \note \a buf can be null. It is valid to create a + QRhiShaderResourceBindings with unspecified resources, but such an object + cannot be used with QRhiCommandBuffer::setShaderResources(). It is however + suitable for creating pipelines. Such a pipeline must then always be used + together with another, layout compatible QRhiShaderResourceBindings with + resources present passed to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBuffer( int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size) @@ -2901,7 +2917,15 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBuffer( size of the bound region is specified by \a size. Like with non-dynamic offsets, \c{offset + size} cannot exceed the size of \a buf. - \note \a buf must have been created with QRhiBuffer::UniformBuffer. + \note When \a buf is not null, it must have been created with + QRhiBuffer::UniformBuffer. + + \note \a buf can be null. It is valid to create a + QRhiShaderResourceBindings with unspecified resources, but such an object + cannot be used with QRhiCommandBuffer::setShaderResources(). It is however + suitable for creating pipelines. Such a pipeline must then always be used + together with another, layout compatible QRhiShaderResourceBindings with + resources present passed to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBufferWithDynamicOffset( int binding, StageFlags stage, QRhiBuffer *buf, int size) @@ -2919,6 +2943,13 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBufferWithDynamicOff \note This function is equivalent to calling sampledTextures() with a \c count of 1. + \note \a tex and \a sampler can be null. It is valid to create a + QRhiShaderResourceBindings with unspecified resources, but such an object + cannot be used with QRhiCommandBuffer::setShaderResources(). It is however + suitable for creating pipelines. Such a pipeline must then always be used + together with another, layout compatible QRhiShaderResourceBindings with + resources present passed to QRhiCommandBuffer::setShaderResources(). + \sa sampledTextures() */ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTexture( @@ -2953,6 +2984,13 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTexture( advised to provide "dummy" samplers and textures if some array elements are not relevant (due to not being accessed in the shader). + \note \a texSamplers can be null. It is valid to create a + QRhiShaderResourceBindings with unspecified resources, but such an object + cannot be used with QRhiCommandBuffer::setShaderResources(). It is however + suitable for creating pipelines. Such a pipeline must then always be used + together with another, layout compatible QRhiShaderResourceBindings with + resources present passed to QRhiCommandBuffer::setShaderResources(). + \sa sampledTexture() */ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTextures( @@ -2964,8 +3002,12 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTextures( b.d.stage = stage; b.d.type = SampledTexture; b.d.u.stex.count = count; - for (int i = 0; i < count; ++i) - b.d.u.stex.texSamplers[i] = texSamplers[i]; + for (int i = 0; i < count; ++i) { + if (texSamplers) + b.d.u.stex.texSamplers[i] = texSamplers[i]; + else + b.d.u.stex.texSamplers[i] = {}; + } return b; } @@ -2975,7 +3017,15 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTextures( will have access to all layers of the specified \a level. (so if the texture is a cubemap, the shader must use imageCube instead of image2D) - \note \a tex must have been created with QRhiTexture::UsedWithLoadStore. + \note When \a tex is not null, it must have been created with + QRhiTexture::UsedWithLoadStore. + + \note \a tex can be null. It is valid to create a QRhiShaderResourceBindings + with unspecified resources, but such an object cannot be used with + QRhiCommandBuffer::setShaderResources(). It is however suitable for creating + pipelines. Such a pipeline must then always be used together with another, + layout compatible QRhiShaderResourceBindings with resources present passed + to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::imageLoad( int binding, StageFlags stage, QRhiTexture *tex, int level) @@ -2995,7 +3045,15 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::imageLoad( will have access to all layers of the specified \a level. (so if the texture is a cubemap, the shader must use imageCube instead of image2D) - \note \a tex must have been created with QRhiTexture::UsedWithLoadStore. + \note When \a tex is not null, it must have been created with + QRhiTexture::UsedWithLoadStore. + + \note \a tex can be null. It is valid to create a QRhiShaderResourceBindings + with unspecified resources, but such an object cannot be used with + QRhiCommandBuffer::setShaderResources(). It is however suitable for creating + pipelines. Such a pipeline must then always be used together with another, + layout compatible QRhiShaderResourceBindings with resources present passed + to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::imageStore( int binding, StageFlags stage, QRhiTexture *tex, int level) @@ -3011,7 +3069,15 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::imageStore( will have access to all layers of the specified \a level. (so if the texture is a cubemap, the shader must use imageCube instead of image2D) - \note \a tex must have been created with QRhiTexture::UsedWithLoadStore. + \note When \a tex is not null, it must have been created with + QRhiTexture::UsedWithLoadStore. + + \note \a tex can be null. It is valid to create a QRhiShaderResourceBindings + with unspecified resources, but such an object cannot be used with + QRhiCommandBuffer::setShaderResources(). It is however suitable for creating + pipelines. Such a pipeline must then always be used together with another, + layout compatible QRhiShaderResourceBindings with resources present passed + to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::imageLoadStore( int binding, StageFlags stage, QRhiTexture *tex, int level) @@ -3025,7 +3091,15 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::imageLoadStore( \return a shader resource binding for a read-only storage buffer with the given \a binding number and pipeline \a stage. - \note \a buf must have been created with QRhiBuffer::StorageBuffer. + \note When \a buf is not null, must have been created with + QRhiBuffer::StorageBuffer. + + \note \a buf can be null. It is valid to create a + QRhiShaderResourceBindings with unspecified resources, but such an object + cannot be used with QRhiCommandBuffer::setShaderResources(). It is however + suitable for creating pipelines. Such a pipeline must then always be used + together with another, layout compatible QRhiShaderResourceBindings with + resources present passed to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoad( int binding, StageFlags stage, QRhiBuffer *buf) @@ -3045,7 +3119,15 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoad( given \a binding number and pipeline \a stage. This overload binds a region only, as specified by \a offset and \a size. - \note \a buf must have been created with QRhiBuffer::StorageBuffer. + \note When \a buf is not null, must have been created with + QRhiBuffer::StorageBuffer. + + \note \a buf can be null. It is valid to create a + QRhiShaderResourceBindings with unspecified resources, but such an object + cannot be used with QRhiCommandBuffer::setShaderResources(). It is however + suitable for creating pipelines. Such a pipeline must then always be used + together with another, layout compatible QRhiShaderResourceBindings with + resources present passed to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoad( int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size) @@ -3061,7 +3143,15 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoad( \return a shader resource binding for a write-only storage buffer with the given \a binding number and pipeline \a stage. - \note \a buf must have been created with QRhiBuffer::StorageBuffer. + \note When \a buf is not null, must have been created with + QRhiBuffer::StorageBuffer. + + \note \a buf can be null. It is valid to create a + QRhiShaderResourceBindings with unspecified resources, but such an object + cannot be used with QRhiCommandBuffer::setShaderResources(). It is however + suitable for creating pipelines. Such a pipeline must then always be used + together with another, layout compatible QRhiShaderResourceBindings with + resources present passed to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferStore( int binding, StageFlags stage, QRhiBuffer *buf) @@ -3076,7 +3166,15 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferStore( given \a binding number and pipeline \a stage. This overload binds a region only, as specified by \a offset and \a size. - \note \a buf must have been created with QRhiBuffer::StorageBuffer. + \note When \a buf is not null, must have been created with + QRhiBuffer::StorageBuffer. + + \note \a buf can be null. It is valid to create a + QRhiShaderResourceBindings with unspecified resources, but such an object + cannot be used with QRhiCommandBuffer::setShaderResources(). It is however + suitable for creating pipelines. Such a pipeline must then always be used + together with another, layout compatible QRhiShaderResourceBindings with + resources present passed to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferStore( int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size) @@ -3092,7 +3190,15 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferStore( \return a shader resource binding for a read-write storage buffer with the given \a binding number and pipeline \a stage. - \note \a buf must have been created with QRhiBuffer::StorageBuffer. + \note When \a buf is not null, must have been created with + QRhiBuffer::StorageBuffer. + + \note \a buf can be null. It is valid to create a + QRhiShaderResourceBindings with unspecified resources, but such an object + cannot be used with QRhiCommandBuffer::setShaderResources(). It is however + suitable for creating pipelines. Such a pipeline must then always be used + together with another, layout compatible QRhiShaderResourceBindings with + resources present passed to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoadStore( int binding, StageFlags stage, QRhiBuffer *buf) @@ -3107,7 +3213,15 @@ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoadStore( given \a binding number and pipeline \a stage. This overload binds a region only, as specified by \a offset and \a size. - \note \a buf must have been created with QRhiBuffer::StorageBuffer. + \note When \a buf is not null, must have been created with + QRhiBuffer::StorageBuffer. + + \note \a buf can be null. It is valid to create a + QRhiShaderResourceBindings with unspecified resources, but such an object + cannot be used with QRhiCommandBuffer::setShaderResources(). It is however + suitable for creating pipelines. Such a pipeline must then always be used + together with another, layout compatible QRhiShaderResourceBindings with + resources present passed to QRhiCommandBuffer::setShaderResources(). */ QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoadStore( int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size) diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index ee7735c1bb..d9575e949b 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -3006,54 +3006,8 @@ bool QMetalShaderResourceBindings::create() boundResourceData.resize(sortedBindings.count()); - for (int i = 0, ie = sortedBindings.count(); i != ie; ++i) { - const QRhiShaderResourceBinding::Data *b = sortedBindings.at(i).data(); - QMetalShaderResourceBindings::BoundResourceData &bd(boundResourceData[i]); - switch (b->type) { - case QRhiShaderResourceBinding::UniformBuffer: - { - QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf); - bd.ubuf.id = bufD->m_id; - bd.ubuf.generation = bufD->generation; - } - break; - case QRhiShaderResourceBinding::SampledTexture: - { - const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; - bd.stex.count = data->count; - for (int elem = 0; elem < data->count; ++elem) { - QMetalTexture *texD = QRHI_RES(QMetalTexture, data->texSamplers[elem].tex); - QMetalSampler *samplerD = QRHI_RES(QMetalSampler, data->texSamplers[elem].sampler); - bd.stex.d[elem].texId = texD->m_id; - bd.stex.d[elem].texGeneration = texD->generation; - bd.stex.d[elem].samplerId = samplerD->m_id; - bd.stex.d[elem].samplerGeneration = samplerD->generation; - } - } - break; - case QRhiShaderResourceBinding::ImageLoad: - case QRhiShaderResourceBinding::ImageStore: - case QRhiShaderResourceBinding::ImageLoadStore: - { - QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); - bd.simage.id = texD->m_id; - bd.simage.generation = texD->generation; - } - break; - case QRhiShaderResourceBinding::BufferLoad: - case QRhiShaderResourceBinding::BufferStore: - case QRhiShaderResourceBinding::BufferLoadStore: - { - QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf); - bd.sbuf.id = bufD->m_id; - bd.sbuf.generation = bufD->generation; - } - break; - default: - Q_UNREACHABLE(); - break; - } - } + for (BoundResourceData &bd : boundResourceData) + memset(&bd, 0, sizeof(BoundResourceData)); generation += 1; return true; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index 4bf859a716..46a6146f20 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -2555,7 +2555,6 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i const bool updateAll = descSetIdx < 0; int frameSlot = updateAll ? 0 : descSetIdx; while (frameSlot < (updateAll ? QVK_FRAMES_IN_FLIGHT : descSetIdx + 1)) { - srbD->boundResourceData[frameSlot].resize(srbD->sortedBindings.count()); for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) { const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings.at(i).data(); QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[frameSlot][i]); @@ -6188,7 +6187,11 @@ bool QVkShaderResourceBindings::create() if (!rhiD->allocateDescriptorSet(&allocInfo, descSets, &poolIndex)) return false; - rhiD->updateShaderResourceBindings(this); + for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { + boundResourceData[i].resize(sortedBindings.count()); + for (BoundResourceData &bd : boundResourceData[i]) + memset(&bd, 0, sizeof(BoundResourceData)); + } lastActiveFrameSlot = -1; generation += 1; diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index f559b40cf6..ee14116e1c 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -91,6 +91,13 @@ private slots: void resourceUpdateBatchRGBATextureMip(); void invalidPipeline_data(); void invalidPipeline(); + void srbLayoutCompatibility_data(); + void srbLayoutCompatibility(); + void srbWithNoResource_data(); + void srbWithNoResource(); + void renderPassDescriptorCompatibility_data(); + void renderPassDescriptorCompatibility(); + void renderToTextureSimple_data(); void renderToTextureSimple(); void renderToTextureMip_data(); @@ -103,14 +110,12 @@ private slots: void renderToTextureArrayOfTexturedQuad(); void renderToTextureTexturedQuadAndUniformBuffer_data(); void renderToTextureTexturedQuadAndUniformBuffer(); + void renderToTextureDeferredSrb_data(); + void renderToTextureDeferredSrb(); void renderToWindowSimple_data(); void renderToWindowSimple(); void finishWithinSwapchainFrame_data(); void finishWithinSwapchainFrame(); - void srbLayoutCompatibility_data(); - void srbLayoutCompatibility(); - void renderPassDescriptorCompatibility_data(); - void renderPassDescriptorCompatibility(); private: void setWindowType(QWindow *window, QRhi::Implementation impl); @@ -2212,6 +2217,150 @@ void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer() QCOMPARE(result1.pixel(28, 178), empty); } +void tst_QRhi::renderToTextureDeferredSrb_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureDeferredSrb() +{ + 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"); + + QImage inputImage; + inputImage.load(QLatin1String(":/data/qt256.png")); + QVERIFY(!inputImage.isNull()); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 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()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float verticesUvs[] = { + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(verticesUvs))); + QVERIFY(vbuf->create()); + updates->uploadStaticBuffer(vbuf.data(), verticesUvs); + + QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(inputTexture->create()); + updates->uploadTexture(inputTexture.data(), inputImage); + + QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler->create()); + + QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4)); + QVERIFY(ubuf->create()); + + QMatrix4x4 matrix; + updates->updateDynamicBuffer(ubuf.data(), 0, 64, matrix.constData()); + float opacity = 0.5f; + updates->updateDynamicBuffer(ubuf.data(), 64, 4, &opacity); + + // this is the specific thing to test here: an srb with null resources + const QRhiShaderResourceBinding::StageFlags commonVisibility = QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; + QScopedPointer<QRhiShaderResourceBindings> layoutOnlySrb(rhi->newShaderResourceBindings()); + layoutOnlySrb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, nullptr), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, nullptr, nullptr) + }); + QVERIFY(layoutOnlySrb->create()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); + QShader vs = loadShader(":/data/textured.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/textured.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 4 * sizeof(float) } }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } + }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(layoutOnlySrb.data()); // no resources needed yet + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->create()); + + // another, layout compatible, srb with the actual resources + QScopedPointer<QRhiShaderResourceBindings> layoutCompatibleSrbWithResources(rhi->newShaderResourceBindings()); + layoutCompatibleSrbWithResources->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data()), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data()) + }); + QVERIFY(layoutCompatibleSrbWithResources->create()); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(pipeline.data()); + cb->setShaderResources(layoutCompatibleSrbWithResources.data()); // here we must use the srb referencing the resources + cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(4); + + 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); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + + QVERIFY(!result.isNull()); + + if (impl == QRhi::Null) + return; + + if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) + result = std::move(result).mirrored(); + + // opacity 0.5 (premultiplied) + static const auto checkSemiWhite = [](const QRgb &c) { + QRgb semiWhite127 = qPremultiply(qRgba(255, 255, 255, 127)); + QRgb semiWhite128 = qPremultiply(qRgba(255, 255, 255, 128)); + return c == semiWhite127 || c == semiWhite128; + }; + QVERIFY(checkSemiWhite(result.pixel(79, 77))); + QVERIFY(checkSemiWhite(result.pixel(124, 81))); + QVERIFY(checkSemiWhite(result.pixel(128, 149))); + QVERIFY(checkSemiWhite(result.pixel(120, 189))); + QVERIFY(checkSemiWhite(result.pixel(116, 185))); + QVERIFY(checkSemiWhite(result.pixel(191, 172))); + + QRgb empty = qRgba(0, 0, 0, 0); + QCOMPARE(result.pixel(11, 45), empty); + QCOMPARE(result.pixel(246, 202), empty); + QCOMPARE(result.pixel(130, 18), empty); + QCOMPARE(result.pixel(4, 227), empty); +} + void tst_QRhi::setWindowType(QWindow *window, QRhi::Implementation impl) { switch (impl) { @@ -2630,6 +2779,48 @@ void tst_QRhi::srbLayoutCompatibility() } } +void tst_QRhi::srbWithNoResource_data() +{ + rhiTestData(); +} + +void tst_QRhi::srbWithNoResource() +{ + 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 srb"); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512))); + QVERIFY(texture->create()); + QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler->create()); + QScopedPointer<QRhiBuffer> buf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 1024)); + QVERIFY(buf->create()); + + { + QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings()); + srb1->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, nullptr), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, nullptr, nullptr) + }); + QVERIFY(srb1->create()); + + QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings()); + srb2->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, buf.data()), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture.data(), sampler.data()) + }); + QVERIFY(srb2->create()); + + QVERIFY(srb1->isLayoutCompatible(srb2.data())); + QVERIFY(srb2->isLayoutCompatible(srb1.data())); + } +} + void tst_QRhi::renderPassDescriptorCompatibility_data() { rhiTestData(); |