diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index 584fbb263d..7a0d53e1e4 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -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 diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index be96ebbf05..8f53808d34 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -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; diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 221ec6e6e5..f7c7f4a9f2 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -2386,6 +2386,11 @@ bool QD3D11Buffer::build() return true; } +QRhiBuffer::NativeBuffer QD3D11Buffer::nativeBuffer() +{ + return { { &buffer }, 1 }; +} + ID3D11UnorderedAccessView *QD3D11Buffer::unorderedAccessView() { if (uav) diff --git a/src/gui/rhi/qrhid3d11_p_p.h b/src/gui/rhi/qrhid3d11_p_p.h index c3a4021241..04751397f7 100644 --- a/src/gui/rhi/qrhid3d11_p_p.h +++ b/src/gui/rhi/qrhid3d11_p_p.h @@ -64,6 +64,7 @@ struct QD3D11Buffer : public QRhiBuffer ~QD3D11Buffer(); void release() override; bool build() override; + QRhiBuffer::NativeBuffer nativeBuffer() override; ID3D11UnorderedAccessView *unorderedAccessView(); diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index df0b255bed..feeb65137a 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -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) diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h index e10da2a342..8b7d01532a 100644 --- a/src/gui/rhi/qrhigles2_p_p.h +++ b/src/gui/rhi/qrhigles2_p_p.h @@ -64,6 +64,7 @@ struct QGles2Buffer : public QRhiBuffer ~QGles2Buffer(); void release() override; bool build() override; + QRhiBuffer::NativeBuffer nativeBuffer() override; GLuint buffer = 0; GLenum targetForDataOps; diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index b0813bfc77..48a562ef1d 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -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), diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h index 58e93e2cdb..212b731b71 100644 --- a/src/gui/rhi/qrhimetal_p_p.h +++ b/src/gui/rhi/qrhimetal_p_p.h @@ -65,6 +65,7 @@ struct QMetalBuffer : public QRhiBuffer ~QMetalBuffer(); void release() override; bool build() override; + QRhiBuffer::NativeBuffer nativeBuffer() override; QMetalBufferData *d; uint generation = 0; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index 5d6e9b21bc..d378e2a4ad 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -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) diff --git a/src/gui/rhi/qrhivulkan_p_p.h b/src/gui/rhi/qrhivulkan_p_p.h index d42b83b882..6322882569 100644 --- a/src/gui/rhi/qrhivulkan_p_p.h +++ b/src/gui/rhi/qrhivulkan_p_p.h @@ -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]; diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index ab435caa40..549481aa21 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -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 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 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(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(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(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;