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