rhi: Auto-rebuild rt by tracking attachment id and generation

Unlike the shader resource binding lists that automatically recognize
in setShaderResources() when a referenced QRhiResource has been rebuilt
in the meantime (create() was called i.e. there may be completely
different native objects underneath), QRhiTextureRenderTarget has no
such thing. This leads to an asymmetric API and requires also rebuilding
the rt whenever an attachment is rebuilt:

rt = rhi->newTextureRenderTarget({ { texture } })
rt->create()
cb->beginPass(rt, ...)
texture->setPixelSize(...)
texture->create()
rt->create() // this should not be needed
cb->beginPass(rt, ...)

Avoid having to do that second rt->create().

Change-Id: If14eaa7aac3530950498bbdf834324d0741a7c4d
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2021-12-08 19:40:25 +01:00
parent 48b75def5d
commit bc4570ed24
12 changed files with 207 additions and 2 deletions

View File

@ -330,9 +330,22 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
ubuf->setSize(512);
ubuf->create(); // same as ubuf->destroy(); ubuf->create();
// that's it, srb needs no changes whatsoever
// That's it, srb needs no changes whatsoever, any references in it to
// ubuf stay valid. When it comes to internal details, such as that
// ubuf may now be backed by a completely different native buffer
// resource, that is is recognized and handled automatically by the
// next setShaderResources().
\endcode
QRhiTextureRenderTarget offers the same contract: calling
QRhiCommandBuffer::beginPass() is safe even when one of the render target's
associated textures or renderbuffers has been rebuilt (by calling \c
create() on it) since the creation of the render target object. This allows
the application to resize a texture by setting a new pixel size on the
QRhiTexture and calling create(), thus creating a whole new native texture
resource underneath, without having to update the QRhiTextureRenderTarget
as that will be done implicitly in beginPass().
\section3 Pooled objects
In addition to resources, there are pooled objects as well, such as,
@ -5669,6 +5682,25 @@ void QRhiCommandBuffer::resourceUpdate(QRhiResourceUpdateBatch *resourceUpdates)
called inside a pass. Also, with the exception of setGraphicsPipeline(),
they expect to have a pipeline set already on the command buffer.
Unspecified issues may arise otherwise, depending on the backend.
If \a rt is a QRhiTextureRenderTarget, beginPass() performs a check to see
if the texture and renderbuffer objects referenced from the render target
are up-to-date. This is similar to what setShaderResources() does for
QRhiShaderResourceBindings. If any of the attachments had been rebuilt
since QRhiTextureRenderTarget::create(), an implicit call to create() is
made on \a rt. Therefore, if \a rt has a QRhiTexture color attachment \c
texture, and one needs to make the texture a different size, the following
is then valid:
\badcode
rt = rhi->newTextureRenderTarget({ { texture } });
rt->create();
...
texture->setPixelSize(new_size);
texture->create();
cb->beginPass(rt, ...); // this is ok, no explicit rt->create() is required before
\endcode
\sa endPass()
*/
void QRhiCommandBuffer::beginPass(QRhiRenderTarget *rt,
const QColor &colorClearValue,
@ -5684,6 +5716,8 @@ void QRhiCommandBuffer::beginPass(QRhiRenderTarget *rt,
\a resourceUpdates, when not null, specifies a resource update batch that
is to be committed and then released.
\sa beginPass()
*/
void QRhiCommandBuffer::endPass(QRhiResourceUpdateBatch *resourceUpdates)
{

View File

@ -706,6 +706,83 @@ private:
int p = 0;
};
struct QRhiRenderTargetAttachmentTracker
{
struct ResId { quint64 id; uint generation; };
using ResIdList = QVarLengthArray<ResId, 8 * 2 + 1>; // color, resolve, ds
template<typename TexType, typename RenderBufferType>
static void updateResIdList(const QRhiTextureRenderTargetDescription &desc, ResIdList *dst);
template<typename TexType, typename RenderBufferType>
static bool isUpToDate(const QRhiTextureRenderTargetDescription &desc, const ResIdList &currentResIdList);
};
inline bool operator==(const QRhiRenderTargetAttachmentTracker::ResId &a, const QRhiRenderTargetAttachmentTracker::ResId &b)
{
return a.id == b.id && a.generation == b.generation;
}
inline bool operator!=(const QRhiRenderTargetAttachmentTracker::ResId &a, const QRhiRenderTargetAttachmentTracker::ResId &b)
{
return !(a == b);
}
template<typename TexType, typename RenderBufferType>
void QRhiRenderTargetAttachmentTracker::updateResIdList(const QRhiTextureRenderTargetDescription &desc, ResIdList *dst)
{
const quintptr colorAttCount = desc.cendColorAttachments() - desc.cbeginColorAttachments();
const bool hasDepthStencil = desc.depthStencilBuffer() || desc.depthTexture();
dst->resize(colorAttCount * 2 + (hasDepthStencil ? 1 : 0));
int n = 0;
for (auto it = desc.cbeginColorAttachments(), itEnd = desc.cendColorAttachments(); it != itEnd; ++it, ++n) {
const QRhiColorAttachment &colorAtt(*it);
if (colorAtt.texture()) {
TexType *texD = QRHI_RES(TexType, colorAtt.texture());
(*dst)[n] = { texD->globalResourceId(), texD->generation };
} else if (colorAtt.renderBuffer()) {
RenderBufferType *rbD = QRHI_RES(RenderBufferType, colorAtt.renderBuffer());
(*dst)[n] = { rbD->globalResourceId(), rbD->generation };
} else {
(*dst)[n] = { 0, 0 };
}
++n;
if (colorAtt.resolveTexture()) {
TexType *texD = QRHI_RES(TexType, colorAtt.resolveTexture());
(*dst)[n] = { texD->globalResourceId(), texD->generation };
} else {
(*dst)[n] = { 0, 0 };
}
}
if (hasDepthStencil) {
if (desc.depthTexture()) {
TexType *depthTexD = QRHI_RES(TexType, desc.depthTexture());
(*dst)[n] = { depthTexD->globalResourceId(), depthTexD->generation };
} else if (desc.depthStencilBuffer()) {
RenderBufferType *depthRbD = QRHI_RES(RenderBufferType, desc.depthStencilBuffer());
(*dst)[n] = { depthRbD->globalResourceId(), depthRbD->generation };
} else {
(*dst)[n] = { 0, 0 };
}
}
}
template<typename TexType, typename RenderBufferType>
bool QRhiRenderTargetAttachmentTracker::isUpToDate(const QRhiTextureRenderTargetDescription &desc, const ResIdList &currentResIdList)
{
// Just as setShaderResources() recognizes if an srb's referenced
// resources have been rebuilt (got a create() since the srb's
// create()), we should do the same for the textures and renderbuffers
// referenced from the rendertarget. It is not uncommon that a texture
// or ds buffer gets resized due to following a window size in some
// form, which involves a create() on them. It is then nice if the
// render target auto-rebuilds in beginPass().
ResIdList resIdList;
updateResIdList<TexType, RenderBufferType>(desc, &resIdList);
return resIdList == currentResIdList;
}
QT_END_NAMESPACE
#endif

View File

@ -1762,6 +1762,8 @@ void QRhiD3D11::beginPass(QRhiCommandBuffer *cb,
QD3D11TextureRenderTarget *rtTex = QRHI_RES(QD3D11TextureRenderTarget, rt);
wantsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QD3D11Texture, QD3D11RenderBuffer>(rtTex->description(), rtD->currentResIdList))
rtTex->create();
}
cbD->commands.get().cmd = QD3D11CommandBuffer::Command::ResetShaderResources;
@ -2898,6 +2900,7 @@ bool QD3D11RenderBuffer::create()
QRHI_PROF;
QRHI_PROF_F(newRenderBuffer(this, false, false, int(sampleDesc.Count)));
generation += 1;
rhiD->registerResource(this);
return true;
}
@ -3609,6 +3612,8 @@ bool QD3D11TextureRenderTarget::create()
d.dsv = dsv;
d.rp = QRHI_RES(QD3D11RenderPassDescriptor, m_renderPassDesc);
QRhiRenderTargetAttachmentTracker::updateResIdList<QD3D11Texture, QD3D11RenderBuffer>(m_desc, &d.currentResIdList);
rhiD->registerResource(this);
return true;
}

View File

@ -96,6 +96,7 @@ struct QD3D11RenderBuffer : public QRhiRenderBuffer
ID3D11RenderTargetView *rtv = nullptr;
DXGI_FORMAT dxgiFormat;
DXGI_SAMPLE_DESC sampleDesc;
uint generation = 0;
friend class QRhiD3D11;
};
@ -172,6 +173,8 @@ struct QD3D11RenderTargetData
static const int MAX_COLOR_ATTACHMENTS = 8;
ID3D11RenderTargetView *rtv[MAX_COLOR_ATTACHMENTS];
ID3D11DepthStencilView *dsv = nullptr;
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList;
};
struct QD3D11ReferenceRenderTarget : public QRhiRenderTarget

View File

@ -3793,6 +3793,12 @@ void QRhiGles2::beginPass(QRhiCommandBuffer *cb,
// glMemoryBarrier() calls based on that tracker when submitted.
enqueueBarriersForPass(cbD);
if (rt->resourceType() == QRhiRenderTarget::TextureRenderTarget) {
QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, rt);
if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QGles2Texture, QGles2RenderBuffer>(rtTex->description(), rtTex->d.currentResIdList))
rtTex->create();
}
bool wantsColorClear, wantsDsClear;
QGles2RenderTargetData *rtD = enqueueBindFramebuffer(rt, cbD, &wantsColorClear, &wantsDsClear);
@ -4626,6 +4632,7 @@ bool QGles2RenderBuffer::create()
}
owns = true;
generation += 1;
rhiD->registerResource(this);
return true;
}
@ -4653,6 +4660,7 @@ bool QGles2RenderBuffer::createFrom(NativeRenderBuffer src)
QRHI_PROF_F(newRenderBuffer(this, false, false, samples));
owns = false;
generation += 1;
rhiD->registerResource(this);
return true;
}
@ -5114,6 +5122,8 @@ bool QGles2TextureRenderTarget::create()
return false;
}
QRhiRenderTargetAttachmentTracker::updateResIdList<QGles2Texture, QGles2RenderBuffer>(m_desc, &d.currentResIdList);
rhiD->registerResource(this);
return true;
}

View File

@ -108,6 +108,7 @@ struct QGles2RenderBuffer : public QRhiRenderBuffer
GLuint stencilRenderbuffer = 0; // when packed depth-stencil not supported
int samples;
bool owns = true;
uint generation = 0;
friend class QRhiGles2;
};
@ -213,6 +214,7 @@ struct QGles2RenderTargetData
int colorAttCount = 0;
int dsAttCount = 0;
bool srgbUpdateAndBlend = false;
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList;
};
struct QGles2ReferenceRenderTarget : public QRhiRenderTarget

View File

@ -296,6 +296,8 @@ struct QMetalRenderTargetData
bool hasStencil = false;
bool depthNeedsStore = false;
} fb;
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList;
};
struct QMetalGraphicsPipelineData
@ -2030,6 +2032,8 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
{
QMetalTextureRenderTarget *rtTex = QRHI_RES(QMetalTextureRenderTarget, rt);
rtD = rtTex->d;
if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QMetalTexture, QMetalRenderBuffer>(rtTex->description(), rtD->currentResIdList))
rtTex->create();
cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
if (rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents)) {
for (uint i = 0; i < uint(rtD->colorAttCount); ++i)
@ -3181,6 +3185,8 @@ bool QMetalTextureRenderTarget::create()
d->dsAttCount = 0;
}
QRhiRenderTargetAttachmentTracker::updateResIdList<QMetalTexture, QMetalRenderBuffer>(m_desc, &d->currentResIdList);
return true;
}

View File

@ -553,12 +553,18 @@ void QRhiNull::beginPass(QRhiCommandBuffer *cb,
QRhiResourceUpdateBatch *resourceUpdates,
QRhiCommandBuffer::BeginPassFlags flags)
{
Q_UNUSED(rt);
Q_UNUSED(colorClearValue);
Q_UNUSED(depthStencilClearValue);
Q_UNUSED(flags);
if (resourceUpdates)
resourceUpdate(cb, resourceUpdates);
if (rt->resourceType() == QRhiRenderTarget::TextureRenderTarget) {
QNullTextureRenderTarget *rtTex = QRHI_RES(QNullTextureRenderTarget, rt);
if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QNullTexture, QNullRenderBuffer>(rtTex->description(), rtTex->d.currentResIdList))
rtTex->create();
}
}
void QRhiNull::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
@ -660,6 +666,7 @@ bool QNullRenderBuffer::create()
destroy();
valid = true;
generation += 1;
QRHI_PROF;
QRHI_PROF_F(newRenderBuffer(this, false, false, 1));
@ -726,6 +733,8 @@ bool QNullTexture::create()
}
}
generation += 1;
QRHI_PROF;
QRHI_PROF_F(newTexture(this, true, mipLevelCount, layerCount, 1));
rhiD->registerResource(this);
@ -748,6 +757,8 @@ bool QNullTexture::createFrom(QRhiTexture::NativeTexture src)
QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
generation += 1;
QRHI_PROF;
QRHI_PROF_F(newTexture(this, false, mipLevelCount, isCube ? 6 : (isArray ? m_arraySize : 1), 1));
rhiD->registerResource(this);
@ -871,6 +882,7 @@ bool QNullTextureRenderTarget::create()
} else if (m_desc.depthTexture()) {
d.pixelSize = m_desc.depthTexture()->pixelSize();
}
QRhiRenderTargetAttachmentTracker::updateResIdList<QNullTexture, QNullRenderBuffer>(m_desc, &d.currentResIdList);
return true;
}

View File

@ -78,6 +78,7 @@ struct QNullRenderBuffer : public QRhiRenderBuffer
QRhiTexture::Format backingFormat() const override;
bool valid = false;
uint generation = 0;
};
struct QNullTexture : public QRhiTexture
@ -91,6 +92,7 @@ struct QNullTexture : public QRhiTexture
bool valid = false;
QVarLengthArray<std::array<QImage, QRhi::MAX_MIP_LEVELS>, 6> image;
uint generation = 0;
};
struct QNullSampler : public QRhiSampler
@ -119,6 +121,7 @@ struct QNullRenderTargetData
QNullRenderPassDescriptor *rp = nullptr;
QSize pixelSize;
float dpr = 1;
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList;
};
struct QNullReferenceRenderTarget : public QRhiRenderTarget

View File

@ -2175,6 +2175,9 @@ static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const
void QRhiVulkan::activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD)
{
if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QVkTexture, QVkRenderBuffer>(rtD->description(), rtD->d.currentResIdList))
rtD->create();
rtD->lastActiveFrameSlot = currentFrameSlot;
rtD->d.rp->lastActiveFrameSlot = currentFrameSlot;
QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
@ -5759,6 +5762,7 @@ bool QVkRenderBuffer::create()
}
lastActiveFrameSlot = -1;
generation += 1;
rhiD->registerResource(this);
return true;
}
@ -6572,6 +6576,8 @@ bool QVkTextureRenderTarget::create()
return false;
}
QRhiRenderTargetAttachmentTracker::updateResIdList<QVkTexture, QVkRenderBuffer>(m_desc, &d.currentResIdList);
lastActiveFrameSlot = -1;
rhiD->registerResource(this);
return true;

View File

@ -123,6 +123,7 @@ struct QVkRenderBuffer : public QRhiRenderBuffer
QVkTexture *backingTexture = nullptr;
VkFormat vkformat;
int lastActiveFrameSlot = -1;
uint generation = 0;
friend class QRhiVulkan;
};
@ -214,6 +215,7 @@ struct QVkRenderTargetData
int colorAttCount = 0;
int dsAttCount = 0;
int resolveAttCount = 0;
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList;
static const int MAX_COLOR_ATTACHMENTS = 8;
};

View File

@ -137,6 +137,8 @@ private slots:
void finishWithinSwapchainFrame();
void resourceUpdateBatchBufferTextureWithSwapchainFrames_data();
void resourceUpdateBatchBufferTextureWithSwapchainFrames();
void textureRenderTargetAutoRebuild_data();
void textureRenderTargetAutoRebuild();
void pipelineCache_data();
void pipelineCache();
@ -3767,6 +3769,49 @@ void tst_QRhi::resourceUpdateBatchBufferTextureWithSwapchainFrames()
}
}
void tst_QRhi::textureRenderTargetAutoRebuild_data()
{
rhiTestData();
}
void tst_QRhi::textureRenderTargetAutoRebuild()
{
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");
QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget));
QVERIFY(texture->create());
QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ { texture.data() } }));
QScopedPointer<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor());
rt->setRenderPassDescriptor(rp.data());
QVERIFY(rt->create());
QRhiCommandBuffer *cb = nullptr;
QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
QVERIFY(cb);
cb->beginPass(rt.data(), Qt::red, { 1.0f, 0 });
cb->endPass();
rhi->endOffscreenFrame();
texture->setPixelSize(QSize(256, 256));
QVERIFY(texture->create());
QCOMPARE(texture->pixelSize(), QSize(256, 256));
// rt still has the old size and knows nothing about texture's underlying native texture resource possibly changing
QCOMPARE(rt->pixelSize(), QSize(512, 512));
QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess);
QVERIFY(cb);
// no rt->create() but beginPass() does it implicitly for us
cb->beginPass(rt.data(), Qt::red, { 1.0f, 0 });
QCOMPARE(rt->pixelSize(), QSize(256, 256));
cb->endPass();
rhi->endOffscreenFrame();
}
void tst_QRhi::srbLayoutCompatibility_data()
{
rhiTestData();