From 2ac2809ec36af238b3755f176ef45427c1d32467 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Fri, 9 Oct 2020 13:34:37 +0200 Subject: rhi: Add support for full, direct buffer updates Change-Id: I02c1f8c32c08d39cde9845d20ba8b02541d9d325 Reviewed-by: Andy Nichols --- src/gui/rhi/qrhi.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++++ src/gui/rhi/qrhi_p.h | 3 +++ src/gui/rhi/qrhi_p_p.h | 1 - src/gui/rhi/qrhid3d11.cpp | 25 ++++++++++++++++++++++ src/gui/rhi/qrhid3d11_p_p.h | 2 ++ src/gui/rhi/qrhigles2.cpp | 6 ++++++ src/gui/rhi/qrhigles2_p_p.h | 1 + src/gui/rhi/qrhimetal.mm | 26 +++++++++++++++++++++++ src/gui/rhi/qrhimetal_p_p.h | 2 ++ src/gui/rhi/qrhinull.cpp | 16 +++++++++++---- src/gui/rhi/qrhinull_p_p.h | 3 ++- src/gui/rhi/qrhivulkan.cpp | 30 +++++++++++++++++++++++++++ src/gui/rhi/qrhivulkan_p_p.h | 2 ++ 13 files changed, 160 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index 1e158f36ef..ead50628f6 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -2112,6 +2112,55 @@ QRhiBuffer::NativeBuffer QRhiBuffer::nativeBuffer() return {}; } +/*! + \return a pointer to a memory block with the host visible buffer data. + + This is a shortcut for medium-to-large dynamic uniform buffers that have + their \b entire contents (or at least all regions that are read by the + shaders in the current frame) changed \b{in every frame} and the + QRhiResourceUpdateBatch-based update mechanism is seen too heavy due to the + amount of data copying involved. + + The call to this function must be eventually followed by a call to + endFullDynamicUniformBufferUpdateForCurrentFrame(), before recording any + render or compute pass that relies on this buffer. + + \warning Updating data via this method is not compatible with + QRhiResourceUpdateBatch-based updates and readbacks. Unexpected behavior + may occur when attempting to combine the two update models for the same + buffer. Similarly, the data updated this direct way may not be visible to + \l{QRhiResourceUpdateBatch::readBackBuffer()}{readBackBuffer operations}, + depending on the backend. + + \warning When updating buffer data via this method, the update must be done + in every frame, otherwise backends that perform double or tripple buffering + of resources may end up in unexpected behavior. + + \warning Partial updates are not possible with this approach since some + backends may choose a strategy where the previous contents of the buffer is + lost upon calling this function. Data must be written to all regions that + are read by shaders in the frame currently being prepared. + + \warning This function can only be called when recording a frame, so + between QRhi::beginFrame() and QRhi::endFrame(). + + \warning This function can only be called on Dynamic buffers with the + UniformBuffer usage flag. + */ +char *QRhiBuffer::beginFullDynamicUniformBufferUpdateForCurrentFrame() +{ + return nullptr; +} + +/*! + To be called when the entire contents of the buffer data has been updated + in the memory block returned from + beginFullDynamicUniformBufferUpdateForCurrentFrame(). + */ +void QRhiBuffer::endFullDynamicUniformBufferUpdateForCurrentFrame() +{ +} + /*! \class QRhiRenderBuffer \internal diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index adbb1b61ff..fa58718ace 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -717,6 +717,9 @@ public: virtual NativeBuffer nativeBuffer(); + virtual char *beginFullDynamicUniformBufferUpdateForCurrentFrame(); + virtual void endFullDynamicUniformBufferUpdateForCurrentFrame(); + protected: QRhiBuffer(QRhiImplementation *rhi, Type type_, UsageFlags usage_, int size_); Type m_type; diff --git a/src/gui/rhi/qrhi_p_p.h b/src/gui/rhi/qrhi_p_p.h index 3a76e9b97b..862d68f5a5 100644 --- a/src/gui/rhi/qrhi_p_p.h +++ b/src/gui/rhi/qrhi_p_p.h @@ -220,7 +220,6 @@ public: static const int MAX_SHADER_CACHE_ENTRIES = 128; -protected: bool debugMarkers = false; int currentFrameSlot = 0; // for vk, mtl, and similar. unused by gl and d3d11. bool inFrame = false; diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 4d00f11c84..7d8339252b 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -2692,6 +2692,31 @@ QRhiBuffer::NativeBuffer QD3D11Buffer::nativeBuffer() return { { &buffer }, 1 }; } +char *QD3D11Buffer::beginFullDynamicUniformBufferUpdateForCurrentFrame() +{ + // Shortcut the entire buffer update mechanism and allow the client to do + // the host writes directly to the buffer. This will lead to unexpected + // results when combined with QRhiResourceUpdateBatch-based updates for the + // buffer, since dynBuf is left untouched and out of sync, but provides a + // fast path for uniform buffers that have all their content changed in + // every frame. + Q_ASSERT(m_type == Dynamic && m_usage.testFlag(UniformBuffer)); + D3D11_MAPPED_SUBRESOURCE mp; + QRHI_RES_RHI(QRhiD3D11); + HRESULT hr = rhiD->context->Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mp); + if (FAILED(hr)) { + qWarning("Failed to map buffer: %s", qPrintable(comErrorMessage(hr))); + return nullptr; + } + return static_cast(mp.pData); +} + +void QD3D11Buffer::endFullDynamicUniformBufferUpdateForCurrentFrame() +{ + QRHI_RES_RHI(QRhiD3D11); + rhiD->context->Unmap(buffer, 0); +} + ID3D11UnorderedAccessView *QD3D11Buffer::unorderedAccessView() { if (uav) diff --git a/src/gui/rhi/qrhid3d11_p_p.h b/src/gui/rhi/qrhid3d11_p_p.h index bec263e210..4de193530a 100644 --- a/src/gui/rhi/qrhid3d11_p_p.h +++ b/src/gui/rhi/qrhid3d11_p_p.h @@ -65,6 +65,8 @@ struct QD3D11Buffer : public QRhiBuffer void destroy() override; bool create() override; QRhiBuffer::NativeBuffer nativeBuffer() override; + char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override; + void endFullDynamicUniformBufferUpdateForCurrentFrame() override; ID3D11UnorderedAccessView *unorderedAccessView(); diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 3d47a71d31..0ec2ff9c35 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -3897,6 +3897,12 @@ QRhiBuffer::NativeBuffer QGles2Buffer::nativeBuffer() return { { &buffer }, 1 }; } +char *QGles2Buffer::beginFullDynamicUniformBufferUpdateForCurrentFrame() +{ + Q_ASSERT(m_type == Dynamic && m_usage.testFlag(UniformBuffer)); + return ubuf; +} + QGles2RenderBuffer::QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags, QRhiTexture::Format backingFormatHint) diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h index 79f38dffce..2023585b6b 100644 --- a/src/gui/rhi/qrhigles2_p_p.h +++ b/src/gui/rhi/qrhigles2_p_p.h @@ -65,6 +65,7 @@ struct QGles2Buffer : public QRhiBuffer void destroy() override; bool create() override; QRhiBuffer::NativeBuffer nativeBuffer() override; + char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override; GLuint buffer = 0; GLenum targetForDataOps; diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 4caaca4a3a..5bbcd50047 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -2254,6 +2254,32 @@ QRhiBuffer::NativeBuffer QMetalBuffer::nativeBuffer() return { { &d->buf[0] }, 1 }; } +char *QMetalBuffer::beginFullDynamicUniformBufferUpdateForCurrentFrame() +{ + // Shortcut the entire buffer update mechanism and allow the client to do + // the host writes directly to the buffer. This will lead to unexpected + // results when combined with QRhiResourceUpdateBatch-based updates for the + // buffer, but provides a fast path for uniform buffers that have all their + // content changed in every frame. + Q_ASSERT(m_type == Dynamic && m_usage.testFlag(UniformBuffer)); + QRHI_RES_RHI(QRhiMetal); + Q_ASSERT(rhiD->inFrame); + const int slot = rhiD->currentFrameSlot; + void *p = [d->buf[slot] contents]; + return static_cast(p); +} + +void QMetalBuffer::endFullDynamicUniformBufferUpdateForCurrentFrame() +{ +#ifdef Q_OS_MACOS + if (d->managed) { + QRHI_RES_RHI(QRhiMetal); + const int slot = rhiD->currentFrameSlot; + [d->buf[slot] didModifyRange: NSMakeRange(0, NSUInteger(m_size))]; + } +#endif +} + static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags, const QRhiMetalData *d) { const bool srgb = flags.testFlag(QRhiTexture::sRGB); diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h index a8c342da25..1691b03ec6 100644 --- a/src/gui/rhi/qrhimetal_p_p.h +++ b/src/gui/rhi/qrhimetal_p_p.h @@ -66,6 +66,8 @@ struct QMetalBuffer : public QRhiBuffer void destroy() override; bool create() override; QRhiBuffer::NativeBuffer nativeBuffer() override; + char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override; + void endFullDynamicUniformBufferUpdateForCurrentFrame() override; QMetalBufferData *d; uint generation = 0; diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index 86beb243c6..737e5fb48b 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -465,12 +465,12 @@ void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *re || u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { QNullBuffer *bufD = QRHI_RES(QNullBuffer, u.buf); - memcpy(bufD->data.data() + u.offset, u.data.constData(), size_t(u.data.size())); + memcpy(bufD->data + u.offset, u.data.constData(), size_t(u.data.size())); } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { QRhiBufferReadbackResult *result = u.result; result->data.resize(u.readSize); QNullBuffer *bufD = QRHI_RES(QNullBuffer, u.buf); - memcpy(result->data.data(), bufD->data.constData() + u.offset, size_t(u.readSize)); + memcpy(result->data.data(), bufD->data + u.offset, size_t(u.readSize)); if (result->completed) result->completed(); } @@ -566,7 +566,8 @@ QNullBuffer::~QNullBuffer() void QNullBuffer::destroy() { - data.clear(); + delete[] data; + data = nullptr; QRHI_PROF; QRHI_PROF_F(releaseBuffer(this)); @@ -574,13 +575,20 @@ void QNullBuffer::destroy() bool QNullBuffer::create() { - data.fill('\0', m_size); + data = new char[m_size]; + memset(data, 0, m_size); QRHI_PROF; QRHI_PROF_F(newBuffer(this, uint(m_size), 1, 0)); return true; } +char *QNullBuffer::beginFullDynamicUniformBufferUpdateForCurrentFrame() +{ + Q_ASSERT(m_type == Dynamic && m_usage.testFlag(UniformBuffer)); + return data; +} + QNullRenderBuffer::QNullRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags, QRhiTexture::Format backingFormatHint) diff --git a/src/gui/rhi/qrhinull_p_p.h b/src/gui/rhi/qrhinull_p_p.h index 567b672b3f..e0e796f7ca 100644 --- a/src/gui/rhi/qrhinull_p_p.h +++ b/src/gui/rhi/qrhinull_p_p.h @@ -59,8 +59,9 @@ struct QNullBuffer : public QRhiBuffer ~QNullBuffer(); void destroy() override; bool create() override; + char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override; - QByteArray data; + char *data = nullptr; }; struct QNullRenderBuffer : public QRhiRenderBuffer diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index 4e23f1622c..d0e98fb751 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -5373,6 +5373,36 @@ QRhiBuffer::NativeBuffer QVkBuffer::nativeBuffer() return { { &buffers[0] }, 1 }; } +char *QVkBuffer::beginFullDynamicUniformBufferUpdateForCurrentFrame() +{ + // Shortcut the entire buffer update mechanism and allow the client to do + // the host writes directly to the buffer. This will lead to unexpected + // results when combined with QRhiResourceUpdateBatch-based updates for the + // buffer, but provides a fast path for uniform buffers that have all their + // content changed in every frame. + Q_ASSERT(m_type == Dynamic && m_usage.testFlag(UniformBuffer)); + QRHI_RES_RHI(QRhiVulkan); + Q_ASSERT(rhiD->inFrame); + const int slot = rhiD->currentFrameSlot; + void *p = nullptr; + VmaAllocation a = toVmaAllocation(allocations[slot]); + VkResult err = vmaMapMemory(toVmaAllocator(rhiD->allocator), a, &p); + if (err != VK_SUCCESS) { + qWarning("Failed to map buffer: %d", err); + return nullptr; + } + return static_cast(p); +} + +void QVkBuffer::endFullDynamicUniformBufferUpdateForCurrentFrame() +{ + QRHI_RES_RHI(QRhiVulkan); + const int slot = rhiD->currentFrameSlot; + VmaAllocation a = toVmaAllocation(allocations[slot]); + vmaUnmapMemory(toVmaAllocator(rhiD->allocator), a); + vmaFlushAllocation(toVmaAllocator(rhiD->allocator), a, 0, m_size); +} + QVkRenderBuffer::QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, int sampleCount, Flags flags, QRhiTexture::Format backingFormatHint) diff --git a/src/gui/rhi/qrhivulkan_p_p.h b/src/gui/rhi/qrhivulkan_p_p.h index 0a1c43f538..5a20e6a757 100644 --- a/src/gui/rhi/qrhivulkan_p_p.h +++ b/src/gui/rhi/qrhivulkan_p_p.h @@ -77,6 +77,8 @@ struct QVkBuffer : public QRhiBuffer void destroy() override; bool create() override; QRhiBuffer::NativeBuffer nativeBuffer() override; + char *beginFullDynamicUniformBufferUpdateForCurrentFrame() override; + void endFullDynamicUniformBufferUpdateForCurrentFrame() override; VkBuffer buffers[QVK_FRAMES_IN_FLIGHT]; QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT]; -- cgit v1.2.3