Commit f4266304 authored by Laszlo Agocs's avatar Laszlo Agocs

vk: resource sharing

Sharable resources, which are at least buffers and textures, can now be
orphaned, meaning the owning QRhi can be destroyed before releasing the
QRhiResource. They will still function in a limited way (cannot be rebuilt
anymore) as long as there is at least one QRhi associated with the
QRhiResourceSharingHost (i.e. as long as the share group is non-empty).
parent d500b540
......@@ -337,6 +337,7 @@ void Window::init()
newTex = true;
image.load(QLatin1String(":/qt256.png"));
tex = m_rhi->newTexture(QRhiTexture::RGBA8, image.size());
Q_ASSERT(tex->isSharable());
tex->build();
}
......@@ -394,7 +395,7 @@ void Window::releaseResources()
m_sc = nullptr;
}
// ### this is wrong
// tex may outlive its creating QRhi, that's fine since it's isSharable()==true
if (activeRhiCount == 1) {
tex->releaseAndDestroy();
tex = nullptr;
......
......@@ -359,6 +359,8 @@ QT_BEGIN_NAMESPACE
underlying graphics resources of QRhiResource subclasses such as
QRhiTexture visible to all the QRhi instances that use the same
QRhiResourceSharingHost.
\sa QRhiResource::isSharable()
*/
/*!
......@@ -1159,6 +1161,25 @@ void QRhiResource::setName(const QByteArray &name)
objectName.replace(',', '_'); // cannot contain comma for QRhiProfiler
}
/*!
\return true if this resource is sharable between QRhi instances via a
QRhiResourceSharingHost. It also means that such an QRhiResource can
outlive the QRhi on which it was created.
\note Once a sharable QRhiResource is \c orphaned, meaning that the QRhi
from which it was created gets destroyed, build() is not a valid operation
anymore. It can only be used in graphics operations (as the underlying
graphics objects are still there and valid) or can be released. This is
true as long as the QRhiResourceSharingHost, with which the creator QRhi
was associated with, has at least one associated QRhi alive.
\sa QRhiResourceSharingHost, QRhi::CrossThreadResourceSharing
*/
bool QRhiResource::isSharable() const
{
return false;
}
/*!
\class QRhiBuffer
\inmodule QtRhi
......@@ -2334,6 +2355,32 @@ QRhiCommandBuffer::QRhiCommandBuffer(QRhiImplementation *rhi)
QRhiImplementation::~QRhiImplementation()
{
qDeleteAll(resUpdPool);
if (rsh) {
for (QRhiResource *res : qAsConst(resources)) {
if (res->isSharable()) {
res->orphanedWithRsh = rsh;
} else {
qWarning("QRhi %p going down orphaning an unreleased, non-sharable resource %p (%s). This is bad.",
q, res, res->objectName.constData());
}
res->rhi = nullptr;
}
} else {
// Be nice and show something about leaked stuff. Though we may not get
// this far with some backends where the allocator or the api may check
// and freak out for unfreed graphics objects in the derived dtor already.
#ifndef QT_NO_DEBUG
if (!resources.isEmpty()) {
qWarning("QRhi %p going down with %d unreleased resources and no QRhiResourceSharingHost. This is bad.",
q, resources.count());
for (QRhiResource *res : qAsConst(resources)) {
qWarning(" Resource %p (%s)", res, res->objectName.constData());
res->rhi = nullptr;
}
}
#endif
}
}
void QRhiImplementation::sendVMemStatsToProfiler()
......@@ -2543,7 +2590,25 @@ quint32 QRhiImplementation::approxByteSizeForTexture(QRhiTexture::Format format,
This makes the underlying graphics resources of QRhiResource subclasses
such as QRhiTexture available to all the QRhi instances that use the same
QRhiResourceSharingHost.
QRhiResourceSharingHost. This applies only to QRhiResource instances that
report \c true from \l{QRhiResource::isSharable()}{isSharable()}.
In order to avoid lifetime management issues with shared resources, a
sharable QRhiResource is allowed to be orphaned, meaning the QRhi the
resource was created from can be destroyed while keeping the QRhiResource
usable (although operations like \c build() are not allowed anymore then).
Applications can thus postpone calling
\l{QRhiResource::release()}{release()} on the resource as long as there is
at least one QRhi associated with the QRhiResourceSharingHost that was
associated with the resource's creator QRhi.
\note The QRhiResourceSharingHost does not perform graphics resource
operations on its own. Therefore it is important that all relevant
QRhiResource instances are released before the last associated QRhi is
destroyed. Attempting to release a QRhiResource after that will lead to
graphics resource leaks (assuming the resource sharing host gets destroyed
then; if there are new QRhi instances created for the same resource sharing
host later on then there is no issue, as long as, again, those are alive).
\note When creating QRhi instances on different threads, using
QRhiResourceSharingHost may not be supported, depending on the backend and
......@@ -2558,6 +2623,76 @@ quint32 QRhiImplementation::approxByteSizeForTexture(QRhiTexture::Format format,
created. It is however up to the application to organize those threads in a
way that the QRhiResourceSharingHost is only destroyed after all associated
QRhi instances have been fully destroyed.
To illustrate resource sharing, take the following code snippets.
In many cases an application will use a single QRhi. Resources created from it
must be always released before destroying the rhi:
\badcode
QRhi *rhi = QRhi::create(initParams);
QRhiTexture *tex = rhi->newTexture(...);
... use tex with rhi
tex->releaseAndDestroy();
delete rhi;
\endcode
Let's introduce a second QRhi:
\badcode
QRhi *rhi = QRhi::create(initParams);
QRhi *rhi2 = QRhi::create(initParams);
QRhiTexture *tex = rhi->newTexture(...);
... use tex with rhi
tex->releaseAndDestroy();
delete rhi;
delete rhi2;
\endcode
Here using \c tex with \c rhi2 is forbidden. (although it may work with some backends)
Let's make it work:
\badcode
QRhiResourceSharingHost *rsh = new QRhiResourceSharingHost;
initParams.resourceSharingHost = rsh;
QRhi *rhi = QRhi::create(initParams);
QRhi *rhi2 = QRhi::create(initParams);
QRhiTexture *tex = rhi->newTexture(...);
if (!tex->isSharable()) { error("not supported"); }
... use tex with rhi or rhi2
tex->releaseAndDestroy();
delete rhi;
delete rhi2;
delete rsh;
\endcode
Now \c tex is usable with \c rhi2 as well.
What if \c rhi is destroyed before \c rhi2?
\badcode
QRhiResourceSharingHost *rsh = new QRhiResourceSharingHost;
initParams.resourceSharingHost = rsh;
QRhi *rhi = QRhi::create(initParams);
QRhi *rhi2 = QRhi::create(initParams);
QRhiTexture *tex = rhi->newTexture(...);
if (!tex->isSharable()) { error("not supported"); }
... use tex with rhi or rhi2
delete rhi;
... use tex with rhi2
tex->releaseAndDestroy();
delete rhi2;
delete rsh;
\endcode
This is valid as well. After the \c{delete rhi} \c tex is in an orphaned
state so calling \c build() on it for instance would fail.
\note Moving the \c{tex->releaseAndDestroy()} call between the \c{delete
rhi2} and \c{delete rsh} statements would be incorrect.
\sa QRhiResource::isSharable(), QRhi::CrossThreadResourceSharing,
QRhiInitParams, QRhi::create()
*/
/*!
......
......@@ -491,11 +491,16 @@ public:
QByteArray name() const;
void setName(const QByteArray &name);
virtual bool isSharable() const;
protected:
QRhiResource(QRhiImplementation *rhi_);
Q_DISABLE_COPY(QRhiResource)
friend class QRhiImplementation;
QRhiImplementation *rhi = nullptr;
QByteArray objectName;
QRhiResourceSharingHostPrivate *orphanedWithRsh = nullptr;
Q_DECL_UNUSED_MEMBER quint64 m_reserved;
};
class Q_RHI_EXPORT QRhiBuffer : public QRhiResource
......
......@@ -155,13 +155,41 @@ public:
return p->rhiDWhenEnabled ? p : nullptr;
}
void registerResource(QRhiResource *res)
{
res->orphanedWithRsh = nullptr;
resources.insert(res);
}
void unregisterResource(QRhiResource *res)
{
resources.remove(res);
}
QSet<QRhiResource *> activeResources() const
{
return resources;
}
bool orphanCheck(QRhiResource *res)
{
if (res->orphanedWithRsh) {
qWarning("Attempted to perform something on an orphaned QRhiResource %p (%s). This is invalid.",
res, res->objectName.constData());
return false;
}
return true;
}
QRhi *q;
protected:
QRhiResourceSharingHostPrivate *rsh = nullptr;
QVector<QRhiResourceUpdateBatch *> resUpdPool;
QBitArray resUpdPoolMap;
QRhiProfiler profiler;
bool debugMarkers = false;
QSet<QRhiResource *> resources;
friend class QRhi;
friend class QRhiResourceUpdateBatchPrivate;
......
......@@ -532,7 +532,6 @@ public:
void finishActiveReadbacks();
void reportLiveObjects(ID3D11Device *device);
QRhiResourceSharingHostPrivate *rsh = nullptr;
bool debugLayer = false;
bool importedDevice = false;
ID3D11Device *dev = nullptr;
......
......@@ -1879,13 +1879,14 @@ void QGles2Texture::release()
specified = false;
nativeHandlesStruct.texture = 0;
if (owns) {
QRHI_RES_RHI(QRhiGles2);
QRHI_RES_RHI(QRhiGles2);
if (owns)
rhiD->releaseQueue.append(e);
}
QRHI_PROF;
QRHI_PROF_F(releaseTexture(this));
rhiD->unregisterResource(this);
}
static inline bool isPowerOfTwo(int x)
......@@ -1979,11 +1980,14 @@ bool QGles2Texture::build()
nativeHandlesStruct.texture = texture;
generation += 1;
rhiD->registerResource(this);
return true;
}
bool QGles2Texture::buildFrom(const QRhiNativeHandles *src)
{
QRHI_RES_RHI(QRhiGles2);
const QRhiGles2TextureNativeHandles *h = static_cast<const QRhiGles2TextureNativeHandles *>(src);
if (!h || !h->texture)
return false;
......@@ -2001,6 +2005,7 @@ bool QGles2Texture::buildFrom(const QRhiNativeHandles *src)
nativeHandlesStruct.texture = texture;
generation += 1;
rhiD->registerResource(this);
return true;
}
......
......@@ -523,7 +523,6 @@ public:
void executeBindGraphicsPipeline(QRhiGraphicsPipeline *ps, QRhiShaderResourceBindings *srb);
void setChangedUniforms(QGles2GraphicsPipeline *psD, QRhiShaderResourceBindings *srb, bool changedOnly);
QRhiResourceSharingHostPrivate *rsh = nullptr;
QOpenGLContext *ctx = nullptr;
bool importedContext = false;
QWindow *maybeWindow = nullptr;
......
......@@ -77,7 +77,11 @@ public:
QRhiGles2NativeHandles d_gles2;
#endif
#if QT_CONFIG(vulkan)
QRhiVulkanNativeHandles d_vulkan;
struct {
QRhiVulkanNativeHandles h;
QVulkanDeviceFunctions *df = nullptr;
void *releaseQueue = nullptr;
} d_vulkan;
#endif
#ifdef Q_OS_WIN
QRhiD3D11NativeHandles d_d3d11;
......
This diff is collapsed.
......@@ -66,6 +66,7 @@ typedef void * QVkAllocator;
struct QVkBuffer : public QRhiBuffer
{
QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size);
bool isSharable() const override;
void release() override;
bool build() override;
......@@ -104,6 +105,7 @@ struct QVkTexture : public QRhiTexture
{
QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
int sampleCount, Flags flags);
bool isSharable() const override;
void release() override;
bool build() override;
bool buildFrom(const QRhiNativeHandles *src) override;
......@@ -484,7 +486,6 @@ public:
// in case they changed in the meantime.
void updateShaderResourceBindings(QRhiShaderResourceBindings *srb, int descSetIdx = -1);
QRhiResourceSharingHostPrivate *rsh = nullptr;
QVulkanInstance *inst = nullptr;
QWindow *maybeWindow = nullptr;
bool importedDevice = false;
......@@ -622,6 +623,9 @@ public:
};
};
QVector<DeferredReleaseEntry> releaseQueue;
static void executeDeferredReleasesOnRshNow(QRhiResourceSharingHostPrivate *rsh,
QVector<DeferredReleaseEntry> *rshRelQueue);
};
Q_DECLARE_TYPEINFO(QRhiVulkan::DescriptorPoolData, Q_MOVABLE_TYPE);
......
res.sh.: reuse device / create sharing context
res.sh.: rhi resource tracking, isSharable, orphaning, ...
res.sh.: mtl
res.sh.: example to show read/write same texture
res.sh.: exercise it in multiwindow_threaded too
advanced blend modes
......@@ -48,6 +49,7 @@ dxc for d3d as an alternative to fxc?
hlsl -> dxc -> spirv -> spirv-cross hmmm...
+++ done
res.sh.: gl/d3d/vk
res.sh.: example to show using same texture
revise create() and importing external device objects
revise structs in api
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment