Metal: support efficient BufferUsage::STREAM usage (#4525)

This commit is contained in:
Ben Doherty
2021-08-20 10:51:30 -07:00
committed by GitHub
parent 8aba6f0dcd
commit 22afc3a93b
3 changed files with 59 additions and 6 deletions
+13
View File
@@ -51,9 +51,19 @@ public:
*
* @return The MTLBuffer representing the current state of the buffer to bind, or nil if there
* is no device allocation.
*
* For STREAM buffers, getGpuBufferStreamOffset() should be called to retrieve the correct
* buffer offset.
*
*/
id<MTLBuffer> getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) noexcept;
/**
* Returns the offset into the buffer returned by getGpuBufferForDraw. This is always 0 for
* non-STREAM buffers.
*/
size_t getGpuBufferStreamOffset() noexcept { return mCurrentStreamOffset; }
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
enum Stage {
@@ -73,11 +83,14 @@ public:
private:
BufferUsage mUsage;
size_t mBufferSize = 0;
size_t mCurrentStreamOffset = 0;
const MetalBufferPoolEntry* mBufferPoolEntry = nullptr;
void* mCpuBuffer = nullptr;
MetalContext& mContext;
void copyIntoStreamBuffer(void* src, size_t size);
};
} // namespace metal
+44 -5
View File
@@ -28,11 +28,19 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferUsage usage, size_t size,
: mBufferSize(size), mContext(context) {
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit
// buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:.
const bool dynamicUsage = usage == BufferUsage::DYNAMIC || usage == BufferUsage::STREAM;
if (size <= 4 * 1024 && dynamicUsage && !forceGpuBuffer) {
mBufferPoolEntry = nullptr;
mCpuBuffer = malloc(size);
if (size <= 4 * 1024) {
// We'll take the same approach for STREAM buffers under 4K.
if (usage == BufferUsage::STREAM) {
usage = BufferUsage::DYNAMIC;
}
if (usage == BufferUsage::DYNAMIC && !forceGpuBuffer) {
mBufferPoolEntry = nullptr;
mCpuBuffer = malloc(size);
}
}
mUsage = usage;
}
MetalBuffer::~MetalBuffer() {
@@ -54,6 +62,13 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
"Attempting to copy %d bytes into a buffer of size %d at offset %d",
size, mBufferSize, byteOffset);
if (mUsage == BufferUsage::STREAM) {
// byteOffset must be 0 for STREAM buffers.
assert_invariant(byteOffset == 0);
copyIntoStreamBuffer(src, size);
return;
}
// Either copy into the Metal buffer or into our cpu buffer.
if (mCpuBuffer) {
memcpy(static_cast<uint8_t*>(mCpuBuffer) + byteOffset, src, size);
@@ -70,6 +85,30 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
memcpy(static_cast<uint8_t*>(mBufferPoolEntry->buffer.contents) + byteOffset, src, size);
}
void MetalBuffer::copyIntoStreamBuffer(void* src, size_t size) {
// Stream buffers are handled a bit differently. In STREAM mode, we have a single large buffer.
// At each update we "advance" into the buffer, keeping track of the where we're at, and bind
// the buffer at that location. Effectively, "byteOffset" is managed automatically.
assert_invariant(size <= mBufferSize);
assert_invariant(!mCpuBuffer);
if (mCurrentStreamOffset + size > mBufferSize) {
// Allocate a new buffer and reset the stream offset.
mCurrentStreamOffset = 0;
if (mBufferPoolEntry) { mContext.bufferPool->releaseBuffer(mBufferPoolEntry); }
mBufferPoolEntry = mContext.bufferPool->acquireBuffer(mBufferSize);
}
// Lazily acquire a new buffer if we haven't already.
if (!mBufferPoolEntry) {
mBufferPoolEntry = mContext.bufferPool->acquireBuffer(mBufferSize);
}
memcpy(static_cast<uint8_t*>(mBufferPoolEntry->buffer.contents) + mCurrentStreamOffset, src, size);
}
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) noexcept {
if (!mBufferPoolEntry) {
// If there's a CPU buffer, then we return nil here, as the CPU-side buffer will be bound
@@ -121,7 +160,7 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLRenderComman
continue;
}
metalBuffers[b] = gpuBuffer;
metalOffsets[b] = offsets[b];
metalOffsets[b] = offsets[b] + buffer->mCurrentStreamOffset;
}
if (stages & Stage::VERTEX) {
+2 -1
View File
@@ -1339,11 +1339,12 @@ void MetalDriver::draw(backend::PipelineState ps, Handle<HwRenderPrimitive> rph)
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(mContext);
id<MTLBuffer> metalIndexBuffer = indexBuffer->buffer.getGpuBufferForDraw(cmdBuffer);
size_t offset = indexBuffer->buffer.getGpuBufferStreamOffset();
[mContext->currentRenderPassEncoder drawIndexedPrimitives:getMetalPrimitiveType(primitive->type)
indexCount:primitive->count
indexType:getIndexType(indexBuffer->elementSize)
indexBuffer:metalIndexBuffer
indexBufferOffset:primitive->offset];
indexBufferOffset:primitive->offset + offset];
}
void MetalDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {