• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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