rhi: Allow testing QRhiRenderPassDescriptors for compatibility

For Metal and Vulkan this needs actual work because that's where
the concept of renderpass descriptors is relevant. GL and D3D can
just return true always.

The big benefit of this is that Qt Quick can now compare renderpass
descriptors via isCompatible() for its pipeline cache (similarly to
how it is already using isLayoutCompatible() for srbs), and so
renderpass descriptors for layers (Item.layer, ShaderEffect) will
typically be compatible and so can pick up pipelines created by other
layers from the cache.

Also add autotests for shader resource binding and renderpass descriptor
compatibility.

Task-number: QTBUG-80318
Change-Id: I0008bc51c4ee13b0113d2c8caf799e1257f18a18
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
bb10
Laszlo Agocs 2019-11-26 12:43:56 +01:00
parent 48d2ffd397
commit 64760504e7
13 changed files with 518 additions and 67 deletions

View File

@ -2318,6 +2318,24 @@ QRhiResource::Type QRhiRenderPassDescriptor::resourceType() const
return RenderPassDescriptor;
}
/*!
\fn bool QRhiRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const;
\return true if the \a other QRhiRenderPassDescriptor is compatible with
this one, meaning \c this and \a other can be used interchangebly in
QRhiGraphicsPipeline::setRenderPassDescriptor().
The concept of the compatibility of renderpass descriptors is similar to
the \l{QRhiShaderResourceBindings::isLayoutCompatible}{layout
compatibility} of QRhiShaderResourceBindings instances. They allow better
reuse of QRhiGraphicsPipeline instances: for example, a
QRhiGraphicsPipeline instance cache is expected to use these functions to
look for a matching pipeline, instead of just comparing pointers, thus
allowing a different QRhiRenderPassDescriptor and
QRhiShaderResourceBindings to be used in combination with the pipeline, as
long as they are compatible.
*/
/*!
\return a pointer to a backend-specific QRhiNativeHandles subclass, such as
QRhiVulkanRenderPassNativeHandles. The returned value is null when exposing

View File

@ -911,6 +911,7 @@ class Q_GUI_EXPORT QRhiRenderPassDescriptor : public QRhiResource
public:
QRhiResource::Type resourceType() const override;
virtual bool isCompatible(const QRhiRenderPassDescriptor *other) const = 0;
virtual const QRhiNativeHandles *nativeHandles();
protected:

View File

@ -2940,6 +2940,12 @@ void QD3D11RenderPassDescriptor::release()
// nothing to do here
}
bool QD3D11RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
{
Q_UNUSED(other);
return true;
}
QD3D11ReferenceRenderTarget::QD3D11ReferenceRenderTarget(QRhiImplementation *rhi)
: QRhiRenderTarget(rhi),
d(rhi)

View File

@ -136,6 +136,7 @@ struct QD3D11RenderPassDescriptor : public QRhiRenderPassDescriptor
QD3D11RenderPassDescriptor(QRhiImplementation *rhi);
~QD3D11RenderPassDescriptor();
void release() override;
bool isCompatible(const QRhiRenderPassDescriptor *other) const override;
};
struct QD3D11RenderTargetData

View File

@ -3455,6 +3455,12 @@ void QGles2RenderPassDescriptor::release()
// nothing to do here
}
bool QGles2RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
{
Q_UNUSED(other);
return true;
}
QGles2ReferenceRenderTarget::QGles2ReferenceRenderTarget(QRhiImplementation *rhi)
: QRhiRenderTarget(rhi),
d(rhi)

View File

@ -185,6 +185,7 @@ struct QGles2RenderPassDescriptor : public QRhiRenderPassDescriptor
QGles2RenderPassDescriptor(QRhiImplementation *rhi);
~QGles2RenderPassDescriptor();
void release() override;
bool isCompatible(const QRhiRenderPassDescriptor *other) const override;
};
struct QGles2RenderTargetData

View File

@ -2658,6 +2658,32 @@ void QMetalRenderPassDescriptor::release()
// nothing to do here
}
bool QMetalRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
{
if (!other)
return false;
const QMetalRenderPassDescriptor *o = QRHI_RES(const QMetalRenderPassDescriptor, other);
if (colorAttachmentCount != o->colorAttachmentCount)
return false;
if (hasDepthStencil != o->hasDepthStencil)
return false;
for (int i = 0; i < colorAttachmentCount; ++i) {
if (colorFormat[i] != o->colorFormat[i])
return false;
}
if (hasDepthStencil) {
if (dsFormat != o->dsFormat)
return false;
}
return true;
}
QMetalReferenceRenderTarget::QMetalReferenceRenderTarget(QRhiImplementation *rhi)
: QRhiRenderTarget(rhi),
d(new QMetalRenderTargetData)

View File

@ -138,6 +138,7 @@ struct QMetalRenderPassDescriptor : public QRhiRenderPassDescriptor
QMetalRenderPassDescriptor(QRhiImplementation *rhi);
~QMetalRenderPassDescriptor();
void release() override;
bool isCompatible(const QRhiRenderPassDescriptor *other) const override;
// there is no MTLRenderPassDescriptor here as one will be created for each pass in beginPass()

View File

@ -690,6 +690,12 @@ void QNullRenderPassDescriptor::release()
{
}
bool QNullRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
{
Q_UNUSED(other);
return true;
}
QNullReferenceRenderTarget::QNullReferenceRenderTarget(QRhiImplementation *rhi)
: QRhiRenderTarget(rhi),
d(rhi)

View File

@ -101,6 +101,7 @@ struct QNullRenderPassDescriptor : public QRhiRenderPassDescriptor
QNullRenderPassDescriptor(QRhiImplementation *rhi);
~QNullRenderPassDescriptor();
void release() override;
bool isCompatible(const QRhiRenderPassDescriptor *other) const override;
};
struct QNullRenderTargetData

View File

@ -1032,54 +1032,62 @@ VkFormat QRhiVulkan::optimalDepthStencilFormat()
return optimalDsFormat;
}
bool QRhiVulkan::createDefaultRenderPass(VkRenderPass *rp, bool hasDepthStencil, VkSampleCountFlagBits samples, VkFormat colorFormat)
bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasDepthStencil, VkSampleCountFlagBits samples, VkFormat colorFormat)
{
VkAttachmentDescription attDesc[3];
memset(attDesc, 0, sizeof(attDesc));
// attachment list layout is color (1), ds (0-1), resolve (0-1)
attDesc[0].format = colorFormat;
attDesc[0].samples = samples;
attDesc[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attDesc[0].storeOp = samples > VK_SAMPLE_COUNT_1_BIT ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
attDesc[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attDesc[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attDesc[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attDesc[0].finalLayout = samples > VK_SAMPLE_COUNT_1_BIT ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentDescription attDesc;
memset(&attDesc, 0, sizeof(attDesc));
attDesc.format = colorFormat;
attDesc.samples = samples;
attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attDesc.storeOp = samples > VK_SAMPLE_COUNT_1_BIT ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attDesc.finalLayout = samples > VK_SAMPLE_COUNT_1_BIT ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
rpD->attDescs.append(attDesc);
// clear on load + no store + lazy alloc + transient image should play
// nicely with tiled GPUs (no physical backing necessary for ds buffer)
attDesc[1].format = optimalDepthStencilFormat();
attDesc[1].samples = samples;
attDesc[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attDesc[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attDesc[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attDesc[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attDesc[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attDesc[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
rpD->colorRefs.append({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
if (samples > VK_SAMPLE_COUNT_1_BIT) {
attDesc[2].format = colorFormat;
attDesc[2].samples = VK_SAMPLE_COUNT_1_BIT;
attDesc[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attDesc[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attDesc[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attDesc[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attDesc[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attDesc[2].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
if (hasDepthStencil) {
// clear on load + no store + lazy alloc + transient image should play
// nicely with tiled GPUs (no physical backing necessary for ds buffer)
memset(&attDesc, 0, sizeof(attDesc));
attDesc.format = optimalDepthStencilFormat();
attDesc.samples = samples;
attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attDesc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
rpD->attDescs.append(attDesc);
rpD->dsRef = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
}
VkAttachmentReference colorRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
VkAttachmentReference dsRef = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
VkAttachmentReference resolveRef = { 2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
if (samples > VK_SAMPLE_COUNT_1_BIT) {
memset(&attDesc, 0, sizeof(attDesc));
attDesc.format = colorFormat;
attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attDesc.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
rpD->attDescs.append(attDesc);
rpD->resolveRefs.append({ 2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
}
VkSubpassDescription subpassDesc;
memset(&subpassDesc, 0, sizeof(subpassDesc));
subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDesc.colorAttachmentCount = 1;
subpassDesc.pColorAttachments = &colorRef;
subpassDesc.pDepthStencilAttachment = hasDepthStencil ? &dsRef : nullptr;
subpassDesc.pColorAttachments = rpD->colorRefs.constData();
subpassDesc.pDepthStencilAttachment = hasDepthStencil ? &rpD->dsRef : nullptr;
// Replace the first implicit dep (TOP_OF_PIPE / ALL_COMMANDS) with our own.
VkSubpassDependency subpassDep;
@ -1095,7 +1103,7 @@ bool QRhiVulkan::createDefaultRenderPass(VkRenderPass *rp, bool hasDepthStencil,
memset(&rpInfo, 0, sizeof(rpInfo));
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
rpInfo.attachmentCount = 1;
rpInfo.pAttachments = attDesc;
rpInfo.pAttachments = rpD->attDescs.constData();
rpInfo.subpassCount = 1;
rpInfo.pSubpasses = &subpassDesc;
rpInfo.dependencyCount = 1;
@ -1106,19 +1114,21 @@ bool QRhiVulkan::createDefaultRenderPass(VkRenderPass *rp, bool hasDepthStencil,
if (samples > VK_SAMPLE_COUNT_1_BIT) {
rpInfo.attachmentCount += 1;
subpassDesc.pResolveAttachments = &resolveRef;
subpassDesc.pResolveAttachments = rpD->resolveRefs.constData();
}
VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, rp);
VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp);
if (err != VK_SUCCESS) {
qWarning("Failed to create renderpass: %d", err);
return false;
}
rpD->hasDepthStencil = hasDepthStencil;
return true;
}
bool QRhiVulkan::createOffscreenRenderPass(VkRenderPass *rp,
bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
const QRhiColorAttachment *firstColorAttachment,
const QRhiColorAttachment *lastColorAttachment,
bool preserveColor,
@ -1126,10 +1136,6 @@ bool QRhiVulkan::createOffscreenRenderPass(VkRenderPass *rp,
QRhiRenderBuffer *depthStencilBuffer,
QRhiTexture *depthTexture)
{
QVarLengthArray<VkAttachmentDescription, 8> attDescs;
QVarLengthArray<VkAttachmentReference, 8> colorRefs;
QVarLengthArray<VkAttachmentReference, 8> resolveRefs;
// attachment list layout is color (0-8), ds (0-1), resolve (0-8)
for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) {
@ -1150,14 +1156,14 @@ bool QRhiVulkan::createOffscreenRenderPass(VkRenderPass *rp,
// this has to interact correctly with activateTextureRenderTarget(), hence leaving in COLOR_ATT
attDesc.initialLayout = preserveColor ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attDescs.append(attDesc);
rpD->attDescs.append(attDesc);
const VkAttachmentReference ref = { uint32_t(attDescs.count() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
colorRefs.append(ref);
const VkAttachmentReference ref = { uint32_t(rpD->attDescs.count() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
rpD->colorRefs.append(ref);
}
const bool hasDepthStencil = depthStencilBuffer || depthTexture;
if (hasDepthStencil) {
rpD->hasDepthStencil = depthStencilBuffer || depthTexture;
if (rpD->hasDepthStencil) {
const VkFormat dsFormat = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->vkformat
: QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->vkformat;
const VkSampleCountFlagBits samples = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->samples
@ -1174,9 +1180,9 @@ bool QRhiVulkan::createOffscreenRenderPass(VkRenderPass *rp,
attDesc.stencilStoreOp = storeOp;
attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attDescs.append(attDesc);
rpD->attDescs.append(attDesc);
}
VkAttachmentReference dsRef = { uint32_t(attDescs.count() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
rpD->dsRef = { uint32_t(rpD->attDescs.count() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) {
if (it->resolveTexture()) {
@ -1194,37 +1200,37 @@ bool QRhiVulkan::createOffscreenRenderPass(VkRenderPass *rp,
attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attDescs.append(attDesc);
rpD->attDescs.append(attDesc);
const VkAttachmentReference ref = { uint32_t(attDescs.count() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
resolveRefs.append(ref);
const VkAttachmentReference ref = { uint32_t(rpD->attDescs.count() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
rpD->resolveRefs.append(ref);
} else {
const VkAttachmentReference ref = { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
resolveRefs.append(ref);
rpD->resolveRefs.append(ref);
}
}
VkSubpassDescription subpassDesc;
memset(&subpassDesc, 0, sizeof(subpassDesc));
subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDesc.colorAttachmentCount = uint32_t(colorRefs.count());
Q_ASSERT(colorRefs.count() == resolveRefs.count());
subpassDesc.pColorAttachments = !colorRefs.isEmpty() ? colorRefs.constData() : nullptr;
subpassDesc.pDepthStencilAttachment = hasDepthStencil ? &dsRef : nullptr;
subpassDesc.pResolveAttachments = !resolveRefs.isEmpty() ? resolveRefs.constData() : nullptr;
subpassDesc.colorAttachmentCount = uint32_t(rpD->colorRefs.count());
Q_ASSERT(rpD->colorRefs.count() == rpD->resolveRefs.count());
subpassDesc.pColorAttachments = !rpD->colorRefs.isEmpty() ? rpD->colorRefs.constData() : nullptr;
subpassDesc.pDepthStencilAttachment = rpD->hasDepthStencil ? &rpD->dsRef : nullptr;
subpassDesc.pResolveAttachments = !rpD->resolveRefs.isEmpty() ? rpD->resolveRefs.constData() : nullptr;
VkRenderPassCreateInfo rpInfo;
memset(&rpInfo, 0, sizeof(rpInfo));
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
rpInfo.attachmentCount = uint32_t(attDescs.count());
rpInfo.pAttachments = attDescs.constData();
rpInfo.attachmentCount = uint32_t(rpD->attDescs.count());
rpInfo.pAttachments = rpD->attDescs.constData();
rpInfo.subpassCount = 1;
rpInfo.pSubpasses = &subpassDesc;
// don't yet know the correct initial/final access and stage stuff for the
// implicit deps at this point, so leave it to the resource tracking to
// generate barriers
VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, rp);
VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp);
if (err != VK_SUCCESS) {
qWarning("Failed to create renderpass: %d", err);
return false;
@ -5501,6 +5507,61 @@ void QVkRenderPassDescriptor::release()
rhiD->unregisterResource(this);
}
static inline bool attachmentDescriptionEquals(const VkAttachmentDescription &a, const VkAttachmentDescription &b)
{
return a.format == b.format
&& a.samples == b.samples
&& a.loadOp == b.loadOp
&& a.storeOp == b.storeOp
&& a.stencilLoadOp == b.stencilLoadOp
&& a.stencilStoreOp == b.stencilStoreOp
&& a.initialLayout == b.initialLayout
&& a.finalLayout == b.finalLayout;
}
bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
{
if (!other)
return false;
const QVkRenderPassDescriptor *o = QRHI_RES(const QVkRenderPassDescriptor, other);
if (attDescs.count() != o->attDescs.count())
return false;
if (colorRefs.count() != o->colorRefs.count())
return false;
if (resolveRefs.count() != o->resolveRefs.count())
return false;
if (hasDepthStencil != o->hasDepthStencil)
return false;
for (int i = 0, ie = colorRefs.count(); i != ie; ++i) {
const uint32_t attIdx = colorRefs[i].attachment;
if (attIdx != o->colorRefs[i].attachment)
return false;
if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx]))
return false;
}
if (hasDepthStencil) {
const uint32_t attIdx = dsRef.attachment;
if (attIdx != o->dsRef.attachment)
return false;
if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx]))
return false;
}
for (int i = 0, ie = resolveRefs.count(); i != ie; ++i) {
const uint32_t attIdx = resolveRefs[i].attachment;
if (attIdx != o->resolveRefs[i].attachment)
return false;
if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx]))
return false;
}
return true;
}
const QRhiNativeHandles *QVkRenderPassDescriptor::nativeHandles()
{
nativeHandlesStruct.renderPass = rp;
@ -5584,7 +5645,7 @@ QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescrip
QRHI_RES_RHI(QRhiVulkan);
QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi);
if (!rhiD->createOffscreenRenderPass(&rp->rp,
if (!rhiD->createOffscreenRenderPass(rp,
m_desc.cbeginColorAttachments(),
m_desc.cendColorAttachments(),
m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents),
@ -6285,7 +6346,7 @@ QRhiRenderPassDescriptor *QVkSwapChain::newCompatibleRenderPassDescriptor()
QRHI_RES_RHI(QRhiVulkan);
QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi);
if (!rhiD->createDefaultRenderPass(&rp->rp,
if (!rhiD->createDefaultRenderPass(rp,
m_depthStencil != nullptr,
samples,
colorFormat))

View File

@ -171,10 +171,16 @@ struct QVkRenderPassDescriptor : public QRhiRenderPassDescriptor
QVkRenderPassDescriptor(QRhiImplementation *rhi);
~QVkRenderPassDescriptor();
void release() override;
bool isCompatible(const QRhiRenderPassDescriptor *other) const override;
const QRhiNativeHandles *nativeHandles() override;
VkRenderPass rp = VK_NULL_HANDLE;
bool ownsRp = false;
QVarLengthArray<VkAttachmentDescription, 8> attDescs;
QVarLengthArray<VkAttachmentReference, 8> colorRefs;
QVarLengthArray<VkAttachmentReference, 8> resolveRefs;
bool hasDepthStencil = false;
VkAttachmentReference dsRef;
QRhiVulkanRenderPassNativeHandles nativeHandlesStruct;
int lastActiveFrameSlot = -1;
};
@ -727,11 +733,11 @@ public:
VkFormat optimalDepthStencilFormat();
VkSampleCountFlagBits effectiveSampleCount(int sampleCount);
bool createDefaultRenderPass(VkRenderPass *rp,
bool createDefaultRenderPass(QVkRenderPassDescriptor *rpD,
bool hasDepthStencil,
VkSampleCountFlagBits samples,
VkFormat colorFormat);
bool createOffscreenRenderPass(VkRenderPass *rp,
bool createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
const QRhiColorAttachment *firstColorAttachment,
const QRhiColorAttachment *lastColorAttachment,
bool preserveColor,

View File

@ -91,6 +91,10 @@ private slots:
void renderToTextureTexturedQuadAndUniformBuffer();
void renderToWindowSimple_data();
void renderToWindowSimple();
void srbLayoutCompatibility_data();
void srbLayoutCompatibility();
void renderPassDescriptorCompatibility_data();
void renderPassDescriptorCompatibility();
private:
struct {
@ -1728,5 +1732,318 @@ void tst_QRhi::renderToWindowSimple()
QVERIFY(redCount < blueCount);
}
void tst_QRhi::srbLayoutCompatibility_data()
{
rhiTestData();
}
void tst_QRhi::srbLayoutCompatibility()
{
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 texture resource updates");
QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512)));
QVERIFY(texture->build());
QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
QVERIFY(sampler->build());
QScopedPointer<QRhiSampler> otherSampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
QVERIFY(otherSampler->build());
QScopedPointer<QRhiBuffer> buf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 1024));
QVERIFY(buf->build());
QScopedPointer<QRhiBuffer> otherBuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 256));
QVERIFY(otherBuf->build());
// empty (compatible)
{
QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings());
QVERIFY(srb1->build());
QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings());
QVERIFY(srb2->build());
QVERIFY(srb1->isLayoutCompatible(srb2.data()));
QVERIFY(srb2->isLayoutCompatible(srb1.data()));
}
// different count (not compatible)
{
QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings());
QVERIFY(srb1->build());
QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings());
srb2->setBindings({
QRhiShaderResourceBinding::sampledTexture(0, QRhiShaderResourceBinding::FragmentStage, texture.data(), sampler.data())
});
QVERIFY(srb2->build());
QVERIFY(!srb1->isLayoutCompatible(srb2.data()));
QVERIFY(!srb2->isLayoutCompatible(srb1.data()));
}
// full match (compatible)
{
QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings());
srb1->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, buf.data()),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture.data(), sampler.data())
});
QVERIFY(srb1->build());
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->build());
QVERIFY(srb1->isLayoutCompatible(srb2.data()));
QVERIFY(srb2->isLayoutCompatible(srb1.data()));
}
// different visibility (not compatible)
{
QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings());
srb1->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, buf.data()),
});
QVERIFY(srb1->build());
QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings());
srb2->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, buf.data()),
});
QVERIFY(srb2->build());
QVERIFY(!srb1->isLayoutCompatible(srb2.data()));
QVERIFY(!srb2->isLayoutCompatible(srb1.data()));
}
// different binding points (not compatible)
{
QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings());
srb1->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, buf.data()),
});
QVERIFY(srb1->build());
QScopedPointer<QRhiShaderResourceBindings> srb2(rhi->newShaderResourceBindings());
srb2->setBindings({
QRhiShaderResourceBinding::uniformBuffer(1, QRhiShaderResourceBinding::VertexStage, buf.data()),
});
QVERIFY(srb2->build());
QVERIFY(!srb1->isLayoutCompatible(srb2.data()));
QVERIFY(!srb2->isLayoutCompatible(srb1.data()));
}
// different buffer region offset and size (compatible)
{
QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings());
srb1->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, buf.data(), rhi->ubufAligned(1), 128),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture.data(), sampler.data())
});
QVERIFY(srb1->build());
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->build());
QVERIFY(srb1->isLayoutCompatible(srb2.data()));
QVERIFY(srb2->isLayoutCompatible(srb1.data()));
}
// different resources (compatible)
{
QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings());
srb1->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, otherBuf.data()),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture.data(), otherSampler.data())
});
QVERIFY(srb1->build());
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->build());
QVERIFY(srb1->isLayoutCompatible(srb2.data()));
QVERIFY(srb2->isLayoutCompatible(srb1.data()));
}
}
void tst_QRhi::renderPassDescriptorCompatibility_data()
{
rhiTestData();
}
void tst_QRhi::renderPassDescriptorCompatibility()
{
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 texture resource updates");
// Note that checking compatibility is only relevant with backends where
// there is a concept of renderpass descriptions (Vulkan, and partially
// Metal). It is perfectly fine for isCompatible() to always return true
// when that is not the case (D3D11, OpenGL). Hence the 'if (Vulkan or
// Metal)' for all the negative tests. Also note "partial" for Metal:
// resolve textures for examples have no effect on compatibility with Metal.
// tex and tex2 have the same format
QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget));
QVERIFY(tex->build());
QScopedPointer<QRhiTexture> tex2(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget));
QVERIFY(tex2->build());
QScopedPointer<QRhiRenderBuffer> ds(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, QSize(512, 512)));
QVERIFY(ds->build());
// two texture rendertargets with tex and tex2 as color0 (compatible)
{
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ tex.data() }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rpDesc.data());
QVERIFY(rt->build());
QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget({ tex2.data() }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor());
rt2->setRenderPassDescriptor(rpDesc2.data());
QVERIFY(rt2->build());
QVERIFY(rpDesc->isCompatible(rpDesc2.data()));
QVERIFY(rpDesc2->isCompatible(rpDesc.data()));
}
// two texture rendertargets with tex and tex2 as color0, and a depth-stencil attachment as well (compatible)
{
QRhiTextureRenderTargetDescription desc({ tex.data() }, ds.data());
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(desc));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rpDesc.data());
QVERIFY(rt->build());
QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget(desc));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor());
rt2->setRenderPassDescriptor(rpDesc2.data());
QVERIFY(rt2->build());
QVERIFY(rpDesc->isCompatible(rpDesc2.data()));
QVERIFY(rpDesc2->isCompatible(rpDesc.data()));
}
// now one of them does not have the ds attachment (not compatible)
{
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ { tex.data() }, ds.data() }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rpDesc.data());
QVERIFY(rt->build());
QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget({ tex.data() }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor());
rt2->setRenderPassDescriptor(rpDesc2.data());
QVERIFY(rt2->build());
if (impl == QRhi::Vulkan || impl == QRhi::Metal) {
QVERIFY(!rpDesc->isCompatible(rpDesc2.data()));
QVERIFY(!rpDesc2->isCompatible(rpDesc.data()));
}
}
if (rhi->isFeatureSupported(QRhi::MultisampleRenderBuffer)) {
// resolve attachments (compatible)
{
QScopedPointer<QRhiRenderBuffer> msaaRenderBuffer(rhi->newRenderBuffer(QRhiRenderBuffer::Color, QSize(512, 512), 4));
QVERIFY(msaaRenderBuffer->build());
QScopedPointer<QRhiRenderBuffer> msaaRenderBuffer2(rhi->newRenderBuffer(QRhiRenderBuffer::Color, QSize(512, 512), 4));
QVERIFY(msaaRenderBuffer2->build());
QRhiColorAttachment colorAtt(msaaRenderBuffer.data()); // color0, multisample
colorAtt.setResolveTexture(tex.data()); // resolved into a non-msaa texture
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ colorAtt }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rpDesc.data());
QVERIFY(rt->build());
QRhiColorAttachment colorAtt2(msaaRenderBuffer2.data()); // color0, multisample
colorAtt2.setResolveTexture(tex2.data()); // resolved into a non-msaa texture
QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget({ colorAtt2 }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor());
rt2->setRenderPassDescriptor(rpDesc2.data());
QVERIFY(rt2->build());
QVERIFY(rpDesc->isCompatible(rpDesc2.data()));
QVERIFY(rpDesc2->isCompatible(rpDesc.data()));
}
// missing resolve for one of them (not compatible)
{
QScopedPointer<QRhiRenderBuffer> msaaRenderBuffer(rhi->newRenderBuffer(QRhiRenderBuffer::Color, QSize(512, 512), 4));
QVERIFY(msaaRenderBuffer->build());
QScopedPointer<QRhiRenderBuffer> msaaRenderBuffer2(rhi->newRenderBuffer(QRhiRenderBuffer::Color, QSize(512, 512), 4));
QVERIFY(msaaRenderBuffer2->build());
QRhiColorAttachment colorAtt(msaaRenderBuffer.data()); // color0, multisample
colorAtt.setResolveTexture(tex.data()); // resolved into a non-msaa texture
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ colorAtt }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rpDesc.data());
QVERIFY(rt->build());
QRhiColorAttachment colorAtt2(msaaRenderBuffer2.data()); // color0, multisample
QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget({ colorAtt2 }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor());
rt2->setRenderPassDescriptor(rpDesc2.data());
QVERIFY(rt2->build());
if (impl == QRhi::Vulkan) { // no Metal here
QVERIFY(!rpDesc->isCompatible(rpDesc2.data()));
QVERIFY(!rpDesc2->isCompatible(rpDesc.data()));
}
}
} else {
qDebug("Skipping multisample renderbuffer dependent tests");
}
if (rhi->isTextureFormatSupported(QRhiTexture::RGBA32F)) {
QScopedPointer<QRhiTexture> tex3(rhi->newTexture(QRhiTexture::RGBA32F, QSize(512, 512), 1, QRhiTexture::RenderTarget));
QVERIFY(tex3->build());
// different texture formats (not compatible)
{
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ tex.data() }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rpDesc.data());
QVERIFY(rt->build());
QScopedPointer<QRhiTextureRenderTarget> rt2(rhi->newTextureRenderTarget({ tex3.data() }));
QScopedPointer<QRhiRenderPassDescriptor> rpDesc2(rt2->newCompatibleRenderPassDescriptor());
rt2->setRenderPassDescriptor(rpDesc2.data());
QVERIFY(rt2->build());
if (impl == QRhi::Vulkan || impl == QRhi::Metal) {
QVERIFY(!rpDesc->isCompatible(rpDesc2.data()));
QVERIFY(!rpDesc2->isCompatible(rpDesc.data()));
}
}
} else {
qDebug("Skipping texture format dependent tests");
}
}
#include <tst_qrhi.moc>
QTEST_MAIN(tst_QRhi)