1// 2// Copyright 2019 The ANGLE Project Authors. All rights reserved. 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5// 6// mtl_buffer_pool.mm: 7// Implements the class methods for BufferPool. 8// 9 10#include "libANGLE/renderer/metal/mtl_buffer_pool.h" 11 12#include "libANGLE/renderer/metal/ContextMtl.h" 13 14namespace rx 15{ 16 17namespace mtl 18{ 19 20// BufferPool implementation. 21BufferPool::BufferPool(bool alwaysAllocNewBuffer) 22 : mInitialSize(0), 23 mBuffer(nullptr), 24 mNextAllocationOffset(0), 25 mSize(0), 26 mAlignment(1), 27 mBuffersAllocated(0), 28 mMaxBuffers(0), 29 mAlwaysAllocateNewBuffer(alwaysAllocNewBuffer) 30 31{} 32 33void BufferPool::initialize(ContextMtl *contextMtl, 34 size_t initialSize, 35 size_t alignment, 36 size_t maxBuffers) 37{ 38 destroy(contextMtl); 39 40 mInitialSize = initialSize; 41 mSize = 0; 42 43 mMaxBuffers = maxBuffers; 44 45 updateAlignment(contextMtl, alignment); 46} 47 48BufferPool::~BufferPool() {} 49 50angle::Result BufferPool::allocateNewBuffer(ContextMtl *contextMtl) 51{ 52 if (mMaxBuffers > 0 && mBuffersAllocated >= mMaxBuffers) 53 { 54 // We reach the max number of buffers allowed. 55 // Try to deallocate old and smaller size inflight buffers. 56 releaseInFlightBuffers(contextMtl); 57 } 58 59 if (mMaxBuffers > 0 && mBuffersAllocated >= mMaxBuffers) 60 { 61 // If we reach this point, it means there was no buffer deallocated inside 62 // releaseInFlightBuffers() thus, the number of buffers allocated still exceeds number 63 // allowed. 64 ASSERT(!mBufferFreeList.empty()); 65 66 // Reuse the buffer in free list: 67 if (mBufferFreeList.front()->isBeingUsedByGPU(contextMtl)) 68 { 69 contextMtl->flushCommandBufer(); 70 // Force the GPU to finish its rendering and make the old buffer available. 71 contextMtl->cmdQueue().ensureResourceReadyForCPU(mBufferFreeList.front()); 72 } 73 74 mBuffer = mBufferFreeList.front(); 75 mBufferFreeList.erase(mBufferFreeList.begin()); 76 77 return angle::Result::Continue; 78 } 79 80 ANGLE_TRY(Buffer::MakeBuffer(contextMtl, mSize, nullptr, &mBuffer)); 81 82 ASSERT(mBuffer); 83 84 mBuffersAllocated++; 85 86 return angle::Result::Continue; 87} 88 89angle::Result BufferPool::allocate(ContextMtl *contextMtl, 90 size_t sizeInBytes, 91 uint8_t **ptrOut, 92 BufferRef *bufferOut, 93 size_t *offsetOut, 94 bool *newBufferAllocatedOut) 95{ 96 size_t sizeToAllocate = roundUp(sizeInBytes, mAlignment); 97 98 angle::base::CheckedNumeric<size_t> checkedNextWriteOffset = mNextAllocationOffset; 99 checkedNextWriteOffset += sizeToAllocate; 100 101 if (!mBuffer || !checkedNextWriteOffset.IsValid() || 102 checkedNextWriteOffset.ValueOrDie() >= mSize || mAlwaysAllocateNewBuffer) 103 { 104 if (mBuffer) 105 { 106 ANGLE_TRY(commit(contextMtl)); 107 } 108 109 if (sizeToAllocate > mSize) 110 { 111 mSize = std::max(mInitialSize, sizeToAllocate); 112 113 // Clear the free list since the free buffers are now too small. 114 destroyBufferList(contextMtl, &mBufferFreeList); 115 } 116 117 // The front of the free list should be the oldest. Thus if it is in use the rest of the 118 // free list should be in use as well. 119 if (mBufferFreeList.empty() || mBufferFreeList.front()->isBeingUsedByGPU(contextMtl)) 120 { 121 ANGLE_TRY(allocateNewBuffer(contextMtl)); 122 } 123 else 124 { 125 mBuffer = mBufferFreeList.front(); 126 mBufferFreeList.erase(mBufferFreeList.begin()); 127 } 128 129 ASSERT(mBuffer->size() == mSize); 130 131 mNextAllocationOffset = 0; 132 133 if (newBufferAllocatedOut != nullptr) 134 { 135 *newBufferAllocatedOut = true; 136 } 137 } 138 else if (newBufferAllocatedOut != nullptr) 139 { 140 *newBufferAllocatedOut = false; 141 } 142 143 ASSERT(mBuffer != nullptr); 144 145 if (bufferOut != nullptr) 146 { 147 *bufferOut = mBuffer; 148 } 149 150 // Optionally map() the buffer if possible 151 if (ptrOut) 152 { 153 *ptrOut = mBuffer->map(contextMtl) + mNextAllocationOffset; 154 } 155 156 if (offsetOut) 157 { 158 *offsetOut = static_cast<size_t>(mNextAllocationOffset); 159 } 160 mNextAllocationOffset += static_cast<uint32_t>(sizeToAllocate); 161 return angle::Result::Continue; 162} 163 164angle::Result BufferPool::commit(ContextMtl *contextMtl) 165{ 166 if (mBuffer) 167 { 168 mBuffer->unmap(contextMtl); 169 170 mInFlightBuffers.push_back(mBuffer); 171 mBuffer = nullptr; 172 } 173 174 mNextAllocationOffset = 0; 175 176 return angle::Result::Continue; 177} 178 179void BufferPool::releaseInFlightBuffers(ContextMtl *contextMtl) 180{ 181 for (auto &toRelease : mInFlightBuffers) 182 { 183 // If the dynamic buffer was resized we cannot reuse the retained buffer. 184 if (toRelease->size() < mSize) 185 { 186 toRelease = nullptr; 187 mBuffersAllocated--; 188 } 189 else 190 { 191 mBufferFreeList.push_back(toRelease); 192 } 193 } 194 195 mInFlightBuffers.clear(); 196} 197 198void BufferPool::destroyBufferList(ContextMtl *contextMtl, std::vector<BufferRef> *buffers) 199{ 200 ASSERT(mBuffersAllocated >= buffers->size()); 201 mBuffersAllocated -= buffers->size(); 202 buffers->clear(); 203} 204 205void BufferPool::destroy(ContextMtl *contextMtl) 206{ 207 destroyBufferList(contextMtl, &mInFlightBuffers); 208 destroyBufferList(contextMtl, &mBufferFreeList); 209 210 reset(); 211 212 if (mBuffer) 213 { 214 mBuffer->unmap(contextMtl); 215 216 mBuffer = nullptr; 217 } 218} 219 220void BufferPool::updateAlignment(ContextMtl *contextMtl, size_t alignment) 221{ 222 ASSERT(alignment > 0); 223 224 // NOTE(hqle): May check additional platform limits. 225 226 mAlignment = alignment; 227} 228 229void BufferPool::reset() 230{ 231 mSize = 0; 232 mNextAllocationOffset = 0; 233 mMaxBuffers = 0; 234 mAlwaysAllocateNewBuffer = false; 235 mBuffersAllocated = 0; 236} 237} 238} 239