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 | |
7 | namespace 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 | |