rhi: Allow querying the native buffer objects behind a QRhiBuffer

Modeled after QRhiTexture's NativeTexture query.

This becomes valuable in advanced cases of integrating external native
rendering code with Qt Quick(3D), because it allows using (typically
vertex and index) buffers created by Quick(3D) in the custom renderer as
well, without having to duplicate the content by manually creating native
buffers with the same vertex and index data.

Change-Id: I659193345fa1dfe6221b898043f0b75ba649d296
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
bb10
Laszlo Agocs 2020-02-20 11:03:16 +01:00
parent 79b605d285
commit 2940c375e4
11 changed files with 206 additions and 0 deletions

View File

@ -1959,6 +1959,40 @@ quint64 QRhiResource::globalResourceId() const
size() will always report the size requested in \a sz.
*/
/*!
\class QRhiBuffer::NativeBuffer
\brief Contains information about the underlying native resources of a buffer.
*/
/*!
\variable QRhiBuffer::NativeBuffer::objects
\brief an array with pointers to the native object handles.
With OpenGL, the native handle is a GLuint value, so the elements in the \c
objects array are pointers to a GLuint. With Vulkan, the native handle is a
VkBuffer, so the elements of the array are pointers to a VkBuffer. With
Direct3D 11 and Metal the elements are pointers to a ID3D11Buffer or
MTLBuffer pointer, respectively.
\note Pay attention to the fact that the elements are always pointers to
the native buffer handle type, even if the native type itself is a pointer.
*/
/*!
\variable QRhiBuffer::NativeBuffer::slotCount
\brief Specifies the number of valid elements in the objects array.
The value can be 0, 1, 2, or 3 in practice. 0 indicates that the QRhiBuffer
is not backed by any native buffer objects. This can happen with
QRhiBuffers with the usage UniformBuffer when the underlying API does not
support (or the backend chooses not to use) native uniform buffers. 1 is
commonly used for Immutable and Static types (but some backends may
differ). 2 or 3 is typical when the type is Dynamic (but some backends may
differ).
\sa QRhi::currentFrameSlot(), QRhi::FramesInFlight
*/
/*!
\internal
*/
@ -1987,6 +2021,46 @@ QRhiResource::Type QRhiBuffer::resourceType() const
Regardless of the return value, calling release() is always safe.
*/
/*!
\return the underlying native resources for this buffer. The returned value
will be empty if exposing the underlying native resources is not supported by
the backend.
A QRhiBuffer may be backed by multiple native buffer objects, depending on
the type() and the QRhi backend in use. When this is the case, all of them
are returned in the objects array in the returned struct, with slotCount
specifying the number of native buffer objects. While
\l{QRhi::beginFrame()}{recording a frame}, QRhi::currentFrameSlot() can be
used to determine which of the native buffers QRhi is using for operations
that read or write from this QRhiBuffer within the frame being recorded.
In some cases a QRhiBuffer will not be backed by a native buffer object at
all. In this case slotCount will be set to 0 and no valid native objects
are returned. This is not an error, and is perfectly valid when a given
backend does not use native buffers for QRhiBuffers with certain types or
usages.
\note Be aware that QRhi backends may employ various buffer update
strategies. Unlike textures, where uploading image data always means
recording a buffer-to-image (or similar) copy command on the command
buffer, buffers, in particular Dynamic and UniformBuffer ones, can operate
in many different ways. For example, a QRhiBuffer with usage type
UniformBuffer may not even be backed by a native buffer object at all if
uniform buffers are not used or supported by a given backend and graphics
API. There are also differences to how data is written to the buffer and
the type of backing memory used, and, if host visible memory is involved,
when memory writes become available and visible. Therefore, in general it
is recommended to limit native buffer object access to vertex and index
buffers with types Static or Immutable, because these operate in a
relatively uniform manner with all backends.
\sa QRhi::currentFrameSlot(), QRhi::FramesInFlight
*/
QRhiBuffer::NativeBuffer QRhiBuffer::nativeBuffer()
{
return {};
}
/*!
\class QRhiRenderBuffer
\internal

View File

@ -681,6 +681,11 @@ public:
};
Q_DECLARE_FLAGS(UsageFlags, UsageFlag)
struct NativeBuffer {
const void *objects[3];
int slotCount;
};
QRhiResource::Type resourceType() const override;
Type type() const { return m_type; }
@ -694,6 +699,8 @@ public:
virtual bool build() = 0;
virtual NativeBuffer nativeBuffer();
protected:
QRhiBuffer(QRhiImplementation *rhi, Type type_, UsageFlags usage_, int size_);
Type m_type;

View File

@ -2386,6 +2386,11 @@ bool QD3D11Buffer::build()
return true;
}
QRhiBuffer::NativeBuffer QD3D11Buffer::nativeBuffer()
{
return { { &buffer }, 1 };
}
ID3D11UnorderedAccessView *QD3D11Buffer::unorderedAccessView()
{
if (uav)

View File

@ -64,6 +64,7 @@ struct QD3D11Buffer : public QRhiBuffer
~QD3D11Buffer();
void release() override;
bool build() override;
QRhiBuffer::NativeBuffer nativeBuffer() override;
ID3D11UnorderedAccessView *unorderedAccessView();

View File

@ -3303,6 +3303,14 @@ bool QGles2Buffer::build()
return true;
}
QRhiBuffer::NativeBuffer QGles2Buffer::nativeBuffer()
{
if (m_usage.testFlag(QRhiBuffer::UniformBuffer))
return { {}, 0 };
return { { &buffer }, 1 };
}
QGles2RenderBuffer::QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
int sampleCount, QRhiRenderBuffer::Flags flags)
: QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags)

View File

@ -64,6 +64,7 @@ struct QGles2Buffer : public QRhiBuffer
~QGles2Buffer();
void release() override;
bool build() override;
QRhiBuffer::NativeBuffer nativeBuffer() override;
GLuint buffer = 0;
GLenum targetForDataOps;

View File

@ -2200,6 +2200,19 @@ bool QMetalBuffer::build()
return true;
}
QRhiBuffer::NativeBuffer QMetalBuffer::nativeBuffer()
{
if (d->slotted) {
NativeBuffer b;
Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QMTL_FRAMES_IN_FLIGHT));
for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
b.objects[i] = &d->buf[i];
b.slotCount = QMTL_FRAMES_IN_FLIGHT;
return b;
}
return { { &d->buf[0] }, 1 };
}
QMetalRenderBuffer::QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
int sampleCount, QRhiRenderBuffer::Flags flags)
: QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags),

View File

@ -65,6 +65,7 @@ struct QMetalBuffer : public QRhiBuffer
~QMetalBuffer();
void release() override;
bool build() override;
QRhiBuffer::NativeBuffer nativeBuffer() override;
QMetalBufferData *d;
uint generation = 0;

View File

@ -5185,6 +5185,19 @@ bool QVkBuffer::build()
return true;
}
QRhiBuffer::NativeBuffer QVkBuffer::nativeBuffer()
{
if (m_type == Dynamic) {
NativeBuffer b;
Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QVK_FRAMES_IN_FLIGHT));
for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
b.objects[i] = &buffers[i];
b.slotCount = QVK_FRAMES_IN_FLIGHT;
return b;
}
return { { &buffers[0] }, 1 };
}
QVkRenderBuffer::QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
int sampleCount, Flags flags)
: QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags)

View File

@ -76,6 +76,7 @@ struct QVkBuffer : public QRhiBuffer
~QVkBuffer();
void release() override;
bool build() override;
QRhiBuffer::NativeBuffer nativeBuffer() override;
VkBuffer buffers[QVK_FRAMES_IN_FLIGHT];
QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT];

View File

@ -75,6 +75,8 @@ private slots:
void nativeHandles();
void nativeTexture_data();
void nativeTexture();
void nativeBuffer_data();
void nativeBuffer();
void resourceUpdateBatchBuffer_data();
void resourceUpdateBatchBuffer();
void resourceUpdateBatchRGBATextureUpload_data();
@ -546,6 +548,86 @@ void tst_QRhi::nativeTexture()
}
}
void tst_QRhi::nativeBuffer_data()
{
rhiTestData();
}
void tst_QRhi::nativeBuffer()
{
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 native buffer query");
const QRhiBuffer::Type types[3] = { QRhiBuffer::Immutable, QRhiBuffer::Static, QRhiBuffer::Dynamic };
const QRhiBuffer::UsageFlags usages[3] = { QRhiBuffer::VertexBuffer, QRhiBuffer::IndexBuffer, QRhiBuffer::UniformBuffer };
for (int typeUsageIdx = 0; typeUsageIdx < 3; ++typeUsageIdx) {
QScopedPointer<QRhiBuffer> buf(rhi->newBuffer(types[typeUsageIdx], usages[typeUsageIdx], 256));
QVERIFY(buf->build());
const QRhiBuffer::NativeBuffer nativeBuf = buf->nativeBuffer();
QVERIFY(nativeBuf.slotCount <= rhi->resourceLimit(QRhi::FramesInFlight));
switch (impl) {
case QRhi::Null:
break;
#ifdef TST_VK
case QRhi::Vulkan:
{
QVERIFY(nativeBuf.slotCount >= 1); // always backed by native buffers
for (int i = 0; i < nativeBuf.slotCount; ++i) {
auto *buffer = static_cast<const VkBuffer *>(nativeBuf.objects[i]);
QVERIFY(buffer);
QVERIFY(*buffer);
}
}
break;
#endif
#ifdef TST_GL
case QRhi::OpenGLES2:
{
QVERIFY(nativeBuf.slotCount >= 0); // UniformBuffers are not backed by native buffers, so 0 is perfectly valid
for (int i = 0; i < nativeBuf.slotCount; ++i) {
auto *bufferId = static_cast<const uint *>(nativeBuf.objects[i]);
QVERIFY(bufferId);
QVERIFY(*bufferId);
}
}
break;
#endif
#ifdef TST_D3D11
case QRhi::D3D11:
{
QVERIFY(nativeBuf.slotCount >= 1); // always backed by native buffers
for (int i = 0; i < nativeBuf.slotCount; ++i) {
auto *buffer = static_cast<void * const *>(nativeBuf.objects[i]);
QVERIFY(buffer);
QVERIFY(*buffer);
}
}
break;
#endif
#ifdef TST_MTL
case QRhi::Metal:
{
QVERIFY(nativeBuf.slotCount >= 1); // always backed by native buffers
for (int i = 0; i < nativeBuf.slotCount; ++i) {
void * const * buffer = (void * const *) nativeBuf.objects[i];
QVERIFY(buffer);
QVERIFY(*buffer);
}
}
break;
#endif
default:
Q_ASSERT(false);
}
}
}
static bool submitResourceUpdates(QRhi *rhi, QRhiResourceUpdateBatch *batch)
{
QRhiCommandBuffer *cb = nullptr;