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 // Suballocation.h:
7 // Defines class interface for BufferBlock and Suballocation and other related classes.
8 //
9
10 #ifndef LIBANGLE_RENDERER_VULKAN_SUBALLOCATION_H_
11 #define LIBANGLE_RENDERER_VULKAN_SUBALLOCATION_H_
12
13 #include "common/SimpleMutex.h"
14 #include "common/debug.h"
15 #include "libANGLE/angletypes.h"
16 #include "libANGLE/renderer/serial_utils.h"
17 #include "libANGLE/renderer/vulkan/vk_cache_utils.h"
18 #include "libANGLE/renderer/vulkan/vk_resource.h"
19 #include "libANGLE/renderer/vulkan/vk_utils.h"
20 #include "libANGLE/renderer/vulkan/vk_wrapper.h"
21
22 namespace rx
23 {
24 enum class MemoryAllocationType;
25
26 namespace vk
27 {
28 class ErrorContext;
29
30 // BufferBlock
31 class BufferBlock final : angle::NonCopyable
32 {
33 public:
34 BufferBlock();
35 BufferBlock(BufferBlock &&other);
36 ~BufferBlock();
37
38 void destroy(Renderer *renderer);
39 VkResult init(ErrorContext *context,
40 Buffer &buffer,
41 uint32_t memoryTypeIndex,
42 vma::VirtualBlockCreateFlags flags,
43 DeviceMemory &deviceMemory,
44 VkMemoryPropertyFlags memoryPropertyFlags,
45 VkDeviceSize size);
46 void initWithoutVirtualBlock(ErrorContext *context,
47 Buffer &buffer,
48 MemoryAllocationType memoryAllocationType,
49 uint32_t memoryTypeIndex,
50 DeviceMemory &deviceMemory,
51 VkMemoryPropertyFlags memoryPropertyFlags,
52 VkDeviceSize size,
53 VkDeviceSize allocatedBufferSize);
54
55 BufferBlock &operator=(BufferBlock &&other);
56
getBuffer()57 const Buffer &getBuffer() const { return mBuffer; }
getDeviceMemory()58 const DeviceMemory &getDeviceMemory() const { return mDeviceMemory; }
getDeviceMemory()59 DeviceMemory &getDeviceMemory() { return mDeviceMemory; }
getBufferSerial()60 BufferSerial getBufferSerial() const { return mSerial; }
61
62 VkMemoryPropertyFlags getMemoryPropertyFlags() const;
63 VkDeviceSize getMemorySize() const;
64 VkDeviceSize getAllocatedBufferSize() const;
65
66 VkResult allocate(VkDeviceSize size,
67 VkDeviceSize alignment,
68 VmaVirtualAllocation *allocationOut,
69 VkDeviceSize *offsetOut);
70 void free(VmaVirtualAllocation allocation, VkDeviceSize offset);
71 VkBool32 isEmpty();
72
hasVirtualBlock()73 bool hasVirtualBlock() const { return mVirtualBlock.valid(); }
74 bool isHostVisible() const;
75 bool isCoherent() const;
76 bool isCached() const;
77 bool isMapped() const;
78 VkResult map(const VkDevice device);
79 void unmap(const VkDevice device);
80 uint8_t *getMappedMemory() const;
81
82 // This should be called whenever this found to be empty. The total number of count of empty is
83 // returned.
84 int32_t getAndIncrementEmptyCounter();
85 void calculateStats(vma::StatInfo *pStatInfo) const;
86
87 private:
88 mutable angle::SimpleMutex mVirtualBlockMutex;
89 VirtualBlock mVirtualBlock;
90
91 Buffer mBuffer;
92 DeviceMemory mDeviceMemory;
93 VkMemoryPropertyFlags mMemoryPropertyFlags;
94
95 // Memory size that user of this object thinks we have.
96 VkDeviceSize mSize;
97 // Memory size that was actually allocated for this object.
98 VkDeviceSize mAllocatedBufferSize;
99 // Memory allocation type used for this object.
100 MemoryAllocationType mMemoryAllocationType;
101 // Memory type index used for the allocation. It can be used to determine the heap index.
102 uint32_t mMemoryTypeIndex;
103
104 uint8_t *mMappedMemory;
105 BufferSerial mSerial;
106 // Heuristic information for pruneEmptyBuffer. This tracks how many times (consecutively) this
107 // buffer block is found to be empty when pruneEmptyBuffer is called. This gets reset whenever
108 // it becomes non-empty.
109 int32_t mCountRemainsEmpty;
110 };
111 using BufferBlockPointer = std::unique_ptr<BufferBlock>;
112 using BufferBlockPointerVector = std::vector<BufferBlockPointer>;
113
114 class BufferBlockGarbageList final : angle::NonCopyable
115 {
116 public:
BufferBlockGarbageList()117 BufferBlockGarbageList() : mBufferBlockQueue(kInitialQueueCapacity) {}
~BufferBlockGarbageList()118 ~BufferBlockGarbageList() { ASSERT(mBufferBlockQueue.empty()); }
119
add(BufferBlock * bufferBlock)120 void add(BufferBlock *bufferBlock)
121 {
122 std::unique_lock<angle::SimpleMutex> lock(mMutex);
123 if (mBufferBlockQueue.full())
124 {
125 size_t newCapacity = mBufferBlockQueue.capacity() << 1;
126 mBufferBlockQueue.updateCapacity(newCapacity);
127 }
128 mBufferBlockQueue.push(bufferBlock);
129 }
130
131 // Number of buffer blocks destroyed is returned.
pruneEmptyBufferBlocks(Renderer * renderer)132 size_t pruneEmptyBufferBlocks(Renderer *renderer)
133 {
134 size_t blocksDestroyed = 0;
135 if (!mBufferBlockQueue.empty())
136 {
137 std::unique_lock<angle::SimpleMutex> lock(mMutex);
138 size_t count = mBufferBlockQueue.size();
139 for (size_t i = 0; i < count; i++)
140 {
141 BufferBlock *block = mBufferBlockQueue.front();
142 mBufferBlockQueue.pop();
143 if (block->isEmpty())
144 {
145 block->destroy(renderer);
146 ++blocksDestroyed;
147 }
148 else
149 {
150 mBufferBlockQueue.push(block);
151 }
152 }
153 }
154 return blocksDestroyed;
155 }
156
empty()157 bool empty() const { return mBufferBlockQueue.empty(); }
158
159 private:
160 static constexpr size_t kInitialQueueCapacity = 4;
161 angle::SimpleMutex mMutex;
162 angle::FixedQueue<BufferBlock *> mBufferBlockQueue;
163 };
164
165 // BufferSuballocation
166 class BufferSuballocation final : angle::NonCopyable
167 {
168 public:
169 BufferSuballocation();
170
171 BufferSuballocation(BufferSuballocation &&other);
172 BufferSuballocation &operator=(BufferSuballocation &&other);
173
174 void destroy(Renderer *renderer);
175
176 void init(BufferBlock *block,
177 VmaVirtualAllocation allocation,
178 VkDeviceSize offset,
179 VkDeviceSize size);
180 void initWithEntireBuffer(ErrorContext *context,
181 Buffer &buffer,
182 MemoryAllocationType memoryAllocationType,
183 uint32_t memoryTypeIndex,
184 DeviceMemory &deviceMemory,
185 VkMemoryPropertyFlags memoryPropertyFlags,
186 VkDeviceSize size,
187 VkDeviceSize allocatedBufferSize);
188
189 const Buffer &getBuffer() const;
190 VkDeviceSize getSize() const;
191 const DeviceMemory &getDeviceMemory() const;
192 VkMemoryMapFlags getMemoryPropertyFlags() const;
193 bool isHostVisible() const;
194 bool isCoherent() const;
195 bool isCached() const;
196 bool isMapped() const;
197 uint8_t *getMappedMemory() const;
198 void flush(Renderer *renderer);
199 void invalidate(Renderer *renderer);
200 VkDeviceSize getOffset() const;
201 bool valid() const;
202 VkResult map(ErrorContext *context);
203 BufferSerial getBlockSerial() const;
204 uint8_t *getBlockMemory() const;
205 VkDeviceSize getBlockMemorySize() const;
isSuballocated()206 bool isSuballocated() const { return mBufferBlock->hasVirtualBlock(); }
getBufferBlock()207 BufferBlock *getBufferBlock() const { return mBufferBlock; }
208
209 private:
210 // Only used by DynamicBuffer where DynamicBuffer does the actual suballocation and pass the
211 // offset/size to this object. Since DynamicBuffer does not have a VMA virtual allocator, they
212 // will be ignored at destroy time. The offset/size is set here mainly for easy retrieval when
213 // the BufferHelper object is passed around.
214 friend class BufferHelper;
215 void setOffsetAndSize(VkDeviceSize offset, VkDeviceSize size);
216
217 BufferBlock *mBufferBlock;
218 VmaVirtualAllocation mAllocation;
219 VkDeviceSize mOffset;
220 VkDeviceSize mSize;
221 };
222
223 class BufferSuballocationGarbage
224 {
225 public:
226 BufferSuballocationGarbage() = default;
BufferSuballocationGarbage(BufferSuballocationGarbage && other)227 BufferSuballocationGarbage(BufferSuballocationGarbage &&other)
228 : mLifetime(other.mLifetime),
229 mSuballocation(std::move(other.mSuballocation)),
230 mBuffer(std::move(other.mBuffer))
231 {}
232 BufferSuballocationGarbage &operator=(BufferSuballocationGarbage &&other)
233 {
234 mLifetime = other.mLifetime;
235 mSuballocation = std::move(other.mSuballocation);
236 mBuffer = std::move(other.mBuffer);
237 return *this;
238 }
BufferSuballocationGarbage(const ResourceUse & use,BufferSuballocation && suballocation,Buffer && buffer)239 BufferSuballocationGarbage(const ResourceUse &use,
240 BufferSuballocation &&suballocation,
241 Buffer &&buffer)
242 : mLifetime(use), mSuballocation(std::move(suballocation)), mBuffer(std::move(buffer))
243 {}
244 ~BufferSuballocationGarbage() = default;
245
246 bool destroyIfComplete(Renderer *renderer);
247 bool hasResourceUseSubmitted(Renderer *renderer) const;
getSize()248 VkDeviceSize getSize() const { return mSuballocation.getSize(); }
isSuballocated()249 bool isSuballocated() const { return mSuballocation.isSuballocated(); }
250
251 private:
252 ResourceUse mLifetime;
253 BufferSuballocation mSuballocation;
254 Buffer mBuffer;
255 };
256
257 // BufferBlock implementation.
getMemoryPropertyFlags()258 ANGLE_INLINE VkMemoryPropertyFlags BufferBlock::getMemoryPropertyFlags() const
259 {
260 return mMemoryPropertyFlags;
261 }
262
getMemorySize()263 ANGLE_INLINE VkDeviceSize BufferBlock::getMemorySize() const
264 {
265 return mSize;
266 }
267
getAllocatedBufferSize()268 ANGLE_INLINE VkDeviceSize BufferBlock::getAllocatedBufferSize() const
269 {
270 return mAllocatedBufferSize;
271 }
272
isEmpty()273 ANGLE_INLINE VkBool32 BufferBlock::isEmpty()
274 {
275 std::unique_lock<angle::SimpleMutex> lock(mVirtualBlockMutex);
276 return vma::IsVirtualBlockEmpty(mVirtualBlock.getHandle());
277 }
278
isHostVisible()279 ANGLE_INLINE bool BufferBlock::isHostVisible() const
280 {
281 return (mMemoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0;
282 }
283
isCoherent()284 ANGLE_INLINE bool BufferBlock::isCoherent() const
285 {
286 return (mMemoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
287 }
288
isCached()289 ANGLE_INLINE bool BufferBlock::isCached() const
290 {
291 return (mMemoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) != 0;
292 }
293
isMapped()294 ANGLE_INLINE bool BufferBlock::isMapped() const
295 {
296 return mMappedMemory != nullptr;
297 }
298
getMappedMemory()299 ANGLE_INLINE uint8_t *BufferBlock::getMappedMemory() const
300 {
301 ASSERT(mMappedMemory != nullptr);
302 return mMappedMemory;
303 }
304
305 // BufferSuballocation implementation.
BufferSuballocation()306 ANGLE_INLINE BufferSuballocation::BufferSuballocation()
307 : mBufferBlock(nullptr), mAllocation(VK_NULL_HANDLE), mOffset(0), mSize(0)
308 {}
309
BufferSuballocation(BufferSuballocation && other)310 ANGLE_INLINE BufferSuballocation::BufferSuballocation(BufferSuballocation &&other)
311 : BufferSuballocation()
312 {
313 *this = std::move(other);
314 }
315
316 ANGLE_INLINE BufferSuballocation &BufferSuballocation::operator=(BufferSuballocation &&other)
317 {
318 std::swap(mBufferBlock, other.mBufferBlock);
319 std::swap(mSize, other.mSize);
320 std::swap(mAllocation, other.mAllocation);
321 std::swap(mOffset, other.mOffset);
322 return *this;
323 }
324
valid()325 ANGLE_INLINE bool BufferSuballocation::valid() const
326 {
327 return mBufferBlock != nullptr;
328 }
329
destroy(Renderer * renderer)330 ANGLE_INLINE void BufferSuballocation::destroy(Renderer *renderer)
331 {
332 if (valid())
333 {
334 ASSERT(mBufferBlock);
335 if (mBufferBlock->hasVirtualBlock())
336 {
337 mBufferBlock->free(mAllocation, mOffset);
338 mBufferBlock = nullptr;
339 }
340 else
341 {
342 // When virtual block is invalid, this is the standalone buffer that are created by
343 // BufferSuballocation::initWithEntireBuffer call. In this case, vmaBufferSuballocation
344 // owns block, we must properly delete the block object.
345 mBufferBlock->destroy(renderer);
346 SafeDelete(mBufferBlock);
347 }
348 mAllocation = VK_NULL_HANDLE;
349 mOffset = 0;
350 mSize = 0;
351 }
352 }
353
init(BufferBlock * block,VmaVirtualAllocation allocation,VkDeviceSize offset,VkDeviceSize size)354 ANGLE_INLINE void BufferSuballocation::init(BufferBlock *block,
355 VmaVirtualAllocation allocation,
356 VkDeviceSize offset,
357 VkDeviceSize size)
358 {
359 ASSERT(!valid());
360 ASSERT(block != nullptr);
361 ASSERT(allocation != VK_NULL_HANDLE);
362 ASSERT(offset != VK_WHOLE_SIZE);
363 mBufferBlock = block;
364 mAllocation = allocation;
365 mOffset = offset;
366 mSize = size;
367 }
368
initWithEntireBuffer(ErrorContext * context,Buffer & buffer,MemoryAllocationType memoryAllocationType,uint32_t memoryTypeIndex,DeviceMemory & deviceMemory,VkMemoryPropertyFlags memoryPropertyFlags,VkDeviceSize size,VkDeviceSize allocatedBufferSize)369 ANGLE_INLINE void BufferSuballocation::initWithEntireBuffer(
370 ErrorContext *context,
371 Buffer &buffer,
372 MemoryAllocationType memoryAllocationType,
373 uint32_t memoryTypeIndex,
374 DeviceMemory &deviceMemory,
375 VkMemoryPropertyFlags memoryPropertyFlags,
376 VkDeviceSize size,
377 VkDeviceSize allocatedBufferSize)
378 {
379 ASSERT(!valid());
380
381 std::unique_ptr<BufferBlock> block = std::make_unique<BufferBlock>();
382 block->initWithoutVirtualBlock(context, buffer, memoryAllocationType, memoryTypeIndex,
383 deviceMemory, memoryPropertyFlags, size, allocatedBufferSize);
384
385 mBufferBlock = block.release();
386 mAllocation = VK_NULL_HANDLE;
387 mOffset = 0;
388 mSize = mBufferBlock->getMemorySize();
389 }
390
getBuffer()391 ANGLE_INLINE const Buffer &BufferSuballocation::getBuffer() const
392 {
393 return mBufferBlock->getBuffer();
394 }
395
getSize()396 ANGLE_INLINE VkDeviceSize BufferSuballocation::getSize() const
397 {
398 return mSize;
399 }
400
getDeviceMemory()401 ANGLE_INLINE const DeviceMemory &BufferSuballocation::getDeviceMemory() const
402 {
403 return mBufferBlock->getDeviceMemory();
404 }
405
getMemoryPropertyFlags()406 ANGLE_INLINE VkMemoryMapFlags BufferSuballocation::getMemoryPropertyFlags() const
407 {
408 return mBufferBlock->getMemoryPropertyFlags();
409 }
410
isHostVisible()411 ANGLE_INLINE bool BufferSuballocation::isHostVisible() const
412 {
413 return mBufferBlock->isHostVisible();
414 }
isCoherent()415 ANGLE_INLINE bool BufferSuballocation::isCoherent() const
416 {
417 return mBufferBlock->isCoherent();
418 }
isCached()419 ANGLE_INLINE bool BufferSuballocation::isCached() const
420 {
421 return mBufferBlock->isCached();
422 }
isMapped()423 ANGLE_INLINE bool BufferSuballocation::isMapped() const
424 {
425 return mBufferBlock->isMapped();
426 }
getMappedMemory()427 ANGLE_INLINE uint8_t *BufferSuballocation::getMappedMemory() const
428 {
429 return mBufferBlock->getMappedMemory() + getOffset();
430 }
431
getOffset()432 ANGLE_INLINE VkDeviceSize BufferSuballocation::getOffset() const
433 {
434 return mOffset;
435 }
436
setOffsetAndSize(VkDeviceSize offset,VkDeviceSize size)437 ANGLE_INLINE void BufferSuballocation::setOffsetAndSize(VkDeviceSize offset, VkDeviceSize size)
438 {
439 mOffset = offset;
440 mSize = size;
441 }
442
getBlockMemory()443 ANGLE_INLINE uint8_t *BufferSuballocation::getBlockMemory() const
444 {
445 return mBufferBlock->getMappedMemory();
446 }
getBlockMemorySize()447 ANGLE_INLINE VkDeviceSize BufferSuballocation::getBlockMemorySize() const
448 {
449 return mBufferBlock->getMemorySize();
450 }
getBlockSerial()451 ANGLE_INLINE BufferSerial BufferSuballocation::getBlockSerial() const
452 {
453 ASSERT(valid());
454 return mBufferBlock->getBufferSerial();
455 }
456 } // namespace vk
457 } // namespace rx
458
459 #endif // LIBANGLE_RENDERER_VULKAN_SUBALLOCATION_H_
460