rhi: Add support for full, direct buffer updates

Change-Id: I02c1f8c32c08d39cde9845d20ba8b02541d9d325
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
bb10
Laszlo Agocs 2020-10-09 13:34:37 +02:00
parent cd0b5bba9a
commit 2ac2809ec3
13 changed files with 160 additions and 6 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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<char *>(mp.pData);
}
void QD3D11Buffer::endFullDynamicUniformBufferUpdateForCurrentFrame()
{
QRHI_RES_RHI(QRhiD3D11);
rhiD->context->Unmap(buffer, 0);
}
ID3D11UnorderedAccessView *QD3D11Buffer::unorderedAccessView()
{
if (uav)

View File

@ -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();

View File

@ -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)

View File

@ -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;

View File

@ -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<char *>(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);

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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<char *>(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)

View File

@ -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];