// // Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // mtl_buffer_pool.mm: // Implements the class methods for BufferPool. // #include "libANGLE/renderer/metal/mtl_buffer_pool.h" #include "libANGLE/renderer/metal/ContextMtl.h" namespace rx { namespace mtl { // BufferPool implementation. BufferPool::BufferPool(bool alwaysAllocNewBuffer) : mInitialSize(0), mBuffer(nullptr), mNextAllocationOffset(0), mSize(0), mAlignment(1), mBuffersAllocated(0), mMaxBuffers(0), mAlwaysAllocateNewBuffer(alwaysAllocNewBuffer) {} void BufferPool::initialize(ContextMtl *contextMtl, size_t initialSize, size_t alignment, size_t maxBuffers) { destroy(contextMtl); mInitialSize = initialSize; mSize = 0; mMaxBuffers = maxBuffers; updateAlignment(contextMtl, alignment); } BufferPool::~BufferPool() {} angle::Result BufferPool::allocateNewBuffer(ContextMtl *contextMtl) { if (mMaxBuffers > 0 && mBuffersAllocated >= mMaxBuffers) { // We reach the max number of buffers allowed. // Try to deallocate old and smaller size inflight buffers. releaseInFlightBuffers(contextMtl); } if (mMaxBuffers > 0 && mBuffersAllocated >= mMaxBuffers) { // If we reach this point, it means there was no buffer deallocated inside // releaseInFlightBuffers() thus, the number of buffers allocated still exceeds number // allowed. ASSERT(!mBufferFreeList.empty()); // Reuse the buffer in free list: if (mBufferFreeList.front()->isBeingUsedByGPU(contextMtl)) { contextMtl->flushCommandBufer(); // Force the GPU to finish its rendering and make the old buffer available. contextMtl->cmdQueue().ensureResourceReadyForCPU(mBufferFreeList.front()); } mBuffer = mBufferFreeList.front(); mBufferFreeList.erase(mBufferFreeList.begin()); return angle::Result::Continue; } ANGLE_TRY(Buffer::MakeBuffer(contextMtl, mSize, nullptr, &mBuffer)); ASSERT(mBuffer); mBuffersAllocated++; return angle::Result::Continue; } angle::Result BufferPool::allocate(ContextMtl *contextMtl, size_t sizeInBytes, uint8_t **ptrOut, BufferRef *bufferOut, size_t *offsetOut, bool *newBufferAllocatedOut) { size_t sizeToAllocate = roundUp(sizeInBytes, mAlignment); angle::base::CheckedNumeric checkedNextWriteOffset = mNextAllocationOffset; checkedNextWriteOffset += sizeToAllocate; if (!mBuffer || !checkedNextWriteOffset.IsValid() || checkedNextWriteOffset.ValueOrDie() >= mSize || mAlwaysAllocateNewBuffer) { if (mBuffer) { ANGLE_TRY(commit(contextMtl)); } if (sizeToAllocate > mSize) { mSize = std::max(mInitialSize, sizeToAllocate); // Clear the free list since the free buffers are now too small. destroyBufferList(contextMtl, &mBufferFreeList); } // The front of the free list should be the oldest. Thus if it is in use the rest of the // free list should be in use as well. if (mBufferFreeList.empty() || mBufferFreeList.front()->isBeingUsedByGPU(contextMtl)) { ANGLE_TRY(allocateNewBuffer(contextMtl)); } else { mBuffer = mBufferFreeList.front(); mBufferFreeList.erase(mBufferFreeList.begin()); } ASSERT(mBuffer->size() == mSize); mNextAllocationOffset = 0; if (newBufferAllocatedOut != nullptr) { *newBufferAllocatedOut = true; } } else if (newBufferAllocatedOut != nullptr) { *newBufferAllocatedOut = false; } ASSERT(mBuffer != nullptr); if (bufferOut != nullptr) { *bufferOut = mBuffer; } // Optionally map() the buffer if possible if (ptrOut) { *ptrOut = mBuffer->map(contextMtl) + mNextAllocationOffset; } if (offsetOut) { *offsetOut = static_cast(mNextAllocationOffset); } mNextAllocationOffset += static_cast(sizeToAllocate); return angle::Result::Continue; } angle::Result BufferPool::commit(ContextMtl *contextMtl) { if (mBuffer) { mBuffer->unmap(contextMtl); mInFlightBuffers.push_back(mBuffer); mBuffer = nullptr; } mNextAllocationOffset = 0; return angle::Result::Continue; } void BufferPool::releaseInFlightBuffers(ContextMtl *contextMtl) { for (auto &toRelease : mInFlightBuffers) { // If the dynamic buffer was resized we cannot reuse the retained buffer. if (toRelease->size() < mSize) { toRelease = nullptr; mBuffersAllocated--; } else { mBufferFreeList.push_back(toRelease); } } mInFlightBuffers.clear(); } void BufferPool::destroyBufferList(ContextMtl *contextMtl, std::vector *buffers) { ASSERT(mBuffersAllocated >= buffers->size()); mBuffersAllocated -= buffers->size(); buffers->clear(); } void BufferPool::destroy(ContextMtl *contextMtl) { destroyBufferList(contextMtl, &mInFlightBuffers); destroyBufferList(contextMtl, &mBufferFreeList); reset(); if (mBuffer) { mBuffer->unmap(contextMtl); mBuffer = nullptr; } } void BufferPool::updateAlignment(ContextMtl *contextMtl, size_t alignment) { ASSERT(alignment > 0); // NOTE(hqle): May check additional platform limits. mAlignment = alignment; } void BufferPool::reset() { mSize = 0; mNextAllocationOffset = 0; mMaxBuffers = 0; mAlwaysAllocateNewBuffer = false; mBuffersAllocated = 0; } } }