1// 2// Copyright 2022 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_manager.mm: 7// Implements the class methods for BufferManager. 8// 9 10#include "libANGLE/renderer/metal/mtl_buffer_manager.h" 11 12#include "libANGLE/renderer/metal/ContextMtl.h" 13#include "libANGLE/renderer/metal/DisplayMtl.h" 14 15namespace rx 16{ 17 18namespace mtl 19{ 20 21namespace 22{ 23 24int storageModeToCacheIndex(MTLStorageMode storageMode) 25{ 26 static_assert(MTLStorageModeShared == 0); 27#if TARGET_OS_OSX || TARGET_OS_MACCATALYST 28 static_assert(MTLStorageModeManaged == 1); 29#endif 30 return static_cast<int>(storageMode); 31} 32 33} // namespace 34 35BufferManager::BufferManager() {} 36 37void BufferManager::freeUnusedBuffers(ContextMtl *contextMtl) 38{ 39 // Scan for the first buffer still in use. 40 BufferList::iterator firstInUseIter = 41 std::find_if(mInUseBuffers.begin(), mInUseBuffers.end(), 42 [&contextMtl](auto ref) { return ref->isBeingUsedByGPU(contextMtl); }); 43 44 // Move unused buffers to the free lists 45 for (BufferList::iterator it = mInUseBuffers.begin(); it != firstInUseIter; ++it) 46 { 47 addBufferRefToFreeLists(*it); 48 } 49 mInUseBuffers.erase(mInUseBuffers.begin(), firstInUseIter); 50} 51 52void BufferManager::addBufferRefToFreeLists(mtl::BufferRef &bufferRef) 53{ 54 int cacheIndex = storageModeToCacheIndex(bufferRef->storageMode()); 55 ASSERT(cacheIndex < kNumCachedStorageModes); 56 mFreeBuffers[cacheIndex].insert(BufferMap::value_type(bufferRef->size(), bufferRef)); 57} 58 59void BufferManager::returnBuffer(ContextMtl *contextMtl, BufferRef &bufferRef) 60{ 61 int cacheIndex = storageModeToCacheIndex(bufferRef->storageMode()); 62 if (cacheIndex >= kNumCachedStorageModes) 63 { 64 return; // Storage mode that we do not have a cache for. 65 } 66 bufferRef->setNumContextSwitchesAtLastUse(mContextSwitches); 67 bufferRef->setNumCommandBufferCommitsAtLastUse(mCommandBufferCommits); 68 if (bufferRef->isBeingUsedByGPU(contextMtl)) 69 { 70 mInUseBuffers.push_back(bufferRef); 71 } 72 else 73 { 74 addBufferRefToFreeLists(bufferRef); 75 } 76} 77 78void BufferManager::incrementNumContextSwitches() 79{ 80 ++mContextSwitches; 81 // Ignore wraparound for the moment 82 if (mContextSwitches - mContextSwitchesAtLastGC >= kContextSwitchesBetweenGC) 83 { 84 collectGarbage(GCReason::ContextSwitches); 85 } 86} 87 88void BufferManager::incrementNumCommandBufferCommits() 89{ 90 ++mCommandBufferCommits; 91 // Ignore wraparound for the moment 92 if (mCommandBufferCommits - mCommandBufferCommitsAtLastGC >= kCommandBufferCommitsBetweenGC) 93 { 94 collectGarbage(GCReason::CommandBufferCommits); 95 } 96} 97 98void BufferManager::collectGarbage(BufferManager::GCReason reason) 99{ 100 ANGLE_UNUSED_VARIABLE(reason); 101#ifdef ANGLE_MTL_TRACK_BUFFER_MEM 102 { 103 fprintf(stderr, "** Before BufferManager GC (reason=%s): totalMem: %zu, ", 104 (reason == GCReason::ContextSwitches 105 ? "context switches" 106 : (reason == GCReason::CommandBufferCommits ? "command buffer commits" 107 : "total memory")), 108 mTotalMem); 109 size_t numBuffers = 0; 110 for (auto iter = mAllocatedSizes.begin(); iter != mAllocatedSizes.end(); ++iter) 111 { 112 fprintf(stderr, "%zu: %zu, ", iter->first, iter->second); 113 numBuffers += iter->second; 114 } 115 fprintf(stderr, " total: %zu\n", numBuffers); 116 } 117#endif 118 119 for (int i = 0; i < kNumCachedStorageModes; ++i) 120 { 121 BufferMap &map = mFreeBuffers[i]; 122 auto iter = map.begin(); 123 while (iter != map.end()) 124 { 125 // Clean out cached buffers for the following reasons: 126 // 1) GC was triggered because of total memory use. 127 // 2) The buffer has gone unused for multiple context switches. 128 // 3) The buffer hasn't been used for many command buffer commits. 129 if (reason == GCReason::TotalMem || 130 (mContextSwitches - iter->second->getNumContextSwitchesAtLastUse() >= 131 kContextSwitchesBetweenGC) || 132 (mCommandBufferCommits - iter->second->getNumCommandBufferCommitsAtLastUse() >= 133 kCommandBufferCommitsBetweenGC)) 134 { 135 iter = map.erase(iter); 136 } 137 else 138 { 139 ++iter; 140 } 141 } 142 } 143 mContextSwitchesAtLastGC = mContextSwitches; 144 mCommandBufferCommitsAtLastGC = mCommandBufferCommits; 145 146 { 147 mTotalMem = 0; 148#ifdef ANGLE_MTL_TRACK_BUFFER_MEM 149 mAllocatedSizes.clear(); 150#endif 151 for (auto iter = mInUseBuffers.begin(); iter != mInUseBuffers.end(); ++iter) 152 { 153 size_t sz = (*iter)->size(); 154 mTotalMem += sz; 155#ifdef ANGLE_MTL_TRACK_BUFFER_MEM 156 ++mAllocatedSizes[sz]; 157#endif 158 } 159 160 for (int i = 0; i < kNumCachedStorageModes; ++i) 161 { 162 BufferMap &map = mFreeBuffers[i]; 163 for (auto iter = map.begin(); iter != map.end(); ++iter) 164 { 165 size_t sz = iter->first; 166#ifdef ANGLE_MTL_TRACK_BUFFER_MEM 167 ++mAllocatedSizes[sz]; 168#endif 169 mTotalMem += sz; 170 } 171 } 172 173#ifdef ANGLE_MTL_TRACK_BUFFER_MEM 174 fprintf(stderr, "** After BufferManager GC: totalMem: %zu, ", mTotalMem); 175 size_t numBuffers = 0; 176 for (auto iter = mAllocatedSizes.begin(); iter != mAllocatedSizes.end(); ++iter) 177 { 178 fprintf(stderr, "%zu: %zu, ", iter->first, iter->second); 179 numBuffers += iter->second; 180 } 181 fprintf(stderr, " total: %zu\n", numBuffers); 182#endif 183 184 mTotalMemAtLastGC = mTotalMem; 185 } 186} 187 188angle::Result BufferManager::getBuffer(ContextMtl *contextMtl, 189 MTLStorageMode storageMode, 190 size_t size, 191 BufferRef &bufferRef) 192{ 193 freeUnusedBuffers(contextMtl); 194 const int cacheIndex = storageModeToCacheIndex(storageMode); 195 if (cacheIndex < kNumCachedStorageModes) 196 { 197 // Buffer has a storage mode that have a cache for. 198 BufferMap &freeBuffers = mFreeBuffers[cacheIndex]; 199 auto iter = freeBuffers.find(size); 200 if (iter != freeBuffers.end()) 201 { 202 bufferRef = iter->second; 203 freeBuffers.erase(iter); 204 bufferRef->setNumContextSwitchesAtLastUse(mContextSwitches); 205 bufferRef->setNumCommandBufferCommitsAtLastUse(mCommandBufferCommits); 206 return angle::Result::Continue; 207 } 208 } 209 210 // Create a new one 211 mtl::BufferRef newBufferRef; 212 213 ANGLE_TRY(mtl::Buffer::MakeBufferWithStorageMode(contextMtl, storageMode, size, nullptr, 214 &newBufferRef)); 215 216 mTotalMem += size; 217 218#ifdef ANGLE_MTL_TRACK_BUFFER_MEM 219 { 220 mAllocatedSizes[size]++; 221 fprintf(stderr, "totalMem: %zu, ", mTotalMem); 222 size_t numBuffers = 0; 223 for (auto iter = mAllocatedSizes.begin(); iter != mAllocatedSizes.end(); ++iter) 224 { 225 fprintf(stderr, "%zu: %zu, ", iter->first, iter->second); 226 numBuffers += iter->second; 227 } 228 fprintf(stderr, " total: %zu\n", numBuffers); 229 } 230#endif 231 232 bool lotOfMemAllocedRecently = (mTotalMem > mTotalMemAtLastGC + kMemAllocedBetweenGC); 233 if (mTotalMem > kMinMemBasedGC && 234 (mTotalMem > 2 * mTotalMemAtLastGC || lotOfMemAllocedRecently)) 235 { 236 if (mInUseBuffers.size() > 0 && lotOfMemAllocedRecently) 237 { 238 // A considerable amount of memory has been allocated since the last GC. Wait for all 239 // pending GPU work to finish in order to unreference in-flight buffers more quickly. 240 contextMtl->flushCommandBuffer(mtl::WaitUntilFinished); 241 } 242 else 243 { 244 // Flush the current command buffer so that the in-flight buffers are scheduled and will 245 // eventually become unreferenced. They won't be collected during this GC cycle but 246 // hopefully will in the next one. 247 contextMtl->flushCommandBuffer(mtl::NoWait); 248 } 249 freeUnusedBuffers(contextMtl); 250 collectGarbage(GCReason::TotalMem); 251 } 252 253 bufferRef = newBufferRef; 254 255 return angle::Result::Continue; 256} 257 258angle::Result BufferManager::queueBlitCopyDataToBuffer(ContextMtl *contextMtl, 259 const void *srcPtr, 260 size_t sizeToCopy, 261 size_t offset, 262 mtl::BufferRef &dstMetalBuffer) 263{ 264 const uint8_t *src = reinterpret_cast<const uint8_t *>(srcPtr); 265 266 for (size_t srcOffset = 0; srcOffset < sizeToCopy; srcOffset += kMaxStagingBufferSize) 267 { 268 size_t subSizeToCopy = std::min(kMaxStagingBufferSize, sizeToCopy - srcOffset); 269 270 mtl::BufferRef bufferRef; 271 // TODO(anglebug.com/7544): Here we pass DynamicDraw to get managed buffer for the 272 // operation. This should be checked to see if this makes sense. 273 auto storageMode = Buffer::getStorageModeForUsage(contextMtl, Buffer::Usage::DynamicDraw); 274 ANGLE_TRY(getBuffer(contextMtl, storageMode, subSizeToCopy, bufferRef)); 275 276 // copy data to buffer 277 uint8_t *ptr = bufferRef->mapWithOpt(contextMtl, false, true); 278 std::copy(src + srcOffset, src + srcOffset + subSizeToCopy, ptr); 279 bufferRef->unmapAndFlushSubset(contextMtl, 0, subSizeToCopy); 280 281 // queue blit 282 mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder(); 283 blitEncoder->copyBuffer(bufferRef, 0, dstMetalBuffer, offset + srcOffset, subSizeToCopy); 284 285 returnBuffer(contextMtl, bufferRef); 286 } 287 return angle::Result::Continue; 288} 289 290} // namespace mtl 291} // namespace rx 292