1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "Prerequisites/BsPrerequisitesUtil.h"
4#include "Allocators/BsFrameAlloc.h"
5#include "Error/BsException.h"
6
7namespace bs
8{
9 UINT8* FrameAlloc::MemBlock::alloc(UINT32 amount)
10 {
11 UINT8* freePtr = &mData[mFreePtr];
12 mFreePtr += amount;
13
14 return freePtr;
15 }
16
17 void FrameAlloc::MemBlock::clear()
18 {
19 mFreePtr = 0;
20 }
21
22#if BS_DEBUG_MODE
23 FrameAlloc::FrameAlloc(UINT32 blockSize)
24 :mBlockSize(blockSize), mFreeBlock(nullptr), mNextBlockIdx(0), mTotalAllocBytes(0),
25 mLastFrame(nullptr)
26 {
27 }
28#else
29 FrameAlloc::FrameAlloc(UINT32 blockSize)
30 : mBlockSize(blockSize), mFreeBlock(nullptr), mNextBlockIdx(0), mTotalAllocBytes(0), mLastFrame(nullptr)
31 {
32 }
33#endif
34
35 FrameAlloc::~FrameAlloc()
36 {
37 for(auto& block : mBlocks)
38 deallocBlock(block);
39 }
40
41 UINT8* FrameAlloc::alloc(UINT32 amount)
42 {
43#if BS_DEBUG_MODE
44 amount += sizeof(UINT32);
45#endif
46 UINT32 freeMem = 0;
47 if(mFreeBlock != nullptr)
48 freeMem = mFreeBlock->mSize - mFreeBlock->mFreePtr;
49
50 if(amount > freeMem)
51 allocBlock(amount);
52
53 UINT8* data = mFreeBlock->alloc(amount);
54
55#if BS_DEBUG_MODE
56 mTotalAllocBytes += amount;
57
58 UINT32* storedSize = reinterpret_cast<UINT32*>(data);
59 *storedSize = amount;
60
61 return data + sizeof(UINT32);
62#else
63 return data;
64#endif
65 }
66
67 UINT8* FrameAlloc::allocAligned(UINT32 amount, UINT32 alignment)
68 {
69#if BS_DEBUG_MODE
70 amount += sizeof(UINT32);
71#endif
72
73 UINT32 freeMem = 0;
74 UINT32 freePtr = 0;
75 if(mFreeBlock != nullptr)
76 {
77 freeMem = mFreeBlock->mSize - mFreeBlock->mFreePtr;
78
79#if BS_DEBUG_MODE
80 freePtr = mFreeBlock->mFreePtr + sizeof(UINT32);
81#else
82 freePtr = mFreeBlock->mFreePtr;
83#endif
84 }
85
86 UINT32 alignOffset = (alignment - (freePtr & (alignment - 1))) & (alignment - 1);
87 if ((amount + alignOffset) > freeMem)
88 {
89 // New blocks are allocated on a 16 byte boundary, ensure enough space is allocated taking into account
90 // the requested alignment
91
92#if BS_DEBUG_MODE
93 alignOffset = (alignment - (sizeof(UINT32) & (alignment - 1))) & (alignment - 1);
94#else
95 if (alignment > 16)
96 alignOffset = alignment - 16;
97 else
98 alignOffset = 0;
99#endif
100
101 allocBlock(amount + alignOffset);
102 }
103
104 amount += alignOffset;
105 UINT8* data = mFreeBlock->alloc(amount);
106
107#if BS_DEBUG_MODE
108 mTotalAllocBytes += amount;
109
110 UINT32* storedSize = reinterpret_cast<UINT32*>(data + alignOffset);
111 *storedSize = amount;
112
113 return data + sizeof(UINT32) + alignOffset;
114#else
115 return data + alignOffset;
116#endif
117 }
118
119 void FrameAlloc::free(UINT8* data)
120 {
121 // Dealloc is only used for debug and can be removed if needed. All the actual deallocation
122 // happens in clear()
123
124#if BS_DEBUG_MODE
125 if(data)
126 {
127 data -= sizeof(UINT32);
128 UINT32* storedSize = reinterpret_cast<UINT32*>(data);
129 mTotalAllocBytes -= *storedSize;
130 }
131#endif
132 }
133
134 void FrameAlloc::markFrame()
135 {
136 void** framePtr = (void**)alloc(sizeof(void*));
137 *framePtr = mLastFrame;
138 mLastFrame = framePtr;
139 }
140
141 void FrameAlloc::clear()
142 {
143 if(mLastFrame != nullptr)
144 {
145 assert(mBlocks.size() > 0 && mNextBlockIdx > 0);
146
147 free((UINT8*)mLastFrame);
148
149 UINT8* framePtr = (UINT8*)mLastFrame;
150 mLastFrame = *(void**)mLastFrame;
151
152#if BS_DEBUG_MODE
153 framePtr -= sizeof(UINT32);
154#endif
155
156 UINT32 startBlockIdx = mNextBlockIdx - 1;
157 UINT32 numFreedBlocks = 0;
158 for (INT32 i = startBlockIdx; i >= 0; i--)
159 {
160 MemBlock* curBlock = mBlocks[i];
161 UINT8* blockEnd = curBlock->mData + curBlock->mSize;
162 if (framePtr >= curBlock->mData && framePtr < blockEnd)
163 {
164 UINT8* dataEnd = curBlock->mData + curBlock->mFreePtr;
165 UINT32 sizeInBlock = (UINT32)(dataEnd - framePtr);
166 assert(sizeInBlock <= curBlock->mFreePtr);
167
168 curBlock->mFreePtr -= sizeInBlock;
169 if (curBlock->mFreePtr == 0)
170 {
171 numFreedBlocks++;
172
173 // Reset block counter if we're gonna reallocate this one
174 if (numFreedBlocks > 1)
175 mNextBlockIdx = (UINT32)i;
176 }
177
178 break;
179 }
180 else
181 {
182 curBlock->mFreePtr = 0;
183 mNextBlockIdx = (UINT32)i;
184 numFreedBlocks++;
185 }
186 }
187
188 if (numFreedBlocks > 1)
189 {
190 UINT32 totalBytes = 0;
191 for (UINT32 i = 0; i < numFreedBlocks; i++)
192 {
193 MemBlock* curBlock = mBlocks[mNextBlockIdx];
194 totalBytes += curBlock->mSize;
195
196 deallocBlock(curBlock);
197 mBlocks.erase(mBlocks.begin() + mNextBlockIdx);
198 }
199
200 UINT32 oldNextBlockIdx = mNextBlockIdx;
201 allocBlock(totalBytes);
202
203 // Point to the first non-full block, or if none available then point the the block we just allocated
204 if (oldNextBlockIdx > 0)
205 mFreeBlock = mBlocks[oldNextBlockIdx - 1];
206 }
207 else
208 {
209 mFreeBlock = mBlocks[mNextBlockIdx - 1];
210 }
211 }
212 else
213 {
214#if BS_DEBUG_MODE
215 if (mTotalAllocBytes.load() > 0)
216 BS_EXCEPT(InvalidStateException, "Not all frame allocated bytes were properly released.");
217#endif
218
219 if (mBlocks.size() > 1)
220 {
221 // Merge all blocks into one
222 UINT32 totalBytes = 0;
223 for (auto& block : mBlocks)
224 {
225 totalBytes += block->mSize;
226 deallocBlock(block);
227 }
228
229 mBlocks.clear();
230 mNextBlockIdx = 0;
231
232 allocBlock(totalBytes);
233 }
234 else if(mBlocks.size() > 0)
235 mBlocks[0]->mFreePtr = 0;
236 }
237 }
238
239 FrameAlloc::MemBlock* FrameAlloc::allocBlock(UINT32 wantedSize)
240 {
241 UINT32 blockSize = mBlockSize;
242 if(wantedSize > blockSize)
243 blockSize = wantedSize;
244
245 MemBlock* newBlock = nullptr;
246 while (mNextBlockIdx < mBlocks.size())
247 {
248 MemBlock* curBlock = mBlocks[mNextBlockIdx];
249 if (blockSize <= curBlock->mSize)
250 {
251 newBlock = curBlock;
252 mNextBlockIdx++;
253 break;
254 }
255 else
256 {
257 // Found an empty block that doesn't fit our data, delete it
258 deallocBlock(curBlock);
259 mBlocks.erase(mBlocks.begin() + mNextBlockIdx);
260 }
261 }
262
263 if (newBlock == nullptr)
264 {
265 UINT32 alignOffset = 16 - (sizeof(MemBlock) & (16 - 1));
266
267 UINT8* data = (UINT8*)reinterpret_cast<UINT8*>(bs_alloc_aligned16(blockSize + sizeof(MemBlock) + alignOffset));
268 newBlock = new (data) MemBlock(blockSize);
269 data += sizeof(MemBlock) + alignOffset;
270 newBlock->mData = data;
271
272 mBlocks.push_back(newBlock);
273 mNextBlockIdx++;
274 }
275
276 mFreeBlock = newBlock; // If previous block had some empty space it is lost until next "clear"
277
278 return newBlock;
279 }
280
281 void FrameAlloc::deallocBlock(MemBlock* block)
282 {
283 block->~MemBlock();
284 bs_free_aligned16(block);
285 }
286
287 void FrameAlloc::setOwnerThread(ThreadId thread)
288 {
289 }
290
291 BS_THREADLOCAL FrameAlloc* _GlobalFrameAlloc = nullptr;
292
293 BS_UTILITY_EXPORT FrameAlloc& gFrameAlloc()
294 {
295 if (_GlobalFrameAlloc == nullptr)
296 {
297 // Note: This will leak memory but since it should exist throughout the entirety
298 // of runtime it should only leak on shutdown when the OS will free it anyway.
299 _GlobalFrameAlloc = new FrameAlloc();
300 }
301
302 return *_GlobalFrameAlloc;
303 }
304
305 BS_UTILITY_EXPORT UINT8* bs_frame_alloc(UINT32 numBytes)
306 {
307 return gFrameAlloc().alloc(numBytes);
308 }
309
310 BS_UTILITY_EXPORT UINT8* bs_frame_alloc_aligned(UINT32 count, UINT32 align)
311 {
312 return gFrameAlloc().allocAligned(count, align);
313 }
314
315 BS_UTILITY_EXPORT void bs_frame_free(void* data)
316 {
317 gFrameAlloc().free((UINT8*)data);
318 }
319
320 BS_UTILITY_EXPORT void bs_frame_free_aligned(void* data)
321 {
322 gFrameAlloc().free((UINT8*)data);
323 }
324
325 BS_UTILITY_EXPORT void bs_frame_mark()
326 {
327 gFrameAlloc().markFrame();
328 }
329
330 BS_UTILITY_EXPORT void bs_frame_clear()
331 {
332 gFrameAlloc().clear();
333 }
334}
335