diff --git a/filament/backend/src/metal/MetalBuffer.h b/filament/backend/src/metal/MetalBuffer.h index 70de26a5c9..f4c69076b2 100644 --- a/filament/backend/src/metal/MetalBuffer.h +++ b/filament/backend/src/metal/MetalBuffer.h @@ -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 getGpuBufferForDraw(id 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 diff --git a/filament/backend/src/metal/MetalBuffer.mm b/filament/backend/src/metal/MetalBuffer.mm index 17c096047c..743c52b982 100644 --- a/filament/backend/src/metal/MetalBuffer.mm +++ b/filament/backend/src/metal/MetalBuffer.mm @@ -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(mCpuBuffer) + byteOffset, src, size); @@ -70,6 +85,30 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) { memcpy(static_cast(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(mBufferPoolEntry->buffer.contents) + mCurrentStreamOffset, src, size); +} + id MetalBuffer::getGpuBufferForDraw(id 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 cmdBuffer, idmCurrentStreamOffset; } if (stages & Stage::VERTEX) { diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index f181ea0a3c..7f656eba70 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -1339,11 +1339,12 @@ void MetalDriver::draw(backend::PipelineState ps, Handle rph) id cmdBuffer = getPendingCommandBuffer(mContext); id 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 tqh) {