1 //
2 // Copyright (c) 2017-2022 Advanced Micro Devices, Inc. All rights reserved.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22
23 #include "Tests.h"
24 #include "VmaUsage.h"
25 #include "Common.h"
26 #include <atomic>
27 #include <thread>
28 #include <mutex>
29 #include <functional>
30
31 #ifdef _WIN32
32
33 static const char* CODE_DESCRIPTION = "Foo";
34 static constexpr VkDeviceSize KILOBYTE = 1024;
35 static constexpr VkDeviceSize MEGABYTE = 1024 * 1024;
36
37 extern VkCommandBuffer g_hTemporaryCommandBuffer;
38 extern const VkAllocationCallbacks* g_Allocs;
39 extern bool VK_KHR_buffer_device_address_enabled;
40 extern bool VK_EXT_memory_priority_enabled;
41 extern PFN_vkGetBufferDeviceAddressKHR g_vkGetBufferDeviceAddressKHR;
42 void BeginSingleTimeCommands();
43 void EndSingleTimeCommands();
44 void SetDebugUtilsObjectName(VkObjectType type, uint64_t handle, const char* name);
45
46 #ifndef VMA_DEBUG_MARGIN
47 #define VMA_DEBUG_MARGIN 0
48 #endif
49
50 enum CONFIG_TYPE
51 {
52 CONFIG_TYPE_MINIMUM,
53 CONFIG_TYPE_SMALL,
54 CONFIG_TYPE_AVERAGE,
55 CONFIG_TYPE_LARGE,
56 CONFIG_TYPE_MAXIMUM,
57 CONFIG_TYPE_COUNT
58 };
59
60 static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_AVERAGE;
61
62 enum class FREE_ORDER { FORWARD, BACKWARD, RANDOM, COUNT };
63
64 static const char* FREE_ORDER_NAMES[] =
65 {
66 "FORWARD",
67 "BACKWARD",
68 "RANDOM",
69 };
70
71 // Copy of internal VmaAlgorithmToStr.
AlgorithmToStr(uint32_t algorithm)72 static const char* AlgorithmToStr(uint32_t algorithm)
73 {
74 switch(algorithm)
75 {
76 case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT:
77 return "Linear";
78 case 0:
79 return "TLSF";
80 default:
81 assert(0);
82 return "";
83 }
84 }
85
VirtualAlgorithmToStr(uint32_t algorithm)86 static const char* VirtualAlgorithmToStr(uint32_t algorithm)
87 {
88 switch (algorithm)
89 {
90 case VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT:
91 return "Linear";
92 case 0:
93 return "TLSF";
94 default:
95 assert(0);
96 return "";
97 }
98 }
99
DefragmentationAlgorithmToStr(uint32_t algorithm)100 static const wchar_t* DefragmentationAlgorithmToStr(uint32_t algorithm)
101 {
102 switch (algorithm)
103 {
104 case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT:
105 return L"Balanced";
106 case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT:
107 return L"Fast";
108 case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT:
109 return L"Full";
110 case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT:
111 return L"Extensive";
112 case 0:
113 return L"Default";
114 default:
115 assert(0);
116 return L"";
117 }
118 }
119
120 struct AllocationSize
121 {
122 uint32_t Probability;
123 VkDeviceSize BufferSizeMin, BufferSizeMax;
124 uint32_t ImageSizeMin, ImageSizeMax;
125 };
126
127 struct Config
128 {
129 uint32_t RandSeed;
130 VkDeviceSize BeginBytesToAllocate;
131 uint32_t AdditionalOperationCount;
132 VkDeviceSize MaxBytesToAllocate;
133 uint32_t MemUsageProbability[4]; // For VMA_MEMORY_USAGE_*
134 std::vector<AllocationSize> AllocationSizes;
135 uint32_t ThreadCount;
136 uint32_t ThreadsUsingCommonAllocationsProbabilityPercent;
137 FREE_ORDER FreeOrder;
138 VmaAllocationCreateFlags AllocationStrategy; // For VMA_ALLOCATION_CREATE_STRATEGY_*
139 };
140
141 struct Result
142 {
143 duration TotalTime;
144 duration AllocationTimeMin, AllocationTimeAvg, AllocationTimeMax;
145 duration DeallocationTimeMin, DeallocationTimeAvg, DeallocationTimeMax;
146 VkDeviceSize TotalMemoryAllocated;
147 VkDeviceSize FreeRangeSizeAvg, FreeRangeSizeMax;
148 };
149
150 struct PoolTestConfig
151 {
152 uint32_t RandSeed;
153 uint32_t ThreadCount;
154 VkDeviceSize PoolSize;
155 uint32_t FrameCount;
156 uint32_t TotalItemCount;
157 // Range for number of items used in each frame.
158 uint32_t UsedItemCountMin, UsedItemCountMax;
159 // Percent of items to make unused, and possibly make some others used in each frame.
160 uint32_t ItemsToMakeUnusedPercent;
161 std::vector<AllocationSize> AllocationSizes;
162
CalcAvgResourceSizePoolTestConfig163 VkDeviceSize CalcAvgResourceSize() const
164 {
165 uint32_t probabilitySum = 0;
166 VkDeviceSize sizeSum = 0;
167 for(size_t i = 0; i < AllocationSizes.size(); ++i)
168 {
169 const AllocationSize& allocSize = AllocationSizes[i];
170 if(allocSize.BufferSizeMax > 0)
171 sizeSum += (allocSize.BufferSizeMin + allocSize.BufferSizeMax) / 2 * allocSize.Probability;
172 else
173 {
174 const VkDeviceSize avgDimension = (allocSize.ImageSizeMin + allocSize.ImageSizeMax) / 2;
175 sizeSum += avgDimension * avgDimension * 4 * allocSize.Probability;
176 }
177 probabilitySum += allocSize.Probability;
178 }
179 return sizeSum / probabilitySum;
180 }
181
UsesBuffersPoolTestConfig182 bool UsesBuffers() const
183 {
184 for(size_t i = 0; i < AllocationSizes.size(); ++i)
185 if(AllocationSizes[i].BufferSizeMax > 0)
186 return true;
187 return false;
188 }
189
UsesImagesPoolTestConfig190 bool UsesImages() const
191 {
192 for(size_t i = 0; i < AllocationSizes.size(); ++i)
193 if(AllocationSizes[i].ImageSizeMax > 0)
194 return true;
195 return false;
196 }
197 };
198
199 struct PoolTestResult
200 {
201 duration TotalTime;
202 duration AllocationTimeMin, AllocationTimeAvg, AllocationTimeMax;
203 duration DeallocationTimeMin, DeallocationTimeAvg, DeallocationTimeMax;
204 size_t FailedAllocationCount, FailedAllocationTotalSize;
205 };
206
207 static const uint32_t IMAGE_BYTES_PER_PIXEL = 1;
208
209 uint32_t g_FrameIndex = 0;
210
211 struct BufferInfo
212 {
213 VkBuffer Buffer = VK_NULL_HANDLE;
214 VmaAllocation Allocation = VK_NULL_HANDLE;
215 };
216
MemoryTypeToHeap(uint32_t memoryTypeIndex)217 static uint32_t MemoryTypeToHeap(uint32_t memoryTypeIndex)
218 {
219 const VkPhysicalDeviceMemoryProperties* props;
220 vmaGetMemoryProperties(g_hAllocator, &props);
221 return props->memoryTypes[memoryTypeIndex].heapIndex;
222 }
223
GetAllocationStrategyCount()224 static uint32_t GetAllocationStrategyCount()
225 {
226 switch(ConfigType)
227 {
228 case CONFIG_TYPE_MINIMUM:
229 case CONFIG_TYPE_SMALL:
230 return 1;
231 default: assert(0);
232 case CONFIG_TYPE_AVERAGE:
233 case CONFIG_TYPE_LARGE:
234 case CONFIG_TYPE_MAXIMUM:
235 return 2;
236 }
237 }
238
GetAllocationStrategyName(VmaAllocationCreateFlags allocStrategy)239 static const char* GetAllocationStrategyName(VmaAllocationCreateFlags allocStrategy)
240 {
241 switch(allocStrategy)
242 {
243 case VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT: return "MIN_MEMORY"; break;
244 case VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT: return "MIN_TIME"; break;
245 case 0: return "Default"; break;
246 default: assert(0); return "";
247 }
248 }
249
GetVirtualAllocationStrategyName(VmaVirtualAllocationCreateFlags allocStrategy)250 static const char* GetVirtualAllocationStrategyName(VmaVirtualAllocationCreateFlags allocStrategy)
251 {
252 switch (allocStrategy)
253 {
254 case VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT: return "MIN_MEMORY"; break;
255 case VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT: return "MIN_TIME"; break;
256 case 0: return "Default"; break;
257 default: assert(0); return "";
258 }
259 }
260
InitResult(Result & outResult)261 static void InitResult(Result& outResult)
262 {
263 outResult.TotalTime = duration::zero();
264 outResult.AllocationTimeMin = duration::max();
265 outResult.AllocationTimeAvg = duration::zero();
266 outResult.AllocationTimeMax = duration::min();
267 outResult.DeallocationTimeMin = duration::max();
268 outResult.DeallocationTimeAvg = duration::zero();
269 outResult.DeallocationTimeMax = duration::min();
270 outResult.TotalMemoryAllocated = 0;
271 outResult.FreeRangeSizeAvg = 0;
272 outResult.FreeRangeSizeMax = 0;
273 }
274
275 class TimeRegisterObj
276 {
277 public:
TimeRegisterObj(duration & min,duration & sum,duration & max)278 TimeRegisterObj(duration& min, duration& sum, duration& max) :
279 m_Min(min),
280 m_Sum(sum),
281 m_Max(max),
282 m_TimeBeg(std::chrono::high_resolution_clock::now())
283 {
284 }
285
~TimeRegisterObj()286 ~TimeRegisterObj()
287 {
288 duration d = std::chrono::high_resolution_clock::now() - m_TimeBeg;
289 m_Sum += d;
290 if(d < m_Min) m_Min = d;
291 if(d > m_Max) m_Max = d;
292 }
293
294 private:
295 duration& m_Min;
296 duration& m_Sum;
297 duration& m_Max;
298 time_point m_TimeBeg;
299 };
300
301 struct PoolTestThreadResult
302 {
303 duration AllocationTimeMin, AllocationTimeSum, AllocationTimeMax;
304 duration DeallocationTimeMin, DeallocationTimeSum, DeallocationTimeMax;
305 size_t AllocationCount, DeallocationCount;
306 size_t FailedAllocationCount, FailedAllocationTotalSize;
307 };
308
309 class AllocationTimeRegisterObj : public TimeRegisterObj
310 {
311 public:
AllocationTimeRegisterObj(Result & result)312 AllocationTimeRegisterObj(Result& result) :
313 TimeRegisterObj(result.AllocationTimeMin, result.AllocationTimeAvg, result.AllocationTimeMax)
314 {
315 }
316 };
317
318 class DeallocationTimeRegisterObj : public TimeRegisterObj
319 {
320 public:
DeallocationTimeRegisterObj(Result & result)321 DeallocationTimeRegisterObj(Result& result) :
322 TimeRegisterObj(result.DeallocationTimeMin, result.DeallocationTimeAvg, result.DeallocationTimeMax)
323 {
324 }
325 };
326
327 class PoolAllocationTimeRegisterObj : public TimeRegisterObj
328 {
329 public:
PoolAllocationTimeRegisterObj(PoolTestThreadResult & result)330 PoolAllocationTimeRegisterObj(PoolTestThreadResult& result) :
331 TimeRegisterObj(result.AllocationTimeMin, result.AllocationTimeSum, result.AllocationTimeMax)
332 {
333 }
334 };
335
336 class PoolDeallocationTimeRegisterObj : public TimeRegisterObj
337 {
338 public:
PoolDeallocationTimeRegisterObj(PoolTestThreadResult & result)339 PoolDeallocationTimeRegisterObj(PoolTestThreadResult& result) :
340 TimeRegisterObj(result.DeallocationTimeMin, result.DeallocationTimeSum, result.DeallocationTimeMax)
341 {
342 }
343 };
344
CurrentTimeToStr(std::string & out)345 static void CurrentTimeToStr(std::string& out)
346 {
347 time_t rawTime; time(&rawTime);
348 struct tm timeInfo; localtime_s(&timeInfo, &rawTime);
349 char timeStr[128];
350 strftime(timeStr, _countof(timeStr), "%c", &timeInfo);
351 out = timeStr;
352 }
353
MainTest(Result & outResult,const Config & config)354 VkResult MainTest(Result& outResult, const Config& config)
355 {
356 assert(config.ThreadCount > 0);
357
358 InitResult(outResult);
359
360 RandomNumberGenerator mainRand{config.RandSeed};
361
362 time_point timeBeg = std::chrono::high_resolution_clock::now();
363
364 std::atomic<size_t> allocationCount = 0;
365 VkResult res = VK_SUCCESS;
366
367 uint32_t memUsageProbabilitySum =
368 config.MemUsageProbability[0] + config.MemUsageProbability[1] +
369 config.MemUsageProbability[2] + config.MemUsageProbability[3];
370 assert(memUsageProbabilitySum > 0);
371
372 uint32_t allocationSizeProbabilitySum = std::accumulate(
373 config.AllocationSizes.begin(),
374 config.AllocationSizes.end(),
375 0u,
376 [](uint32_t sum, const AllocationSize& allocSize) {
377 return sum + allocSize.Probability;
378 });
379
380 struct Allocation
381 {
382 VkBuffer Buffer;
383 VkImage Image;
384 VmaAllocation Alloc;
385 };
386
387 std::vector<Allocation> commonAllocations;
388 std::mutex commonAllocationsMutex;
389
390 auto Allocate = [&](
391 VkDeviceSize bufferSize,
392 const VkExtent2D imageExtent,
393 RandomNumberGenerator& localRand,
394 VkDeviceSize& totalAllocatedBytes,
395 std::vector<Allocation>& allocations) -> VkResult
396 {
397 assert((bufferSize == 0) != (imageExtent.width == 0 && imageExtent.height == 0));
398
399 uint32_t memUsageIndex = 0;
400 uint32_t memUsageRand = localRand.Generate() % memUsageProbabilitySum;
401 while(memUsageRand >= config.MemUsageProbability[memUsageIndex])
402 memUsageRand -= config.MemUsageProbability[memUsageIndex++];
403
404 VmaAllocationCreateInfo memReq = {};
405 memReq.usage = (VmaMemoryUsage)(VMA_MEMORY_USAGE_GPU_ONLY + memUsageIndex);
406 memReq.flags |= config.AllocationStrategy;
407
408 Allocation allocation = {};
409 VmaAllocationInfo allocationInfo;
410
411 // Buffer
412 if(bufferSize > 0)
413 {
414 assert(imageExtent.width == 0);
415 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
416 bufferInfo.size = bufferSize;
417 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
418
419 {
420 AllocationTimeRegisterObj timeRegisterObj{outResult};
421 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &memReq, &allocation.Buffer, &allocation.Alloc, &allocationInfo);
422 }
423 }
424 // Image
425 else
426 {
427 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
428 imageInfo.imageType = VK_IMAGE_TYPE_2D;
429 imageInfo.extent.width = imageExtent.width;
430 imageInfo.extent.height = imageExtent.height;
431 imageInfo.extent.depth = 1;
432 imageInfo.mipLevels = 1;
433 imageInfo.arrayLayers = 1;
434 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
435 imageInfo.tiling = memReq.usage == VMA_MEMORY_USAGE_GPU_ONLY ?
436 VK_IMAGE_TILING_OPTIMAL :
437 VK_IMAGE_TILING_LINEAR;
438 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
439 switch(memReq.usage)
440 {
441 case VMA_MEMORY_USAGE_GPU_ONLY:
442 switch(localRand.Generate() % 3)
443 {
444 case 0:
445 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
446 break;
447 case 1:
448 imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
449 break;
450 case 2:
451 imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
452 break;
453 }
454 break;
455 case VMA_MEMORY_USAGE_CPU_ONLY:
456 case VMA_MEMORY_USAGE_CPU_TO_GPU:
457 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
458 break;
459 case VMA_MEMORY_USAGE_GPU_TO_CPU:
460 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
461 break;
462 }
463 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
464 imageInfo.flags = 0;
465
466 {
467 AllocationTimeRegisterObj timeRegisterObj{outResult};
468 res = vmaCreateImage(g_hAllocator, &imageInfo, &memReq, &allocation.Image, &allocation.Alloc, &allocationInfo);
469 }
470 }
471
472 if(res == VK_SUCCESS)
473 {
474 ++allocationCount;
475 totalAllocatedBytes += allocationInfo.size;
476 bool useCommonAllocations = localRand.Generate() % 100 < config.ThreadsUsingCommonAllocationsProbabilityPercent;
477 if(useCommonAllocations)
478 {
479 std::unique_lock<std::mutex> lock(commonAllocationsMutex);
480 commonAllocations.push_back(allocation);
481 }
482 else
483 allocations.push_back(allocation);
484 }
485 else
486 {
487 TEST(0);
488 }
489 return res;
490 };
491
492 auto GetNextAllocationSize = [&](
493 VkDeviceSize& outBufSize,
494 VkExtent2D& outImageSize,
495 RandomNumberGenerator& localRand)
496 {
497 outBufSize = 0;
498 outImageSize = {0, 0};
499
500 uint32_t allocSizeIndex = 0;
501 uint32_t r = localRand.Generate() % allocationSizeProbabilitySum;
502 while(r >= config.AllocationSizes[allocSizeIndex].Probability)
503 r -= config.AllocationSizes[allocSizeIndex++].Probability;
504
505 const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex];
506 if(allocSize.BufferSizeMax > 0)
507 {
508 assert(allocSize.ImageSizeMax == 0);
509 if(allocSize.BufferSizeMax == allocSize.BufferSizeMin)
510 outBufSize = allocSize.BufferSizeMin;
511 else
512 {
513 outBufSize = allocSize.BufferSizeMin + localRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin);
514 outBufSize = outBufSize / 16 * 16;
515 }
516 }
517 else
518 {
519 if(allocSize.ImageSizeMax == allocSize.ImageSizeMin)
520 outImageSize.width = outImageSize.height = allocSize.ImageSizeMax;
521 else
522 {
523 outImageSize.width = allocSize.ImageSizeMin + localRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
524 outImageSize.height = allocSize.ImageSizeMin + localRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
525 }
526 }
527 };
528
529 std::atomic<uint32_t> numThreadsReachedMaxAllocations = 0;
530 HANDLE threadsFinishEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
531
532 auto ThreadProc = [&](uint32_t randSeed) -> void
533 {
534 RandomNumberGenerator threadRand(randSeed);
535 VkDeviceSize threadTotalAllocatedBytes = 0;
536 std::vector<Allocation> threadAllocations;
537 VkDeviceSize threadBeginBytesToAllocate = config.BeginBytesToAllocate / config.ThreadCount;
538 VkDeviceSize threadMaxBytesToAllocate = config.MaxBytesToAllocate / config.ThreadCount;
539 uint32_t threadAdditionalOperationCount = config.AdditionalOperationCount / config.ThreadCount;
540
541 // BEGIN ALLOCATIONS
542 for(;;)
543 {
544 VkDeviceSize bufferSize = 0;
545 VkExtent2D imageExtent = {};
546 GetNextAllocationSize(bufferSize, imageExtent, threadRand);
547 if(threadTotalAllocatedBytes + bufferSize + imageExtent.width * imageExtent.height * IMAGE_BYTES_PER_PIXEL <
548 threadBeginBytesToAllocate)
549 {
550 if(Allocate(bufferSize, imageExtent, threadRand, threadTotalAllocatedBytes, threadAllocations) != VK_SUCCESS)
551 break;
552 }
553 else
554 break;
555 }
556
557 // ADDITIONAL ALLOCATIONS AND FREES
558 for(size_t i = 0; i < threadAdditionalOperationCount; ++i)
559 {
560 VkDeviceSize bufferSize = 0;
561 VkExtent2D imageExtent = {};
562 GetNextAllocationSize(bufferSize, imageExtent, threadRand);
563
564 // true = allocate, false = free
565 bool allocate = threadRand.Generate() % 2 != 0;
566
567 if(allocate)
568 {
569 if(threadTotalAllocatedBytes +
570 bufferSize +
571 imageExtent.width * imageExtent.height * IMAGE_BYTES_PER_PIXEL <
572 threadMaxBytesToAllocate)
573 {
574 if(Allocate(bufferSize, imageExtent, threadRand, threadTotalAllocatedBytes, threadAllocations) != VK_SUCCESS)
575 break;
576 }
577 }
578 else
579 {
580 bool useCommonAllocations = threadRand.Generate() % 100 < config.ThreadsUsingCommonAllocationsProbabilityPercent;
581 if(useCommonAllocations)
582 {
583 std::unique_lock<std::mutex> lock(commonAllocationsMutex);
584 if(!commonAllocations.empty())
585 {
586 size_t indexToFree = threadRand.Generate() % commonAllocations.size();
587 VmaAllocationInfo allocationInfo;
588 vmaGetAllocationInfo(g_hAllocator, commonAllocations[indexToFree].Alloc, &allocationInfo);
589 if(threadTotalAllocatedBytes >= allocationInfo.size)
590 {
591 DeallocationTimeRegisterObj timeRegisterObj{outResult};
592 if(commonAllocations[indexToFree].Buffer != VK_NULL_HANDLE)
593 vmaDestroyBuffer(g_hAllocator, commonAllocations[indexToFree].Buffer, commonAllocations[indexToFree].Alloc);
594 else
595 vmaDestroyImage(g_hAllocator, commonAllocations[indexToFree].Image, commonAllocations[indexToFree].Alloc);
596 threadTotalAllocatedBytes -= allocationInfo.size;
597 commonAllocations.erase(commonAllocations.begin() + indexToFree);
598 }
599 }
600 }
601 else
602 {
603 if(!threadAllocations.empty())
604 {
605 size_t indexToFree = threadRand.Generate() % threadAllocations.size();
606 VmaAllocationInfo allocationInfo;
607 vmaGetAllocationInfo(g_hAllocator, threadAllocations[indexToFree].Alloc, &allocationInfo);
608 if(threadTotalAllocatedBytes >= allocationInfo.size)
609 {
610 DeallocationTimeRegisterObj timeRegisterObj{outResult};
611 if(threadAllocations[indexToFree].Buffer != VK_NULL_HANDLE)
612 vmaDestroyBuffer(g_hAllocator, threadAllocations[indexToFree].Buffer, threadAllocations[indexToFree].Alloc);
613 else
614 vmaDestroyImage(g_hAllocator, threadAllocations[indexToFree].Image, threadAllocations[indexToFree].Alloc);
615 threadTotalAllocatedBytes -= allocationInfo.size;
616 threadAllocations.erase(threadAllocations.begin() + indexToFree);
617 }
618 }
619 }
620 }
621 }
622
623 ++numThreadsReachedMaxAllocations;
624
625 WaitForSingleObject(threadsFinishEvent, INFINITE);
626
627 // DEALLOCATION
628 while(!threadAllocations.empty())
629 {
630 size_t indexToFree = 0;
631 switch(config.FreeOrder)
632 {
633 case FREE_ORDER::FORWARD:
634 indexToFree = 0;
635 break;
636 case FREE_ORDER::BACKWARD:
637 indexToFree = threadAllocations.size() - 1;
638 break;
639 case FREE_ORDER::RANDOM:
640 indexToFree = mainRand.Generate() % threadAllocations.size();
641 break;
642 }
643
644 {
645 DeallocationTimeRegisterObj timeRegisterObj{outResult};
646 if(threadAllocations[indexToFree].Buffer != VK_NULL_HANDLE)
647 vmaDestroyBuffer(g_hAllocator, threadAllocations[indexToFree].Buffer, threadAllocations[indexToFree].Alloc);
648 else
649 vmaDestroyImage(g_hAllocator, threadAllocations[indexToFree].Image, threadAllocations[indexToFree].Alloc);
650 }
651 threadAllocations.erase(threadAllocations.begin() + indexToFree);
652 }
653 };
654
655 uint32_t threadRandSeed = mainRand.Generate();
656 std::vector<std::thread> bkgThreads;
657 for(size_t i = 0; i < config.ThreadCount; ++i)
658 {
659 bkgThreads.emplace_back(std::bind(ThreadProc, threadRandSeed + (uint32_t)i));
660 }
661
662 // Wait for threads reached max allocations
663 while(numThreadsReachedMaxAllocations < config.ThreadCount)
664 Sleep(0);
665
666 // CALCULATE MEMORY STATISTICS ON FINAL USAGE
667 VmaTotalStatistics vmaStats = {};
668 vmaCalculateStatistics(g_hAllocator, &vmaStats);
669 outResult.TotalMemoryAllocated = vmaStats.total.statistics.blockBytes;
670 outResult.FreeRangeSizeMax = vmaStats.total.unusedRangeSizeMax;
671 outResult.FreeRangeSizeAvg = round_div<VkDeviceSize>(vmaStats.total.statistics.blockBytes - vmaStats.total.statistics.allocationBytes, vmaStats.total.unusedRangeCount);
672
673 // Signal threads to deallocate
674 SetEvent(threadsFinishEvent);
675
676 // Wait for threads finished
677 for(size_t i = 0; i < bkgThreads.size(); ++i)
678 bkgThreads[i].join();
679 bkgThreads.clear();
680
681 CloseHandle(threadsFinishEvent);
682
683 // Deallocate remaining common resources
684 while(!commonAllocations.empty())
685 {
686 size_t indexToFree = 0;
687 switch(config.FreeOrder)
688 {
689 case FREE_ORDER::FORWARD:
690 indexToFree = 0;
691 break;
692 case FREE_ORDER::BACKWARD:
693 indexToFree = commonAllocations.size() - 1;
694 break;
695 case FREE_ORDER::RANDOM:
696 indexToFree = mainRand.Generate() % commonAllocations.size();
697 break;
698 }
699
700 {
701 DeallocationTimeRegisterObj timeRegisterObj{outResult};
702 if(commonAllocations[indexToFree].Buffer != VK_NULL_HANDLE)
703 vmaDestroyBuffer(g_hAllocator, commonAllocations[indexToFree].Buffer, commonAllocations[indexToFree].Alloc);
704 else
705 vmaDestroyImage(g_hAllocator, commonAllocations[indexToFree].Image, commonAllocations[indexToFree].Alloc);
706 }
707 commonAllocations.erase(commonAllocations.begin() + indexToFree);
708 }
709
710 if(allocationCount)
711 {
712 outResult.AllocationTimeAvg /= allocationCount;
713 outResult.DeallocationTimeAvg /= allocationCount;
714 }
715
716 outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg;
717
718 return res;
719 }
720
SaveAllocatorStatsToFile(const wchar_t * filePath,bool detailed=true)721 void SaveAllocatorStatsToFile(const wchar_t* filePath, bool detailed = true)
722 {
723 #if !defined(VMA_STATS_STRING_ENABLED) || VMA_STATS_STRING_ENABLED
724 wprintf(L"Saving JSON dump to file \"%s\"\n", filePath);
725 char* stats;
726 vmaBuildStatsString(g_hAllocator, &stats, detailed ? VK_TRUE : VK_FALSE);
727 SaveFile(filePath, stats, strlen(stats));
728 vmaFreeStatsString(g_hAllocator, stats);
729 #endif
730 }
731
732 struct AllocInfo
733 {
734 VmaAllocation m_Allocation = VK_NULL_HANDLE;
735 VkBuffer m_Buffer = VK_NULL_HANDLE;
736 VkImage m_Image = VK_NULL_HANDLE;
737 VkImageLayout m_ImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
738 uint32_t m_StartValue = 0;
739 union
740 {
741 VkBufferCreateInfo m_BufferInfo;
742 VkImageCreateInfo m_ImageInfo;
743 };
744
745 // After defragmentation.
746 VkBuffer m_NewBuffer = VK_NULL_HANDLE;
747 VkImage m_NewImage = VK_NULL_HANDLE;
748
749 void CreateBuffer(
750 const VkBufferCreateInfo& bufCreateInfo,
751 const VmaAllocationCreateInfo& allocCreateInfo);
752 void CreateImage(
753 const VkImageCreateInfo& imageCreateInfo,
754 const VmaAllocationCreateInfo& allocCreateInfo,
755 VkImageLayout layout);
756 void Destroy();
757 };
758
CreateBuffer(const VkBufferCreateInfo & bufCreateInfo,const VmaAllocationCreateInfo & allocCreateInfo)759 void AllocInfo::CreateBuffer(
760 const VkBufferCreateInfo& bufCreateInfo,
761 const VmaAllocationCreateInfo& allocCreateInfo)
762 {
763 m_BufferInfo = bufCreateInfo;
764 VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &m_Buffer, &m_Allocation, nullptr);
765 TEST(res == VK_SUCCESS);
766 }
CreateImage(const VkImageCreateInfo & imageCreateInfo,const VmaAllocationCreateInfo & allocCreateInfo,VkImageLayout layout)767 void AllocInfo::CreateImage(
768 const VkImageCreateInfo& imageCreateInfo,
769 const VmaAllocationCreateInfo& allocCreateInfo,
770 VkImageLayout layout)
771 {
772 m_ImageInfo = imageCreateInfo;
773 m_ImageLayout = layout;
774 VkResult res = vmaCreateImage(g_hAllocator, &imageCreateInfo, &allocCreateInfo, &m_Image, &m_Allocation, nullptr);
775 TEST(res == VK_SUCCESS);
776 }
777
Destroy()778 void AllocInfo::Destroy()
779 {
780 if(m_Image)
781 {
782 assert(!m_Buffer);
783 vkDestroyImage(g_hDevice, m_Image, g_Allocs);
784 m_Image = VK_NULL_HANDLE;
785 }
786 if(m_Buffer)
787 {
788 assert(!m_Image);
789 vkDestroyBuffer(g_hDevice, m_Buffer, g_Allocs);
790 m_Buffer = VK_NULL_HANDLE;
791 }
792 if(m_Allocation)
793 {
794 vmaFreeMemory(g_hAllocator, m_Allocation);
795 m_Allocation = VK_NULL_HANDLE;
796 }
797 }
798
799 class StagingBufferCollection
800 {
801 public:
StagingBufferCollection()802 StagingBufferCollection() { }
803 ~StagingBufferCollection();
804 // Returns false if maximum total size of buffers would be exceeded.
805 bool AcquireBuffer(VkDeviceSize size, VkBuffer& outBuffer, void*& outMappedPtr);
806 void ReleaseAllBuffers();
807
808 private:
809 static const VkDeviceSize MAX_TOTAL_SIZE = 256ull * 1024 * 1024;
810 struct BufInfo
811 {
812 VmaAllocation Allocation = VK_NULL_HANDLE;
813 VkBuffer Buffer = VK_NULL_HANDLE;
814 VkDeviceSize Size = VK_WHOLE_SIZE;
815 void* MappedPtr = nullptr;
816 bool Used = false;
817 };
818 std::vector<BufInfo> m_Bufs;
819 // Including both used and unused.
820 VkDeviceSize m_TotalSize = 0;
821 };
822
~StagingBufferCollection()823 StagingBufferCollection::~StagingBufferCollection()
824 {
825 for(size_t i = m_Bufs.size(); i--; )
826 {
827 vmaDestroyBuffer(g_hAllocator, m_Bufs[i].Buffer, m_Bufs[i].Allocation);
828 }
829 }
830
AcquireBuffer(VkDeviceSize size,VkBuffer & outBuffer,void * & outMappedPtr)831 bool StagingBufferCollection::AcquireBuffer(VkDeviceSize size, VkBuffer& outBuffer, void*& outMappedPtr)
832 {
833 assert(size <= MAX_TOTAL_SIZE);
834
835 // Try to find existing unused buffer with best size.
836 size_t bestIndex = SIZE_MAX;
837 for(size_t i = 0, count = m_Bufs.size(); i < count; ++i)
838 {
839 BufInfo& currBufInfo = m_Bufs[i];
840 if(!currBufInfo.Used && currBufInfo.Size >= size &&
841 (bestIndex == SIZE_MAX || currBufInfo.Size < m_Bufs[bestIndex].Size))
842 {
843 bestIndex = i;
844 }
845 }
846
847 if(bestIndex != SIZE_MAX)
848 {
849 m_Bufs[bestIndex].Used = true;
850 outBuffer = m_Bufs[bestIndex].Buffer;
851 outMappedPtr = m_Bufs[bestIndex].MappedPtr;
852 return true;
853 }
854
855 // Allocate new buffer with requested size.
856 if(m_TotalSize + size <= MAX_TOTAL_SIZE)
857 {
858 BufInfo bufInfo;
859 bufInfo.Size = size;
860 bufInfo.Used = true;
861
862 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
863 bufCreateInfo.size = size;
864 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
865
866 VmaAllocationCreateInfo allocCreateInfo = {};
867 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
868 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
869
870 VmaAllocationInfo allocInfo;
871 VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo);
872 bufInfo.MappedPtr = allocInfo.pMappedData;
873 TEST(res == VK_SUCCESS && bufInfo.MappedPtr);
874
875 outBuffer = bufInfo.Buffer;
876 outMappedPtr = bufInfo.MappedPtr;
877
878 m_Bufs.push_back(std::move(bufInfo));
879
880 m_TotalSize += size;
881
882 return true;
883 }
884
885 // There are some unused but smaller buffers: Free them and try again.
886 bool hasUnused = false;
887 for(size_t i = 0, count = m_Bufs.size(); i < count; ++i)
888 {
889 if(!m_Bufs[i].Used)
890 {
891 hasUnused = true;
892 break;
893 }
894 }
895 if(hasUnused)
896 {
897 for(size_t i = m_Bufs.size(); i--; )
898 {
899 if(!m_Bufs[i].Used)
900 {
901 m_TotalSize -= m_Bufs[i].Size;
902 vmaDestroyBuffer(g_hAllocator, m_Bufs[i].Buffer, m_Bufs[i].Allocation);
903 m_Bufs.erase(m_Bufs.begin() + i);
904 }
905 }
906
907 return AcquireBuffer(size, outBuffer, outMappedPtr);
908 }
909
910 return false;
911 }
912
ReleaseAllBuffers()913 void StagingBufferCollection::ReleaseAllBuffers()
914 {
915 for(size_t i = 0, count = m_Bufs.size(); i < count; ++i)
916 {
917 m_Bufs[i].Used = false;
918 }
919 }
920
UploadGpuData(const AllocInfo * allocInfo,size_t allocInfoCount)921 static void UploadGpuData(const AllocInfo* allocInfo, size_t allocInfoCount)
922 {
923 StagingBufferCollection stagingBufs;
924
925 bool cmdBufferStarted = false;
926 for(size_t allocInfoIndex = 0; allocInfoIndex < allocInfoCount; ++allocInfoIndex)
927 {
928 const AllocInfo& currAllocInfo = allocInfo[allocInfoIndex];
929 if(currAllocInfo.m_Buffer)
930 {
931 const VkDeviceSize size = currAllocInfo.m_BufferInfo.size;
932
933 VkBuffer stagingBuf = VK_NULL_HANDLE;
934 void* stagingBufMappedPtr = nullptr;
935 if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr))
936 {
937 TEST(cmdBufferStarted);
938 EndSingleTimeCommands();
939 stagingBufs.ReleaseAllBuffers();
940 cmdBufferStarted = false;
941
942 bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr);
943 TEST(ok);
944 }
945
946 // Fill staging buffer.
947 {
948 assert(size % sizeof(uint32_t) == 0);
949 uint32_t* stagingValPtr = (uint32_t*)stagingBufMappedPtr;
950 uint32_t val = currAllocInfo.m_StartValue;
951 for(size_t i = 0; i < size / sizeof(uint32_t); ++i)
952 {
953 *stagingValPtr = val;
954 ++stagingValPtr;
955 ++val;
956 }
957 }
958
959 // Issue copy command from staging buffer to destination buffer.
960 if(!cmdBufferStarted)
961 {
962 cmdBufferStarted = true;
963 BeginSingleTimeCommands();
964 }
965
966 VkBufferCopy copy = {};
967 copy.srcOffset = 0;
968 copy.dstOffset = 0;
969 copy.size = size;
970 vkCmdCopyBuffer(g_hTemporaryCommandBuffer, stagingBuf, currAllocInfo.m_Buffer, 1, ©);
971 }
972 else
973 {
974 TEST(currAllocInfo.m_ImageInfo.format == VK_FORMAT_R8G8B8A8_UNORM && "Only RGBA8 images are currently supported.");
975 TEST(currAllocInfo.m_ImageInfo.mipLevels == 1 && "Only single mip images are currently supported.");
976
977 const VkDeviceSize size = (VkDeviceSize)currAllocInfo.m_ImageInfo.extent.width * currAllocInfo.m_ImageInfo.extent.height * sizeof(uint32_t);
978
979 VkBuffer stagingBuf = VK_NULL_HANDLE;
980 void* stagingBufMappedPtr = nullptr;
981 if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr))
982 {
983 TEST(cmdBufferStarted);
984 EndSingleTimeCommands();
985 stagingBufs.ReleaseAllBuffers();
986 cmdBufferStarted = false;
987
988 bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr);
989 TEST(ok);
990 }
991
992 // Fill staging buffer.
993 {
994 assert(size % sizeof(uint32_t) == 0);
995 uint32_t *stagingValPtr = (uint32_t *)stagingBufMappedPtr;
996 uint32_t val = currAllocInfo.m_StartValue;
997 for(size_t i = 0; i < size / sizeof(uint32_t); ++i)
998 {
999 *stagingValPtr = val;
1000 ++stagingValPtr;
1001 ++val;
1002 }
1003 }
1004
1005 // Issue copy command from staging buffer to destination buffer.
1006 if(!cmdBufferStarted)
1007 {
1008 cmdBufferStarted = true;
1009 BeginSingleTimeCommands();
1010 }
1011
1012
1013 // Transfer to transfer dst layout
1014 VkImageSubresourceRange subresourceRange = {
1015 VK_IMAGE_ASPECT_COLOR_BIT,
1016 0, VK_REMAINING_MIP_LEVELS,
1017 0, VK_REMAINING_ARRAY_LAYERS
1018 };
1019
1020 VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
1021 barrier.srcAccessMask = 0;
1022 barrier.dstAccessMask = 0;
1023 barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1024 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1025 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1026 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1027 barrier.image = currAllocInfo.m_Image;
1028 barrier.subresourceRange = subresourceRange;
1029
1030 vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0,
1031 0, nullptr,
1032 0, nullptr,
1033 1, &barrier);
1034
1035 // Copy image date
1036 VkBufferImageCopy copy = {};
1037 copy.bufferOffset = 0;
1038 copy.bufferRowLength = 0;
1039 copy.bufferImageHeight = 0;
1040 copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1041 copy.imageSubresource.layerCount = 1;
1042 copy.imageExtent = currAllocInfo.m_ImageInfo.extent;
1043
1044 vkCmdCopyBufferToImage(g_hTemporaryCommandBuffer, stagingBuf, currAllocInfo.m_Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©);
1045
1046 // Transfer to desired layout
1047 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1048 barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
1049 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1050 barrier.newLayout = currAllocInfo.m_ImageLayout;
1051
1052 vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0,
1053 0, nullptr,
1054 0, nullptr,
1055 1, &barrier);
1056 }
1057 }
1058
1059 if(cmdBufferStarted)
1060 {
1061 EndSingleTimeCommands();
1062 stagingBufs.ReleaseAllBuffers();
1063 }
1064 }
1065
ValidateGpuData(const AllocInfo * allocInfo,size_t allocInfoCount)1066 static void ValidateGpuData(const AllocInfo* allocInfo, size_t allocInfoCount)
1067 {
1068 StagingBufferCollection stagingBufs;
1069
1070 bool cmdBufferStarted = false;
1071 size_t validateAllocIndexOffset = 0;
1072 std::vector<void*> validateStagingBuffers;
1073 for(size_t allocInfoIndex = 0; allocInfoIndex < allocInfoCount; ++allocInfoIndex)
1074 {
1075 const AllocInfo& currAllocInfo = allocInfo[allocInfoIndex];
1076 if(currAllocInfo.m_Buffer)
1077 {
1078 const VkDeviceSize size = currAllocInfo.m_BufferInfo.size;
1079
1080 VkBuffer stagingBuf = VK_NULL_HANDLE;
1081 void* stagingBufMappedPtr = nullptr;
1082 if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr))
1083 {
1084 TEST(cmdBufferStarted);
1085 EndSingleTimeCommands();
1086 cmdBufferStarted = false;
1087
1088 for(size_t validateIndex = 0;
1089 validateIndex < validateStagingBuffers.size();
1090 ++validateIndex)
1091 {
1092 const size_t validateAllocIndex = validateIndex + validateAllocIndexOffset;
1093 const VkDeviceSize validateSize = allocInfo[validateAllocIndex].m_BufferInfo.size;
1094 TEST(validateSize % sizeof(uint32_t) == 0);
1095 const uint32_t* stagingValPtr = (const uint32_t*)validateStagingBuffers[validateIndex];
1096 uint32_t val = allocInfo[validateAllocIndex].m_StartValue;
1097 bool valid = true;
1098 for(size_t i = 0; i < validateSize / sizeof(uint32_t); ++i)
1099 {
1100 if(*stagingValPtr != val)
1101 {
1102 valid = false;
1103 break;
1104 }
1105 ++stagingValPtr;
1106 ++val;
1107 }
1108 TEST(valid);
1109 }
1110
1111 stagingBufs.ReleaseAllBuffers();
1112
1113 validateAllocIndexOffset = allocInfoIndex;
1114 validateStagingBuffers.clear();
1115
1116 bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr);
1117 TEST(ok);
1118 }
1119
1120 // Issue copy command from staging buffer to destination buffer.
1121 if(!cmdBufferStarted)
1122 {
1123 cmdBufferStarted = true;
1124 BeginSingleTimeCommands();
1125 }
1126
1127 VkBufferCopy copy = {};
1128 copy.srcOffset = 0;
1129 copy.dstOffset = 0;
1130 copy.size = size;
1131 vkCmdCopyBuffer(g_hTemporaryCommandBuffer, currAllocInfo.m_Buffer, stagingBuf, 1, ©);
1132
1133 // Sava mapped pointer for later validation.
1134 validateStagingBuffers.push_back(stagingBufMappedPtr);
1135 }
1136 else
1137 {
1138 TEST(0 && "Images not currently supported.");
1139 }
1140 }
1141
1142 if(cmdBufferStarted)
1143 {
1144 EndSingleTimeCommands();
1145
1146 for(size_t validateIndex = 0;
1147 validateIndex < validateStagingBuffers.size();
1148 ++validateIndex)
1149 {
1150 const size_t validateAllocIndex = validateIndex + validateAllocIndexOffset;
1151 const VkDeviceSize validateSize = allocInfo[validateAllocIndex].m_BufferInfo.size;
1152 TEST(validateSize % sizeof(uint32_t) == 0);
1153 const uint32_t* stagingValPtr = (const uint32_t*)validateStagingBuffers[validateIndex];
1154 uint32_t val = allocInfo[validateAllocIndex].m_StartValue;
1155 bool valid = true;
1156 for(size_t i = 0; i < validateSize / sizeof(uint32_t); ++i)
1157 {
1158 if(*stagingValPtr != val)
1159 {
1160 valid = false;
1161 break;
1162 }
1163 ++stagingValPtr;
1164 ++val;
1165 }
1166 TEST(valid);
1167 }
1168
1169 stagingBufs.ReleaseAllBuffers();
1170 }
1171 }
1172
GetMemReq(VmaAllocationCreateInfo & outMemReq)1173 static void GetMemReq(VmaAllocationCreateInfo& outMemReq)
1174 {
1175 outMemReq = {};
1176 outMemReq.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
1177 //outMemReq.flags = VMA_ALLOCATION_CREATE_PERSISTENT_MAP_BIT;
1178 }
1179
CreateBuffer(VmaAllocationCreateInfo allocCreateInfo,const VkBufferCreateInfo & bufCreateInfo,bool persistentlyMapped,AllocInfo & outAllocInfo)1180 static void CreateBuffer(
1181 VmaAllocationCreateInfo allocCreateInfo,
1182 const VkBufferCreateInfo& bufCreateInfo,
1183 bool persistentlyMapped,
1184 AllocInfo& outAllocInfo)
1185 {
1186 outAllocInfo = {};
1187 outAllocInfo.m_BufferInfo = bufCreateInfo;
1188
1189 if (persistentlyMapped)
1190 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
1191
1192 VmaAllocationInfo vmaAllocInfo = {};
1193 ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &outAllocInfo.m_Buffer, &outAllocInfo.m_Allocation, &vmaAllocInfo) );
1194
1195 // Setup StartValue and fill.
1196 {
1197 outAllocInfo.m_StartValue = (uint32_t)rand();
1198 uint32_t* data = (uint32_t*)vmaAllocInfo.pMappedData;
1199 TEST((data != nullptr) == persistentlyMapped);
1200 if(!persistentlyMapped)
1201 {
1202 ERR_GUARD_VULKAN( vmaMapMemory(g_hAllocator, outAllocInfo.m_Allocation, (void**)&data) );
1203 }
1204
1205 uint32_t value = outAllocInfo.m_StartValue;
1206 TEST(bufCreateInfo.size % 4 == 0);
1207 for(size_t i = 0; i < bufCreateInfo.size / sizeof(uint32_t); ++i)
1208 data[i] = value++;
1209
1210 if(!persistentlyMapped)
1211 vmaUnmapMemory(g_hAllocator, outAllocInfo.m_Allocation);
1212 }
1213 }
1214
CreateImage(VmaAllocationCreateInfo allocCreateInfo,const VkImageCreateInfo & imgCreateInfo,VkImageLayout dstLayout,bool persistentlyMapped,AllocInfo & outAllocInfo)1215 void CreateImage(
1216 VmaAllocationCreateInfo allocCreateInfo,
1217 const VkImageCreateInfo& imgCreateInfo,
1218 VkImageLayout dstLayout,
1219 bool persistentlyMapped,
1220 AllocInfo& outAllocInfo)
1221 {
1222 if (persistentlyMapped)
1223 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
1224 outAllocInfo.CreateImage(imgCreateInfo, allocCreateInfo, dstLayout);
1225
1226 // Perform barrier into destination layout
1227 if (dstLayout != imgCreateInfo.initialLayout)
1228 {
1229 BeginSingleTimeCommands();
1230
1231 VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
1232 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1233 barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
1234 barrier.oldLayout = imgCreateInfo.initialLayout;
1235 barrier.newLayout = dstLayout;
1236 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1237 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1238 barrier.image = outAllocInfo.m_Image;
1239 barrier.subresourceRange =
1240 {
1241 VK_IMAGE_ASPECT_COLOR_BIT,
1242 0, VK_REMAINING_MIP_LEVELS,
1243 0, VK_REMAINING_ARRAY_LAYERS
1244 };
1245
1246 vkCmdPipelineBarrier(g_hTemporaryCommandBuffer,
1247 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
1248 0, nullptr, 0, nullptr, 1, &barrier);
1249
1250 EndSingleTimeCommands();
1251 }
1252 }
1253
CreateAllocation(AllocInfo & outAllocation)1254 static void CreateAllocation(AllocInfo& outAllocation)
1255 {
1256 outAllocation.m_Allocation = nullptr;
1257 outAllocation.m_Buffer = nullptr;
1258 outAllocation.m_Image = nullptr;
1259 outAllocation.m_StartValue = (uint32_t)rand();
1260
1261 VmaAllocationCreateInfo vmaMemReq;
1262 GetMemReq(vmaMemReq);
1263
1264 VmaAllocationInfo allocInfo;
1265
1266 const bool isBuffer = true;//(rand() & 0x1) != 0;
1267 const bool isLarge = (rand() % 16) == 0;
1268 if(isBuffer)
1269 {
1270 const uint32_t bufferSize = isLarge ?
1271 (rand() % 10 + 1) * (1024 * 1024) : // 1 MB ... 10 MB
1272 (rand() % 1024 + 1) * 1024; // 1 KB ... 1 MB
1273
1274 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1275 bufferInfo.size = bufferSize;
1276 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1277
1278 VkResult res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &vmaMemReq, &outAllocation.m_Buffer, &outAllocation.m_Allocation, &allocInfo);
1279 outAllocation.m_BufferInfo = bufferInfo;
1280 TEST(res == VK_SUCCESS);
1281 }
1282 else
1283 {
1284 const uint32_t imageSizeX = isLarge ?
1285 1024 + rand() % (4096 - 1024) : // 1024 ... 4096
1286 rand() % 1024 + 1; // 1 ... 1024
1287 const uint32_t imageSizeY = isLarge ?
1288 1024 + rand() % (4096 - 1024) : // 1024 ... 4096
1289 rand() % 1024 + 1; // 1 ... 1024
1290
1291 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
1292 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1293 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
1294 imageInfo.extent.width = imageSizeX;
1295 imageInfo.extent.height = imageSizeY;
1296 imageInfo.extent.depth = 1;
1297 imageInfo.mipLevels = 1;
1298 imageInfo.arrayLayers = 1;
1299 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1300 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
1301 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
1302 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
1303
1304 VkResult res = vmaCreateImage(g_hAllocator, &imageInfo, &vmaMemReq, &outAllocation.m_Image, &outAllocation.m_Allocation, &allocInfo);
1305 outAllocation.m_ImageInfo = imageInfo;
1306 TEST(res == VK_SUCCESS);
1307 }
1308
1309 uint32_t* data = (uint32_t*)allocInfo.pMappedData;
1310 if(allocInfo.pMappedData == nullptr)
1311 {
1312 VkResult res = vmaMapMemory(g_hAllocator, outAllocation.m_Allocation, (void**)&data);
1313 TEST(res == VK_SUCCESS);
1314 }
1315
1316 uint32_t value = outAllocation.m_StartValue;
1317 TEST(allocInfo.size % 4 == 0);
1318 for(size_t i = 0; i < allocInfo.size / sizeof(uint32_t); ++i)
1319 data[i] = value++;
1320
1321 if(allocInfo.pMappedData == nullptr)
1322 vmaUnmapMemory(g_hAllocator, outAllocation.m_Allocation);
1323 }
1324
DestroyAllocation(const AllocInfo & allocation)1325 static void DestroyAllocation(const AllocInfo& allocation)
1326 {
1327 if(allocation.m_Buffer)
1328 vmaDestroyBuffer(g_hAllocator, allocation.m_Buffer, allocation.m_Allocation);
1329 else
1330 vmaDestroyImage(g_hAllocator, allocation.m_Image, allocation.m_Allocation);
1331 }
1332
DestroyAllAllocations(std::vector<AllocInfo> & allocations)1333 static void DestroyAllAllocations(std::vector<AllocInfo>& allocations)
1334 {
1335 for(size_t i = allocations.size(); i--; )
1336 DestroyAllocation(allocations[i]);
1337 allocations.clear();
1338 }
1339
ValidateAllocationData(const AllocInfo & allocation)1340 static void ValidateAllocationData(const AllocInfo& allocation)
1341 {
1342 VmaAllocationInfo allocInfo;
1343 vmaGetAllocationInfo(g_hAllocator, allocation.m_Allocation, &allocInfo);
1344
1345 uint32_t* data = (uint32_t*)allocInfo.pMappedData;
1346 if(allocInfo.pMappedData == nullptr)
1347 {
1348 VkResult res = vmaMapMemory(g_hAllocator, allocation.m_Allocation, (void**)&data);
1349 TEST(res == VK_SUCCESS);
1350 }
1351
1352 uint32_t value = allocation.m_StartValue;
1353 bool ok = true;
1354 if(allocation.m_Buffer)
1355 {
1356 TEST(allocInfo.size % 4 == 0);
1357 for(size_t i = 0; i < allocInfo.size / sizeof(uint32_t); ++i)
1358 {
1359 if(data[i] != value++)
1360 {
1361 ok = false;
1362 break;
1363 }
1364 }
1365 }
1366 else
1367 {
1368 TEST(allocation.m_Image);
1369 // Images not currently supported.
1370 }
1371 TEST(ok);
1372
1373 if(allocInfo.pMappedData == nullptr)
1374 vmaUnmapMemory(g_hAllocator, allocation.m_Allocation);
1375 }
1376
RecreateAllocationResource(AllocInfo & allocation)1377 static void RecreateAllocationResource(AllocInfo& allocation)
1378 {
1379 VmaAllocationInfo allocInfo;
1380 vmaGetAllocationInfo(g_hAllocator, allocation.m_Allocation, &allocInfo);
1381
1382 if(allocation.m_Buffer)
1383 {
1384 vkDestroyBuffer(g_hDevice, allocation.m_Buffer, g_Allocs);
1385
1386 VkResult res = vkCreateBuffer(g_hDevice, &allocation.m_BufferInfo, g_Allocs, &allocation.m_Buffer);
1387 TEST(res == VK_SUCCESS);
1388
1389 // Just to silence validation layer warnings.
1390 VkMemoryRequirements vkMemReq;
1391 vkGetBufferMemoryRequirements(g_hDevice, allocation.m_Buffer, &vkMemReq);
1392 TEST(vkMemReq.size >= allocation.m_BufferInfo.size);
1393
1394 res = vmaBindBufferMemory(g_hAllocator, allocation.m_Allocation, allocation.m_Buffer);
1395 TEST(res == VK_SUCCESS);
1396 }
1397 else
1398 {
1399 vkDestroyImage(g_hDevice, allocation.m_Image, g_Allocs);
1400
1401 VkResult res = vkCreateImage(g_hDevice, &allocation.m_ImageInfo, g_Allocs, &allocation.m_Image);
1402 TEST(res == VK_SUCCESS);
1403
1404 // Just to silence validation layer warnings.
1405 VkMemoryRequirements vkMemReq;
1406 vkGetImageMemoryRequirements(g_hDevice, allocation.m_Image, &vkMemReq);
1407
1408 res = vmaBindImageMemory(g_hAllocator, allocation.m_Allocation, allocation.m_Image);
1409 TEST(res == VK_SUCCESS);
1410 }
1411 }
1412
ProcessDefragmentationPass(VmaDefragmentationPassMoveInfo & stepInfo)1413 static void ProcessDefragmentationPass(VmaDefragmentationPassMoveInfo& stepInfo)
1414 {
1415 std::vector<VkImageMemoryBarrier> beginImageBarriers;
1416 std::vector<VkImageMemoryBarrier> finalizeImageBarriers;
1417
1418 VkPipelineStageFlags beginSrcStageMask = 0;
1419 VkPipelineStageFlags beginDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
1420
1421 VkPipelineStageFlags finalizeSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
1422 VkPipelineStageFlags finalizeDstStageMask = 0;
1423
1424 bool wantsMemoryBarrier = false;
1425
1426 VkMemoryBarrier beginMemoryBarrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER };
1427 VkMemoryBarrier finalizeMemoryBarrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER };
1428
1429 for (uint32_t i = 0; i < stepInfo.moveCount; ++i)
1430 {
1431 if (stepInfo.pMoves[i].operation == VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY)
1432 {
1433 VmaAllocationInfo info;
1434 vmaGetAllocationInfo(g_hAllocator, stepInfo.pMoves[i].srcAllocation, &info);
1435
1436 AllocInfo* allocInfo = (AllocInfo*)info.pUserData;
1437
1438 if (allocInfo->m_Image)
1439 {
1440 VkImage newImage;
1441
1442 const VkResult result = vkCreateImage(g_hDevice, &allocInfo->m_ImageInfo, g_Allocs, &newImage);
1443 TEST(result >= VK_SUCCESS);
1444
1445 vmaBindImageMemory(g_hAllocator, stepInfo.pMoves[i].dstTmpAllocation, newImage);
1446 allocInfo->m_NewImage = newImage;
1447
1448 // Keep track of our pipeline stages that we need to wait/signal on
1449 beginSrcStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1450 finalizeDstStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1451
1452 // We need one pipeline barrier and two image layout transitions here
1453 // First we'll have to turn our newly created image into VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
1454 // And the second one is turning the old image into VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
1455
1456 VkImageSubresourceRange subresourceRange = {
1457 VK_IMAGE_ASPECT_COLOR_BIT,
1458 0, VK_REMAINING_MIP_LEVELS,
1459 0, VK_REMAINING_ARRAY_LAYERS
1460 };
1461
1462 VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
1463 barrier.srcAccessMask = 0;
1464 barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
1465 barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1466 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1467 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1468 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1469 barrier.image = newImage;
1470 barrier.subresourceRange = subresourceRange;
1471
1472 beginImageBarriers.push_back(barrier);
1473
1474 // Second barrier to convert the existing image. This one actually needs a real barrier
1475 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1476 barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
1477 barrier.oldLayout = allocInfo->m_ImageLayout;
1478 barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
1479 barrier.image = allocInfo->m_Image;
1480
1481 beginImageBarriers.push_back(barrier);
1482
1483 // And lastly we need a barrier that turns our new image into the layout of the old one
1484 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1485 barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
1486 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
1487 barrier.newLayout = allocInfo->m_ImageLayout;
1488 barrier.image = newImage;
1489
1490 finalizeImageBarriers.push_back(barrier);
1491 }
1492 else if (allocInfo->m_Buffer)
1493 {
1494 VkBuffer newBuffer;
1495
1496 const VkResult result = vkCreateBuffer(g_hDevice, &allocInfo->m_BufferInfo, g_Allocs, &newBuffer);
1497 TEST(result >= VK_SUCCESS);
1498
1499 vmaBindBufferMemory(g_hAllocator, stepInfo.pMoves[i].dstTmpAllocation, newBuffer);
1500 allocInfo->m_NewBuffer = newBuffer;
1501
1502 // Keep track of our pipeline stages that we need to wait/signal on
1503 beginSrcStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1504 finalizeDstStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1505
1506 beginMemoryBarrier.srcAccessMask |= VK_ACCESS_MEMORY_WRITE_BIT;
1507 beginMemoryBarrier.dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT;
1508
1509 finalizeMemoryBarrier.srcAccessMask |= VK_ACCESS_TRANSFER_WRITE_BIT;
1510 finalizeMemoryBarrier.dstAccessMask |= VK_ACCESS_MEMORY_READ_BIT;
1511
1512 wantsMemoryBarrier = true;
1513 }
1514 }
1515 }
1516
1517 if (!beginImageBarriers.empty() || wantsMemoryBarrier)
1518 {
1519 const uint32_t memoryBarrierCount = wantsMemoryBarrier ? 1 : 0;
1520
1521 vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, beginSrcStageMask, beginDstStageMask, 0,
1522 memoryBarrierCount, &beginMemoryBarrier,
1523 0, nullptr,
1524 (uint32_t)beginImageBarriers.size(), beginImageBarriers.data());
1525 }
1526
1527 for (uint32_t i = 0; i < stepInfo.moveCount; ++i)
1528 {
1529 if (stepInfo.pMoves[i].operation == VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY)
1530 {
1531 VmaAllocationInfo info;
1532 vmaGetAllocationInfo(g_hAllocator, stepInfo.pMoves[i].srcAllocation, &info);
1533
1534 AllocInfo* allocInfo = (AllocInfo*)info.pUserData;
1535
1536 if (allocInfo->m_Image)
1537 {
1538 std::vector<VkImageCopy> imageCopies;
1539
1540 // Copy all mips of the source image into the target image
1541 VkOffset3D offset = { 0, 0, 0 };
1542 VkExtent3D extent = allocInfo->m_ImageInfo.extent;
1543
1544 VkImageSubresourceLayers subresourceLayers = {
1545 VK_IMAGE_ASPECT_COLOR_BIT,
1546 0,
1547 0, 1
1548 };
1549
1550 for (uint32_t mip = 0; mip < allocInfo->m_ImageInfo.mipLevels; ++mip)
1551 {
1552 subresourceLayers.mipLevel = mip;
1553
1554 VkImageCopy imageCopy{
1555 subresourceLayers,
1556 offset,
1557 subresourceLayers,
1558 offset,
1559 extent
1560 };
1561
1562 imageCopies.push_back(imageCopy);
1563
1564 extent.width = std::max(uint32_t(1), extent.width >> 1);
1565 extent.height = std::max(uint32_t(1), extent.height >> 1);
1566 extent.depth = std::max(uint32_t(1), extent.depth >> 1);
1567 }
1568
1569 vkCmdCopyImage(
1570 g_hTemporaryCommandBuffer,
1571 allocInfo->m_Image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
1572 allocInfo->m_NewImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1573 (uint32_t)imageCopies.size(), imageCopies.data());
1574 }
1575 else if (allocInfo->m_Buffer)
1576 {
1577 VkBufferCopy region = {
1578 0,
1579 0,
1580 allocInfo->m_BufferInfo.size };
1581
1582 vkCmdCopyBuffer(g_hTemporaryCommandBuffer,
1583 allocInfo->m_Buffer, allocInfo->m_NewBuffer,
1584 1, ®ion);
1585 }
1586 }
1587 }
1588
1589 if (!finalizeImageBarriers.empty() || wantsMemoryBarrier)
1590 {
1591 const uint32_t memoryBarrierCount = wantsMemoryBarrier ? 1 : 0;
1592
1593 vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, finalizeSrcStageMask, finalizeDstStageMask, 0,
1594 memoryBarrierCount, &finalizeMemoryBarrier,
1595 0, nullptr,
1596 (uint32_t)finalizeImageBarriers.size(), finalizeImageBarriers.data());
1597 }
1598 }
1599
Defragment(VmaDefragmentationInfo & defragmentationInfo,VmaDefragmentationStats * defragmentationStats=nullptr)1600 static void Defragment(VmaDefragmentationInfo& defragmentationInfo,
1601 VmaDefragmentationStats* defragmentationStats = nullptr)
1602 {
1603 VmaDefragmentationContext defragCtx = nullptr;
1604 VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragmentationInfo, &defragCtx);
1605 TEST(res == VK_SUCCESS);
1606
1607 VmaDefragmentationPassMoveInfo pass = {};
1608 while ((res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_INCOMPLETE)
1609 {
1610 BeginSingleTimeCommands();
1611 ProcessDefragmentationPass(pass);
1612 EndSingleTimeCommands();
1613
1614 // Destroy old buffers/images and replace them with new handles.
1615 for(size_t i = 0; i < pass.moveCount; ++i)
1616 {
1617 VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
1618 VmaAllocationInfo vmaAllocInfo;
1619 vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
1620 AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
1621
1622 if(allocInfo->m_Buffer)
1623 {
1624 assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
1625 vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
1626 allocInfo->m_Buffer = allocInfo->m_NewBuffer;
1627 allocInfo->m_NewBuffer = VK_NULL_HANDLE;
1628 }
1629 else if(allocInfo->m_Image)
1630 {
1631 assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer);
1632 vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs);
1633 allocInfo->m_Image = allocInfo->m_NewImage;
1634 allocInfo->m_NewImage = VK_NULL_HANDLE;
1635 }
1636 else
1637 assert(0);
1638 }
1639 if ((res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_SUCCESS)
1640 break;
1641 TEST(res == VK_INCOMPLETE);
1642 }
1643 TEST(res == VK_SUCCESS);
1644
1645 vmaEndDefragmentation(g_hAllocator, defragCtx, defragmentationStats);
1646 }
1647
ValidateAllocationsData(const AllocInfo * allocs,size_t allocCount)1648 static void ValidateAllocationsData(const AllocInfo* allocs, size_t allocCount)
1649 {
1650 std::for_each(allocs, allocs + allocCount, [](const AllocInfo& allocInfo) {
1651 ValidateAllocationData(allocInfo);
1652 });
1653 }
1654
1655
TestJson()1656 static void TestJson()
1657 {
1658 wprintf(L"Test JSON\n");
1659
1660 std::vector<VmaPool> pools;
1661 std::vector<VmaAllocation> allocs;
1662
1663 VmaAllocationCreateInfo allocCreateInfo = {};
1664
1665 VkBufferCreateInfo buffCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1666 buffCreateInfo.size = 1024;
1667 buffCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1668
1669 VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
1670 imgCreateInfo.imageType = VK_IMAGE_TYPE_2D;
1671 imgCreateInfo.extent.depth = 1;
1672 imgCreateInfo.mipLevels = 1;
1673 imgCreateInfo.arrayLayers = 1;
1674 imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
1675 imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
1676 imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
1677 imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1678
1679 VkMemoryRequirements memReq = {};
1680 {
1681 VkBuffer dummyBuffer = VK_NULL_HANDLE;
1682 TEST(vkCreateBuffer(g_hDevice, &buffCreateInfo, g_Allocs, &dummyBuffer) == VK_SUCCESS && dummyBuffer);
1683
1684 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
1685 vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs);
1686 }
1687
1688 // Select if using custom pool or default
1689 for (uint8_t poolType = 0; poolType < 2; ++poolType)
1690 {
1691 // Select different memoryTypes
1692 for (uint8_t memType = 0; memType < 2; ++memType)
1693 {
1694 switch (memType)
1695 {
1696 case 0:
1697 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
1698 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DONT_BIND_BIT;
1699 break;
1700 case 1:
1701 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
1702 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DONT_BIND_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
1703 break;
1704 }
1705
1706 switch (poolType)
1707 {
1708 case 0:
1709 allocCreateInfo.pool = nullptr;
1710 break;
1711 case 1:
1712 {
1713 VmaPoolCreateInfo poolCreateInfo = {};
1714 TEST(vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &buffCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex) == VK_SUCCESS);
1715
1716 VmaPool pool;
1717 TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
1718
1719 allocCreateInfo.pool = pool;
1720 pools.emplace_back(pool);
1721 break;
1722 }
1723 }
1724
1725 // Select different allocation flags
1726 for (uint8_t allocFlag = 0; allocFlag < 2; ++allocFlag)
1727 {
1728 switch (allocFlag)
1729 {
1730 case 1:
1731 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
1732 break;
1733 }
1734
1735 // Select different alloc types (block, buffer, texture, etc.)
1736 for (uint8_t allocType = 0; allocType < 4; ++allocType)
1737 {
1738 // Select different data stored in the allocation
1739 for (uint8_t data = 0; data < 4; ++data)
1740 {
1741 VmaAllocation alloc = nullptr;
1742
1743 switch (allocType)
1744 {
1745 case 0:
1746 {
1747 VmaAllocationCreateInfo localCreateInfo = allocCreateInfo;
1748 switch (memType)
1749 {
1750 case 0:
1751 localCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1752 break;
1753 case 1:
1754 localCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1755 break;
1756 }
1757 TEST(vmaAllocateMemory(g_hAllocator, &memReq, &localCreateInfo, &alloc, nullptr) == VK_SUCCESS || alloc == VK_NULL_HANDLE);
1758 break;
1759 }
1760 case 1:
1761 {
1762 VkBuffer buffer;
1763 TEST(vmaCreateBuffer(g_hAllocator, &buffCreateInfo, &allocCreateInfo, &buffer, &alloc, nullptr) == VK_SUCCESS || alloc == VK_NULL_HANDLE);
1764 vkDestroyBuffer(g_hDevice, buffer, g_Allocs);
1765 break;
1766 }
1767 case 2:
1768 {
1769 imgCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
1770 imgCreateInfo.extent.width = 512;
1771 imgCreateInfo.extent.height = 1;
1772 VkImage image;
1773 TEST(vmaCreateImage(g_hAllocator, &imgCreateInfo, &allocCreateInfo, &image, &alloc, nullptr) == VK_SUCCESS || alloc == VK_NULL_HANDLE);
1774 vkDestroyImage(g_hDevice, image, g_Allocs);
1775 break;
1776 }
1777 case 3:
1778 {
1779 imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
1780 imgCreateInfo.extent.width = 1024;
1781 imgCreateInfo.extent.height = 512;
1782 VkImage image;
1783 TEST(vmaCreateImage(g_hAllocator, &imgCreateInfo, &allocCreateInfo, &image, &alloc, nullptr) == VK_SUCCESS || alloc == VK_NULL_HANDLE);
1784 vkDestroyImage(g_hDevice, image, g_Allocs);
1785 break;
1786 }
1787 }
1788
1789 if(alloc)
1790 {
1791 switch (data)
1792 {
1793 case 1:
1794 vmaSetAllocationUserData(g_hAllocator, alloc, (void*)16112007);
1795 break;
1796 case 2:
1797 vmaSetAllocationName(g_hAllocator, alloc, "SHEPURD");
1798 break;
1799 case 3:
1800 vmaSetAllocationUserData(g_hAllocator, alloc, (void*)26012010);
1801 vmaSetAllocationName(g_hAllocator, alloc, "JOKER");
1802 break;
1803 }
1804 allocs.emplace_back(alloc);
1805 }
1806 }
1807 }
1808
1809 }
1810 }
1811 }
1812 SaveAllocatorStatsToFile(L"JSON_VULKAN.json");
1813
1814 for (auto& alloc : allocs)
1815 vmaFreeMemory(g_hAllocator, alloc);
1816 for (auto& pool : pools)
1817 vmaDestroyPool(g_hAllocator, pool);
1818 }
1819
TestDefragmentationSimple()1820 void TestDefragmentationSimple()
1821 {
1822 wprintf(L"Test defragmentation simple\n");
1823
1824 RandomNumberGenerator rand(667);
1825
1826 const VkDeviceSize BUF_SIZE = 0x10000;
1827 const VkDeviceSize BLOCK_SIZE = BUF_SIZE * 8;
1828
1829 const VkDeviceSize MIN_BUF_SIZE = 32;
1830 const VkDeviceSize MAX_BUF_SIZE = BUF_SIZE * 4;
1831 auto RandomBufSize = [&]() -> VkDeviceSize
1832 {
1833 return align_up<VkDeviceSize>(rand.Generate() % (MAX_BUF_SIZE - MIN_BUF_SIZE + 1) + MIN_BUF_SIZE, 64);
1834 };
1835
1836 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1837 bufCreateInfo.size = BUF_SIZE;
1838 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1839
1840 VmaAllocationCreateInfo allocCreateInfo = {};
1841 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
1842 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
1843
1844 uint32_t memTypeIndex = UINT32_MAX;
1845 vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &memTypeIndex);
1846
1847 VmaPoolCreateInfo poolCreateInfo = {};
1848 poolCreateInfo.blockSize = BLOCK_SIZE;
1849 poolCreateInfo.memoryTypeIndex = memTypeIndex;
1850
1851 VmaPool pool;
1852 TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
1853 allocCreateInfo.pool = pool;
1854
1855 VmaDefragmentationInfo defragInfo = {};
1856 defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT;
1857 defragInfo.pool = pool;
1858
1859 // Defragmentation of empty pool.
1860 {
1861 VmaDefragmentationContext defragCtx = nullptr;
1862 VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &defragCtx);
1863 TEST(res == VK_SUCCESS);
1864
1865 VmaDefragmentationPassMoveInfo pass = {};
1866 res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass);
1867 TEST(res == VK_SUCCESS);
1868
1869 VmaDefragmentationStats defragStats = {};
1870 vmaEndDefragmentation(g_hAllocator, defragCtx, &defragStats);
1871 TEST(defragStats.allocationsMoved == 0 && defragStats.bytesFreed == 0 &&
1872 defragStats.bytesMoved == 0 && defragStats.deviceMemoryBlocksFreed == 0);
1873 }
1874
1875 std::vector<AllocInfo> allocations;
1876
1877 // persistentlyMappedOption = 0 - not persistently mapped.
1878 // persistentlyMappedOption = 1 - persistently mapped.
1879 for (uint32_t persistentlyMappedOption = 0; persistentlyMappedOption < 2; ++persistentlyMappedOption)
1880 {
1881 wprintf(L" Persistently mapped option = %u\n", persistentlyMappedOption);
1882 const bool persistentlyMapped = persistentlyMappedOption != 0;
1883
1884 // # Test 1
1885 // Buffers of fixed size.
1886 // Fill 2 blocks. Remove odd buffers. Defragment everything.
1887 // Expected result: at least 1 block freed.
1888 {
1889 for (size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i)
1890 {
1891 AllocInfo allocInfo;
1892 CreateBuffer(allocCreateInfo, bufCreateInfo, persistentlyMapped, allocInfo);
1893 allocations.push_back(allocInfo);
1894 }
1895
1896 for (size_t i = 1; i < allocations.size(); ++i)
1897 {
1898 DestroyAllocation(allocations[i]);
1899 allocations.erase(allocations.begin() + i);
1900 }
1901
1902 // Set data for defragmentation retrieval
1903 for (auto& alloc : allocations)
1904 vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
1905
1906 VmaDefragmentationStats defragStats;
1907 Defragment(defragInfo, &defragStats);
1908 TEST(defragStats.allocationsMoved == 4 && defragStats.bytesMoved == 4 * BUF_SIZE);
1909
1910 ValidateAllocationsData(allocations.data(), allocations.size());
1911 DestroyAllAllocations(allocations);
1912 }
1913
1914 // # Test 2
1915 // Buffers of fixed size.
1916 // Fill 2 blocks. Remove odd buffers. Defragment one buffer at time.
1917 // Expected result: Each of 4 interations makes some progress.
1918 {
1919 for (size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i)
1920 {
1921 AllocInfo allocInfo;
1922 CreateBuffer(allocCreateInfo, bufCreateInfo, persistentlyMapped, allocInfo);
1923 allocations.push_back(allocInfo);
1924 }
1925
1926 for (size_t i = 1; i < allocations.size(); ++i)
1927 {
1928 DestroyAllocation(allocations[i]);
1929 allocations.erase(allocations.begin() + i);
1930 }
1931
1932 // Set data for defragmentation retrieval
1933 for (auto& alloc : allocations)
1934 vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
1935
1936 defragInfo.maxAllocationsPerPass = 1;
1937 defragInfo.maxBytesPerPass = BUF_SIZE;
1938
1939 VmaDefragmentationContext defragCtx = nullptr;
1940 VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &defragCtx);
1941 TEST(res == VK_SUCCESS);
1942
1943 for (size_t i = 0; i < BLOCK_SIZE / BUF_SIZE / 2; ++i)
1944 {
1945 VmaDefragmentationPassMoveInfo pass = {};
1946 res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass);
1947 TEST(res == VK_INCOMPLETE);
1948
1949 BeginSingleTimeCommands();
1950 ProcessDefragmentationPass(pass);
1951 EndSingleTimeCommands();
1952
1953 // Destroy old buffers/images and replace them with new handles.
1954 for (size_t i = 0; i < pass.moveCount; ++i)
1955 {
1956 VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
1957 VmaAllocationInfo vmaAllocInfo;
1958 vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
1959 AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
1960
1961 if (allocInfo->m_Buffer)
1962 {
1963 assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
1964 vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
1965 allocInfo->m_Buffer = allocInfo->m_NewBuffer;
1966 allocInfo->m_NewBuffer = VK_NULL_HANDLE;
1967 }
1968 else if (allocInfo->m_Image)
1969 {
1970 assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer);
1971 vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs);
1972 allocInfo->m_Image = allocInfo->m_NewImage;
1973 allocInfo->m_NewImage = VK_NULL_HANDLE;
1974 }
1975 else
1976 assert(0);
1977 }
1978
1979 res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &pass);
1980 TEST(res == VK_INCOMPLETE);
1981 }
1982
1983 VmaDefragmentationStats defragStats = {};
1984 vmaEndDefragmentation(g_hAllocator, defragCtx, &defragStats);
1985 TEST(defragStats.allocationsMoved == 4 && defragStats.bytesMoved == 4 * BUF_SIZE);
1986
1987 ValidateAllocationsData(allocations.data(), allocations.size());
1988 DestroyAllAllocations(allocations);
1989 }
1990
1991 // # Test 3
1992 // Buffers of variable size.
1993 // Create a number of buffers. Remove some percent of them.
1994 // Defragment while having some percent of them unmovable.
1995 // Expected result: Just simple validation.
1996 {
1997 for (size_t i = 0; i < 100; ++i)
1998 {
1999 VkBufferCreateInfo localBufCreateInfo = bufCreateInfo;
2000 localBufCreateInfo.size = RandomBufSize();
2001
2002 AllocInfo allocInfo;
2003 CreateBuffer(allocCreateInfo, localBufCreateInfo, persistentlyMapped, allocInfo);
2004 allocations.push_back(allocInfo);
2005 }
2006
2007 const uint32_t percentToDelete = 60;
2008 const size_t numberToDelete = allocations.size() * percentToDelete / 100;
2009 for (size_t i = 0; i < numberToDelete; ++i)
2010 {
2011 size_t indexToDelete = rand.Generate() % (uint32_t)allocations.size();
2012 DestroyAllocation(allocations[indexToDelete]);
2013 allocations.erase(allocations.begin() + indexToDelete);
2014 }
2015
2016 // Non-movable allocations will be at the beginning of allocations array.
2017 const uint32_t percentNonMovable = 20;
2018 const size_t numberNonMovable = allocations.size() * percentNonMovable / 100;
2019 for (size_t i = 0; i < numberNonMovable; ++i)
2020 {
2021 size_t indexNonMovable = i + rand.Generate() % (uint32_t)(allocations.size() - i);
2022 if (indexNonMovable != i)
2023 std::swap(allocations[i], allocations[indexNonMovable]);
2024 }
2025
2026 // Set data for defragmentation retrieval
2027 for (auto& alloc : allocations)
2028 vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
2029
2030 defragInfo.maxAllocationsPerPass = 0;
2031 defragInfo.maxBytesPerPass = 0;
2032
2033 VmaDefragmentationContext defragCtx = nullptr;
2034 VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &defragCtx);
2035 TEST(res == VK_SUCCESS);
2036
2037 VmaDefragmentationPassMoveInfo pass = {};
2038 while ((res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_INCOMPLETE)
2039 {
2040 VmaDefragmentationMove* end = pass.pMoves + pass.moveCount;
2041 for (uint32_t i = 0; i < numberNonMovable; ++i)
2042 {
2043 VmaDefragmentationMove* move = std::find_if(pass.pMoves, end, [&](VmaDefragmentationMove& move) { return move.srcAllocation == allocations[i].m_Allocation; });
2044 if (move != end)
2045 move->operation = VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE;
2046 }
2047
2048 BeginSingleTimeCommands();
2049 ProcessDefragmentationPass(pass);
2050 EndSingleTimeCommands();
2051
2052 // Destroy old buffers/images and replace them with new handles.
2053 for (size_t i = 0; i < pass.moveCount; ++i)
2054 {
2055 if (pass.pMoves[i].operation != VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE)
2056 {
2057 VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
2058 VmaAllocationInfo vmaAllocInfo;
2059 vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
2060 AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
2061
2062 if (allocInfo->m_Buffer)
2063 {
2064 assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
2065 vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
2066 allocInfo->m_Buffer = allocInfo->m_NewBuffer;
2067 allocInfo->m_NewBuffer = VK_NULL_HANDLE;
2068 }
2069 else
2070 assert(0);
2071 }
2072 }
2073
2074 if ((res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_SUCCESS)
2075 break;
2076 TEST(res == VK_INCOMPLETE);
2077 }
2078 TEST(res == VK_SUCCESS);
2079
2080 VmaDefragmentationStats defragStats;
2081 vmaEndDefragmentation(g_hAllocator, defragCtx, &defragStats);
2082
2083 ValidateAllocationsData(allocations.data(), allocations.size());
2084 DestroyAllAllocations(allocations);
2085 }
2086 }
2087
2088 vmaDestroyPool(g_hAllocator, pool);
2089 }
2090
TestDefragmentationVsMapping()2091 void TestDefragmentationVsMapping()
2092 {
2093 wprintf(L"Test defragmentation vs mapping\n");
2094
2095 VkBufferCreateInfo bufCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
2096 bufCreateInfo.size = 64 * KILOBYTE;
2097 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2098
2099 VmaAllocationCreateInfo dummyAllocCreateInfo = {};
2100 dummyAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
2101 dummyAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
2102
2103 VmaPoolCreateInfo poolCreateInfo = {};
2104 poolCreateInfo.flags = VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT;
2105 poolCreateInfo.blockSize = 1 * MEGABYTE;
2106 TEST(vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &dummyAllocCreateInfo, &poolCreateInfo.memoryTypeIndex)
2107 == VK_SUCCESS);
2108
2109 VmaPool pool = VK_NULL_HANDLE;
2110 TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
2111
2112 RandomNumberGenerator rand{2355762};
2113
2114 // 16 * 64 KB allocations fit into a single 1 MB block. Create 10 such blocks.
2115 constexpr uint32_t START_ALLOC_COUNT = 160;
2116 std::vector<AllocInfo> allocs{START_ALLOC_COUNT};
2117
2118 constexpr uint32_t RAND_NUM_PERSISTENTLY_MAPPED_BIT = 0x1000;
2119 constexpr uint32_t RAND_NUM_MANUAL_MAP_COUNT_MASK = 0x3;
2120
2121 // Create all the allocations, map what's needed.
2122 {
2123 VmaAllocationCreateInfo allocCreateInfo = {};
2124 allocCreateInfo.pool = pool;
2125 for(size_t allocIndex = 0; allocIndex < START_ALLOC_COUNT; ++allocIndex)
2126 {
2127 const uint32_t randNum = rand.Generate();
2128 if(randNum & RAND_NUM_PERSISTENTLY_MAPPED_BIT)
2129 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
2130 else
2131 allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT;
2132 allocs[allocIndex].CreateBuffer(bufCreateInfo, allocCreateInfo);
2133 vmaSetAllocationUserData(g_hAllocator, allocs[allocIndex].m_Allocation, (void*)(uintptr_t)randNum);
2134 }
2135 }
2136
2137 // Destroy 2/3 of them.
2138 for(uint32_t i = 0; i < START_ALLOC_COUNT * 2 / 3; ++i)
2139 {
2140 const uint32_t allocIndexToRemove = rand.Generate() % allocs.size();
2141 allocs[allocIndexToRemove].Destroy();
2142 allocs.erase(allocs.begin() + allocIndexToRemove);
2143 }
2144
2145 // Map the remaining allocations the right number of times.
2146 for(size_t allocIndex = 0, allocCount = allocs.size(); allocIndex < allocCount; ++allocIndex)
2147 {
2148 VmaAllocationInfo allocInfo;
2149 vmaGetAllocationInfo(g_hAllocator, allocs[allocIndex].m_Allocation, &allocInfo);
2150 const uint32_t randNum = (uint32_t)(uintptr_t)allocInfo.pUserData;
2151 const uint32_t mapCount = randNum & RAND_NUM_MANUAL_MAP_COUNT_MASK;
2152 for(uint32_t mapIndex = 0; mapIndex < mapCount; ++mapIndex)
2153 {
2154 void* ptr;
2155 TEST(vmaMapMemory(g_hAllocator, allocs[allocIndex].m_Allocation, &ptr) == VK_SUCCESS);
2156 TEST(ptr != nullptr);
2157 }
2158 }
2159
2160 // Defragment!
2161 {
2162 VmaDefragmentationInfo defragInfo = {};
2163 defragInfo.pool = pool;
2164 defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT;
2165 VmaDefragmentationContext defragCtx;
2166 TEST(vmaBeginDefragmentation(g_hAllocator, &defragInfo, &defragCtx) == VK_SUCCESS);
2167
2168 for(uint32_t passIndex = 0; ; ++passIndex)
2169 {
2170 VmaDefragmentationPassMoveInfo passInfo = {};
2171 VkResult res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &passInfo);
2172 if(res == VK_SUCCESS)
2173 break;
2174 TEST(res == VK_INCOMPLETE);
2175
2176 wprintf(L" Pass %u moving %u allocations\n", passIndex, passInfo.moveCount);
2177
2178 for(uint32_t moveIndex = 0; moveIndex < passInfo.moveCount; ++moveIndex)
2179 {
2180 if(rand.Generate() % 5 == 0)
2181 passInfo.pMoves[moveIndex].operation = VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE;
2182 }
2183
2184
2185 res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &passInfo);
2186 if(res == VK_SUCCESS)
2187 break;
2188 TEST(res == VK_INCOMPLETE);
2189 }
2190
2191 VmaDefragmentationStats defragStats = {};
2192 vmaEndDefragmentation(g_hAllocator, defragCtx, &defragStats);
2193 wprintf(L" Defragmentation: moved %u allocations, %llu B, freed %u memory blocks, %llu B\n",
2194 defragStats.allocationsMoved, defragStats.bytesMoved,
2195 defragStats.deviceMemoryBlocksFreed, defragStats.bytesFreed);
2196 TEST(defragStats.allocationsMoved > 0 && defragStats.bytesMoved > 0);
2197 TEST(defragStats.deviceMemoryBlocksFreed > 0 && defragStats.bytesFreed > 0);
2198 }
2199
2200 // Test mapping and unmap
2201 for(size_t allocIndex = allocs.size(); allocIndex--; )
2202 {
2203 VmaAllocationInfo allocInfo;
2204 vmaGetAllocationInfo(g_hAllocator, allocs[allocIndex].m_Allocation, &allocInfo);
2205 const uint32_t randNum = (uint32_t)(uintptr_t)allocInfo.pUserData;
2206 const bool isMapped = (randNum & (RAND_NUM_PERSISTENTLY_MAPPED_BIT | RAND_NUM_MANUAL_MAP_COUNT_MASK)) != 0;
2207 TEST(isMapped == (allocInfo.pMappedData != nullptr));
2208
2209 const uint32_t mapCount = randNum & RAND_NUM_MANUAL_MAP_COUNT_MASK;
2210 for(uint32_t mapIndex = 0; mapIndex < mapCount; ++mapIndex)
2211 vmaUnmapMemory(g_hAllocator, allocs[allocIndex].m_Allocation);
2212 }
2213
2214 // Destroy all the remaining allocations.
2215 for(size_t i = allocs.size(); i--; )
2216 allocs[i].Destroy();
2217
2218 vmaDestroyPool(g_hAllocator, pool);
2219 }
2220
TestDefragmentationAlgorithms()2221 void TestDefragmentationAlgorithms()
2222 {
2223 wprintf(L"Test defragmentation simple\n");
2224
2225 RandomNumberGenerator rand(669);
2226
2227 const VkDeviceSize BUF_SIZE = 0x10000;
2228 const uint32_t TEX_SIZE = 256;
2229 const VkDeviceSize BLOCK_SIZE = BUF_SIZE * 200 + TEX_SIZE * 200;
2230
2231 const VkDeviceSize MIN_BUF_SIZE = 2048;
2232 const VkDeviceSize MAX_BUF_SIZE = BUF_SIZE * 4;
2233 auto RandomBufSize = [&]() -> VkDeviceSize
2234 {
2235 return align_up<VkDeviceSize>(rand.Generate() % (MAX_BUF_SIZE - MIN_BUF_SIZE + 1) + MIN_BUF_SIZE, 64);
2236 };
2237
2238 const uint32_t MIN_TEX_SIZE = 512;
2239 const uint32_t MAX_TEX_SIZE = TEX_SIZE * 4;
2240 auto RandomTexSize = [&]() -> uint32_t
2241 {
2242 return align_up<uint32_t>(rand.Generate() % (MAX_TEX_SIZE - MIN_TEX_SIZE + 1) + MIN_TEX_SIZE, 64);
2243 };
2244
2245 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2246 bufCreateInfo.size = BUF_SIZE;
2247 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
2248
2249 VkImageCreateInfo imageCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
2250 imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
2251 imageCreateInfo.extent.depth = 1;
2252 imageCreateInfo.mipLevels = 1;
2253 imageCreateInfo.arrayLayers = 1;
2254 imageCreateInfo.format = VK_FORMAT_R8_UNORM;
2255 imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
2256 imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
2257 imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
2258 imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2259
2260 VmaAllocationCreateInfo allocCreateInfo = {};
2261 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
2262 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
2263
2264 uint32_t memTypeIndex = UINT32_MAX;
2265 vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &memTypeIndex);
2266
2267 VmaPoolCreateInfo poolCreateInfo = {};
2268 poolCreateInfo.blockSize = BLOCK_SIZE;
2269 poolCreateInfo.memoryTypeIndex = memTypeIndex;
2270
2271 VmaPool pool;
2272 TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
2273 allocCreateInfo.pool = pool;
2274
2275 VmaDefragmentationInfo defragInfo = {};
2276 defragInfo.pool = pool;
2277
2278 std::vector<AllocInfo> allocations;
2279
2280 for (uint8_t i = 0; i < 4; ++i)
2281 {
2282 switch (i)
2283 {
2284 case 0:
2285 defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT;
2286 break;
2287 case 1:
2288 defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT;
2289 break;
2290 case 2:
2291 defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT;
2292 break;
2293 case 3:
2294 defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT;
2295 break;
2296 }
2297 wprintf(L" Algorithm = %s\n", DefragmentationAlgorithmToStr(defragInfo.flags));
2298
2299 // 0 - Without immovable allocations
2300 // 1 - With immovable allocations
2301 for (uint8_t j = 0; j < 2; ++j)
2302 {
2303 for (size_t i = 0; i < 400; ++i)
2304 {
2305 bufCreateInfo.size = RandomBufSize();
2306
2307 AllocInfo allocInfo;
2308 CreateBuffer(allocCreateInfo, bufCreateInfo, false, allocInfo);
2309 allocations.push_back(allocInfo);
2310 }
2311 for (size_t i = 0; i < 100; ++i)
2312 {
2313 imageCreateInfo.extent.width = RandomTexSize();
2314 imageCreateInfo.extent.height = RandomTexSize();
2315
2316 AllocInfo allocInfo;
2317 CreateImage(allocCreateInfo, imageCreateInfo, VK_IMAGE_LAYOUT_GENERAL, false, allocInfo);
2318 allocations.push_back(allocInfo);
2319 }
2320
2321 const uint32_t percentToDelete = 55;
2322 const size_t numberToDelete = allocations.size() * percentToDelete / 100;
2323 for (size_t i = 0; i < numberToDelete; ++i)
2324 {
2325 size_t indexToDelete = rand.Generate() % (uint32_t)allocations.size();
2326 DestroyAllocation(allocations[indexToDelete]);
2327 allocations.erase(allocations.begin() + indexToDelete);
2328 }
2329
2330 // Non-movable allocations will be at the beginning of allocations array.
2331 const uint32_t percentNonMovable = 20;
2332 const size_t numberNonMovable = j == 0 ? 0 : (allocations.size() * percentNonMovable / 100);
2333 for (size_t i = 0; i < numberNonMovable; ++i)
2334 {
2335 size_t indexNonMovable = i + rand.Generate() % (uint32_t)(allocations.size() - i);
2336 if (indexNonMovable != i)
2337 std::swap(allocations[i], allocations[indexNonMovable]);
2338 }
2339
2340 // Set data for defragmentation retrieval
2341 for (auto& alloc : allocations)
2342 vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
2343
2344 std::wstring output = DefragmentationAlgorithmToStr(defragInfo.flags);
2345 if (j == 0)
2346 output += L"_NoMove";
2347 else
2348 output += L"_Move";
2349 SaveAllocatorStatsToFile((output + L"_Before.json").c_str());
2350
2351 VmaDefragmentationContext defragCtx = nullptr;
2352 VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &defragCtx);
2353 TEST(res == VK_SUCCESS);
2354
2355 VmaDefragmentationPassMoveInfo pass = {};
2356 while ((res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_INCOMPLETE)
2357 {
2358 VmaDefragmentationMove* end = pass.pMoves + pass.moveCount;
2359 for (uint32_t i = 0; i < numberNonMovable; ++i)
2360 {
2361 VmaDefragmentationMove* move = std::find_if(pass.pMoves, end, [&](VmaDefragmentationMove& move) { return move.srcAllocation == allocations[i].m_Allocation; });
2362 if (move != end)
2363 move->operation = VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE;
2364 }
2365 for (uint32_t i = 0; i < pass.moveCount; ++i)
2366 {
2367 auto it = std::find_if(allocations.begin(), allocations.end(), [&](const AllocInfo& info) { return pass.pMoves[i].srcAllocation == info.m_Allocation; });
2368 assert(it != allocations.end());
2369 }
2370
2371 BeginSingleTimeCommands();
2372 ProcessDefragmentationPass(pass);
2373 EndSingleTimeCommands();
2374
2375 // Destroy old buffers/images and replace them with new handles.
2376 for (size_t i = 0; i < pass.moveCount; ++i)
2377 {
2378 if (pass.pMoves[i].operation == VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY)
2379 {
2380 VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
2381 VmaAllocationInfo vmaAllocInfo;
2382 vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
2383 AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
2384
2385 if (allocInfo->m_Buffer)
2386 {
2387 assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
2388 vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
2389 allocInfo->m_Buffer = allocInfo->m_NewBuffer;
2390 allocInfo->m_NewBuffer = VK_NULL_HANDLE;
2391 }
2392 else if (allocInfo->m_Image)
2393 {
2394 assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer);
2395 vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs);
2396 allocInfo->m_Image = allocInfo->m_NewImage;
2397 allocInfo->m_NewImage = VK_NULL_HANDLE;
2398 }
2399 else
2400 assert(0);
2401 }
2402 }
2403
2404 if ((res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_SUCCESS)
2405 break;
2406 TEST(res == VK_INCOMPLETE);
2407 }
2408 TEST(res == VK_SUCCESS);
2409
2410 VmaDefragmentationStats defragStats;
2411 vmaEndDefragmentation(g_hAllocator, defragCtx, &defragStats);
2412
2413 SaveAllocatorStatsToFile((output + L"_After.json").c_str());
2414 ValidateAllocationsData(allocations.data(), allocations.size());
2415 DestroyAllAllocations(allocations);
2416 }
2417 }
2418
2419 vmaDestroyPool(g_hAllocator, pool);
2420 }
2421
TestDefragmentationFull()2422 void TestDefragmentationFull()
2423 {
2424 std::vector<AllocInfo> allocations;
2425
2426 // Create initial allocations.
2427 for(size_t i = 0; i < 400; ++i)
2428 {
2429 AllocInfo allocation;
2430 CreateAllocation(allocation);
2431 allocations.push_back(allocation);
2432 }
2433
2434 // Delete random allocations
2435 const size_t allocationsToDeletePercent = 80;
2436 size_t allocationsToDelete = allocations.size() * allocationsToDeletePercent / 100;
2437 for(size_t i = 0; i < allocationsToDelete; ++i)
2438 {
2439 size_t index = (size_t)rand() % allocations.size();
2440 DestroyAllocation(allocations[index]);
2441 allocations.erase(allocations.begin() + index);
2442 }
2443 //ValidateAllocationsData(allocations.data(), allocations.size()); // Ultra-slow
2444 //SaveAllocatorStatsToFile(L"Before.csv");
2445
2446 {
2447 std::vector<VmaAllocation> vmaAllocations(allocations.size());
2448 for(size_t i = 0; i < allocations.size(); ++i)
2449 vmaAllocations[i] = allocations[i].m_Allocation;
2450
2451 const size_t nonMovablePercent = 0;
2452 size_t nonMovableCount = vmaAllocations.size() * nonMovablePercent / 100;
2453 for(size_t i = 0; i < nonMovableCount; ++i)
2454 {
2455 size_t index = (size_t)rand() % vmaAllocations.size();
2456 vmaAllocations.erase(vmaAllocations.begin() + index);
2457 }
2458
2459 // Set data for defragmentation retrieval
2460 for (auto& alloc : allocations)
2461 vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
2462
2463 const uint32_t defragCount = 1;
2464 for(uint32_t defragIndex = 0; defragIndex < defragCount; ++defragIndex)
2465 {
2466 std::vector<VkBool32> allocationsChanged(vmaAllocations.size());
2467
2468 VmaDefragmentationInfo defragmentationInfo = {};
2469 defragmentationInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT;
2470
2471 wprintf(L"Defragmentation #%u\n", defragIndex);
2472
2473 time_point begTime = std::chrono::high_resolution_clock::now();
2474
2475 VmaDefragmentationStats stats;
2476 Defragment(defragmentationInfo, &stats);
2477
2478 float defragmentDuration = ToFloatSeconds(std::chrono::high_resolution_clock::now() - begTime);
2479
2480 wprintf(L"Moved allocations %u, bytes %llu\n", stats.allocationsMoved, stats.bytesMoved);
2481 wprintf(L"Freed blocks %u, bytes %llu\n", stats.deviceMemoryBlocksFreed, stats.bytesFreed);
2482 wprintf(L"Time: %.2f s\n", defragmentDuration);
2483
2484 for(size_t i = 0; i < vmaAllocations.size(); ++i)
2485 {
2486 if(allocationsChanged[i])
2487 {
2488 RecreateAllocationResource(allocations[i]);
2489 }
2490 }
2491
2492 //wchar_t fileName[MAX_PATH];
2493 //swprintf(fileName, MAX_PATH, L"After_%02u.csv", defragIndex);
2494 //SaveAllocatorStatsToFile(fileName);
2495 }
2496 }
2497
2498 // Destroy all remaining allocations.
2499 //ValidateAllocationsData(allocations.data(), allocations.size()); // Ultra-slow
2500 DestroyAllAllocations(allocations);
2501 }
2502
TestDefragmentationGpu()2503 static void TestDefragmentationGpu()
2504 {
2505 wprintf(L"Test defragmentation GPU\n");
2506
2507 std::vector<AllocInfo> allocations;
2508
2509 // Create that many allocations to surely fill 3 new blocks of 256 MB.
2510 const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024;
2511 const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024;
2512 const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024;
2513 const size_t bufCount = (size_t)(totalSize / bufSizeMin);
2514 const size_t percentToLeave = 30;
2515 const size_t percentNonMovable = 3;
2516 RandomNumberGenerator rand = { 234522 };
2517
2518 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2519
2520 VmaAllocationCreateInfo allocCreateInfo = {};
2521 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
2522
2523 // Create all intended buffers.
2524 for(size_t i = 0; i < bufCount; ++i)
2525 {
2526 bufCreateInfo.size = align_up(rand.Generate() % (bufSizeMax - bufSizeMin) + bufSizeMin, 32ull);
2527
2528 if(rand.Generate() % 100 < percentNonMovable)
2529 {
2530 bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
2531 VK_BUFFER_USAGE_TRANSFER_DST_BIT |
2532 VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2533 allocCreateInfo.pUserData = (void*)(uintptr_t)2;
2534 }
2535 else
2536 {
2537 // Different usage just to see different color in output from VmaDumpVis.
2538 bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
2539 VK_BUFFER_USAGE_TRANSFER_DST_BIT |
2540 VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2541 // And in JSON dump.
2542 allocCreateInfo.pUserData = (void*)(uintptr_t)1;
2543 }
2544
2545 AllocInfo alloc;
2546 alloc.CreateBuffer(bufCreateInfo, allocCreateInfo);
2547 alloc.m_StartValue = rand.Generate();
2548 allocations.push_back(alloc);
2549 }
2550
2551 // Destroy some percentage of them.
2552 {
2553 const size_t buffersToDestroy = round_div<size_t>(bufCount * (100 - percentToLeave), 100);
2554 for(size_t i = 0; i < buffersToDestroy; ++i)
2555 {
2556 const size_t index = rand.Generate() % allocations.size();
2557 allocations[index].Destroy();
2558 allocations.erase(allocations.begin() + index);
2559 }
2560 }
2561
2562 // Set data for defragmentation retrieval
2563 for (auto& alloc : allocations)
2564 vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
2565
2566 // Fill them with meaningful data.
2567 UploadGpuData(allocations.data(), allocations.size());
2568
2569 wchar_t fileName[MAX_PATH];
2570 swprintf_s(fileName, L"GPU_defragmentation_A_before.json");
2571 SaveAllocatorStatsToFile(fileName);
2572
2573 // Defragment using GPU only.
2574 {
2575 const size_t allocCount = allocations.size();
2576
2577 std::vector<VmaAllocation> allocationPtrs;
2578 std::vector<VkBool32> allocationChanged;
2579 std::vector<size_t> allocationOriginalIndex;
2580
2581 for(size_t i = 0; i < allocCount; ++i)
2582 {
2583 VmaAllocationInfo allocInfo = {};
2584 vmaGetAllocationInfo(g_hAllocator, allocations[i].m_Allocation, &allocInfo);
2585 if((uintptr_t)allocInfo.pUserData == 1) // Movable
2586 {
2587 allocationPtrs.push_back(allocations[i].m_Allocation);
2588 allocationChanged.push_back(VK_FALSE);
2589 allocationOriginalIndex.push_back(i);
2590 }
2591 }
2592
2593 const size_t movableAllocCount = allocationPtrs.size();
2594
2595 VmaDefragmentationInfo defragInfo = {};
2596 VmaDefragmentationStats stats;
2597 Defragment(defragInfo, &stats);
2598
2599 for(size_t i = 0; i < movableAllocCount; ++i)
2600 {
2601 if(allocationChanged[i])
2602 {
2603 const size_t origAllocIndex = allocationOriginalIndex[i];
2604 RecreateAllocationResource(allocations[origAllocIndex]);
2605 }
2606 }
2607
2608 // If corruption detection is enabled, GPU defragmentation may not work on
2609 // memory types that have this detection active, e.g. on Intel.
2610 #if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0
2611 TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0);
2612 TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0);
2613 #endif
2614 }
2615
2616 swprintf_s(fileName, L"GPU_defragmentation_B_after.json");
2617 SaveAllocatorStatsToFile(fileName);
2618 ValidateGpuData(allocations.data(), allocations.size());
2619
2620 // Destroy all remaining buffers.
2621 for(size_t i = allocations.size(); i--; )
2622 {
2623 allocations[i].Destroy();
2624 }
2625 }
2626
TestDefragmentationIncrementalBasic()2627 static void TestDefragmentationIncrementalBasic()
2628 {
2629 wprintf(L"Test defragmentation incremental basic\n");
2630
2631 std::vector<AllocInfo> allocations;
2632
2633 // Create that many allocations to surely fill 3 new blocks of 256 MB.
2634 const std::array<uint32_t, 3> imageSizes = { 256, 512, 1024 };
2635 const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024;
2636 const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024;
2637 const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024;
2638 const size_t imageCount = totalSize / ((size_t)imageSizes[0] * imageSizes[0] * 4) / 2;
2639 const size_t bufCount = (size_t)(totalSize / bufSizeMin) / 2;
2640 const size_t percentToLeave = 30;
2641 RandomNumberGenerator rand = { 234522 };
2642
2643 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
2644 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2645 imageInfo.extent.depth = 1;
2646 imageInfo.mipLevels = 1;
2647 imageInfo.arrayLayers = 1;
2648 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
2649 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
2650 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
2651 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
2652 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2653
2654 VmaAllocationCreateInfo allocCreateInfo = {};
2655 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
2656
2657 // Create all intended images.
2658 for(size_t i = 0; i < imageCount; ++i)
2659 {
2660 const uint32_t size = imageSizes[rand.Generate() % 3];
2661
2662 imageInfo.extent.width = size;
2663 imageInfo.extent.height = size;
2664
2665 AllocInfo alloc;
2666 CreateImage(allocCreateInfo, imageInfo, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, alloc);
2667 alloc.m_StartValue = 0;
2668
2669 allocations.push_back(alloc);
2670 }
2671
2672 // And all buffers
2673 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2674
2675 for(size_t i = 0; i < bufCount; ++i)
2676 {
2677 bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16);
2678 bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2679
2680 AllocInfo alloc;
2681 alloc.CreateBuffer(bufCreateInfo, allocCreateInfo);
2682 alloc.m_StartValue = 0;
2683
2684 allocations.push_back(alloc);
2685 }
2686
2687 // Destroy some percentage of them.
2688 {
2689 const size_t allocationsToDestroy = round_div<size_t>((imageCount + bufCount) * (100 - percentToLeave), 100);
2690 for(size_t i = 0; i < allocationsToDestroy; ++i)
2691 {
2692 const size_t index = rand.Generate() % allocations.size();
2693 allocations[index].Destroy();
2694 allocations.erase(allocations.begin() + index);
2695 }
2696 }
2697
2698 {
2699 // Set our user data pointers. A real application should probably be more clever here
2700 const size_t allocationCount = allocations.size();
2701 for(size_t i = 0; i < allocationCount; ++i)
2702 {
2703 AllocInfo &alloc = allocations[i];
2704 vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
2705 }
2706 }
2707
2708 // Fill them with meaningful data.
2709 UploadGpuData(allocations.data(), allocations.size());
2710
2711 wchar_t fileName[MAX_PATH];
2712 swprintf_s(fileName, L"GPU_defragmentation_incremental_basic_A_before.json");
2713 SaveAllocatorStatsToFile(fileName);
2714
2715 // Defragment using GPU only.
2716 {
2717 VmaDefragmentationInfo defragInfo = {};
2718 VmaDefragmentationContext ctx = VK_NULL_HANDLE;
2719 VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &ctx);
2720 TEST(res == VK_SUCCESS);
2721
2722 VmaDefragmentationPassMoveInfo pass = {};
2723 while ((res = vmaBeginDefragmentationPass(g_hAllocator, ctx, &pass)) == VK_INCOMPLETE)
2724 {
2725 // Ignore data outside of test
2726 for (uint32_t i = 0; i < pass.moveCount; ++i)
2727 {
2728 auto it = std::find_if(allocations.begin(), allocations.end(), [&](const AllocInfo& info) { return pass.pMoves[i].srcAllocation == info.m_Allocation; });
2729 if (it == allocations.end())
2730 pass.pMoves[i].operation = VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE;
2731 }
2732
2733 BeginSingleTimeCommands();
2734 ProcessDefragmentationPass(pass);
2735 EndSingleTimeCommands();
2736
2737 // Destroy old buffers/images and replace them with new handles.
2738 for (size_t i = 0; i < pass.moveCount; ++i)
2739 {
2740 if (pass.pMoves[i].operation != VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE)
2741 {
2742 VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
2743 VmaAllocationInfo vmaAllocInfo;
2744 vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
2745 AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
2746
2747 if (allocInfo->m_Buffer)
2748 {
2749 assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
2750 vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
2751 allocInfo->m_Buffer = allocInfo->m_NewBuffer;
2752 allocInfo->m_NewBuffer = VK_NULL_HANDLE;
2753 }
2754 else if (allocInfo->m_Image)
2755 {
2756 assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer);
2757 vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs);
2758 allocInfo->m_Image = allocInfo->m_NewImage;
2759 allocInfo->m_NewImage = VK_NULL_HANDLE;
2760 }
2761 else
2762 assert(0);
2763 }
2764 }
2765
2766 if ((res = vmaEndDefragmentationPass(g_hAllocator, ctx, &pass)) == VK_SUCCESS)
2767 break;
2768 TEST(res == VK_INCOMPLETE);
2769 }
2770
2771 TEST(res == VK_SUCCESS);
2772 VmaDefragmentationStats stats = {};
2773 vmaEndDefragmentation(g_hAllocator, ctx, &stats);
2774
2775 // If corruption detection is enabled, GPU defragmentation may not work on
2776 // memory types that have this detection active, e.g. on Intel.
2777 #if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0
2778 TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0);
2779 TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0);
2780 #endif
2781 }
2782
2783 //ValidateGpuData(allocations.data(), allocations.size());
2784
2785 swprintf_s(fileName, L"GPU_defragmentation_incremental_basic_B_after.json");
2786 SaveAllocatorStatsToFile(fileName);
2787
2788 // Destroy all remaining buffers and images.
2789 for(size_t i = allocations.size(); i--; )
2790 {
2791 allocations[i].Destroy();
2792 }
2793 }
2794
TestDefragmentationIncrementalComplex()2795 void TestDefragmentationIncrementalComplex()
2796 {
2797 wprintf(L"Test defragmentation incremental complex\n");
2798
2799 std::vector<AllocInfo> allocations;
2800
2801 // Create that many allocations to surely fill 3 new blocks of 256 MB.
2802 const std::array<uint32_t, 3> imageSizes = { 256, 512, 1024 };
2803 const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024;
2804 const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024;
2805 const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024;
2806 const size_t imageCount = (size_t)(totalSize / (imageSizes[0] * imageSizes[0] * 4)) / 2;
2807 const size_t bufCount = (size_t)(totalSize / bufSizeMin) / 2;
2808 const size_t percentToLeave = 30;
2809 RandomNumberGenerator rand = { 234522 };
2810
2811 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
2812 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2813 imageInfo.extent.depth = 1;
2814 imageInfo.mipLevels = 1;
2815 imageInfo.arrayLayers = 1;
2816 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
2817 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
2818 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
2819 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
2820 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2821
2822 VmaAllocationCreateInfo allocCreateInfo = {};
2823 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
2824
2825 // Create all intended images.
2826 for(size_t i = 0; i < imageCount; ++i)
2827 {
2828 const uint32_t size = imageSizes[rand.Generate() % 3];
2829
2830 imageInfo.extent.width = size;
2831 imageInfo.extent.height = size;
2832
2833 AllocInfo alloc;
2834 CreateImage(allocCreateInfo, imageInfo, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, alloc);
2835 alloc.m_StartValue = 0;
2836
2837 allocations.push_back(alloc);
2838 }
2839
2840 // And all buffers
2841 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2842
2843 for(size_t i = 0; i < bufCount; ++i)
2844 {
2845 bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16);
2846 bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2847
2848 AllocInfo alloc;
2849 alloc.CreateBuffer(bufCreateInfo, allocCreateInfo);
2850 alloc.m_StartValue = 0;
2851
2852 allocations.push_back(alloc);
2853 }
2854
2855 // Destroy some percentage of them.
2856 {
2857 const size_t allocationsToDestroy = round_div<size_t>((imageCount + bufCount) * (100 - percentToLeave), 100);
2858 for(size_t i = 0; i < allocationsToDestroy; ++i)
2859 {
2860 const size_t index = rand.Generate() % allocations.size();
2861 allocations[index].Destroy();
2862 allocations.erase(allocations.begin() + index);
2863 }
2864 }
2865
2866 {
2867 // Set our user data pointers. A real application should probably be more clever here
2868 for (auto& alloc : allocations)
2869 vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
2870 }
2871
2872 // Fill them with meaningful data.
2873 UploadGpuData(allocations.data(), allocations.size());
2874
2875 wchar_t fileName[MAX_PATH];
2876 swprintf_s(fileName, L"GPU_defragmentation_incremental_complex_A_before.json");
2877 SaveAllocatorStatsToFile(fileName);
2878
2879 const size_t maxAdditionalAllocations = 100;
2880 std::vector<AllocInfo> additionalAllocations;
2881 additionalAllocations.reserve(maxAdditionalAllocations);
2882
2883 const auto makeAdditionalAllocation = [&]()
2884 {
2885 if (additionalAllocations.size() < maxAdditionalAllocations)
2886 {
2887 bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16);
2888 bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2889
2890 AllocInfo alloc;
2891 alloc.CreateBuffer(bufCreateInfo, allocCreateInfo);
2892
2893 additionalAllocations.push_back(alloc);
2894 vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &additionalAllocations.back());
2895 }
2896 };
2897
2898 // Defragment using GPU only.
2899 {
2900 VmaDefragmentationInfo defragInfo = {};
2901 defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT;
2902
2903 VmaDefragmentationContext ctx = VK_NULL_HANDLE;
2904 VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &ctx);
2905 TEST(res == VK_SUCCESS);
2906
2907 makeAdditionalAllocation();
2908
2909 VmaDefragmentationPassMoveInfo pass = {};
2910 while((res = vmaBeginDefragmentationPass(g_hAllocator, ctx, &pass)) == VK_INCOMPLETE)
2911 {
2912 makeAdditionalAllocation();
2913
2914 // Ignore data outside of test
2915 for (uint32_t i = 0; i < pass.moveCount; ++i)
2916 {
2917 auto it = std::find_if(allocations.begin(), allocations.end(), [&](const AllocInfo& info) { return pass.pMoves[i].srcAllocation == info.m_Allocation; });
2918 if (it == allocations.end())
2919 {
2920 auto it = std::find_if(additionalAllocations.begin(), additionalAllocations.end(), [&](const AllocInfo& info) { return pass.pMoves[i].srcAllocation == info.m_Allocation; });
2921 if (it == additionalAllocations.end())
2922 pass.pMoves[i].operation = VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE;
2923 }
2924 }
2925
2926 BeginSingleTimeCommands();
2927 ProcessDefragmentationPass(pass);
2928 EndSingleTimeCommands();
2929
2930 makeAdditionalAllocation();
2931
2932 // Destroy old buffers/images and replace them with new handles.
2933 for (size_t i = 0; i < pass.moveCount; ++i)
2934 {
2935 if (pass.pMoves[i].operation != VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE)
2936 {
2937 VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
2938 VmaAllocationInfo vmaAllocInfo;
2939 vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
2940 AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
2941
2942 if (allocInfo->m_Buffer)
2943 {
2944 assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
2945 vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
2946 allocInfo->m_Buffer = allocInfo->m_NewBuffer;
2947 allocInfo->m_NewBuffer = VK_NULL_HANDLE;
2948 }
2949 else if (allocInfo->m_Image)
2950 {
2951 assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer);
2952 vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs);
2953 allocInfo->m_Image = allocInfo->m_NewImage;
2954 allocInfo->m_NewImage = VK_NULL_HANDLE;
2955 }
2956 else
2957 assert(0);
2958 }
2959 }
2960
2961 if ((res = vmaEndDefragmentationPass(g_hAllocator, ctx, &pass)) == VK_SUCCESS)
2962 break;
2963 TEST(res == VK_INCOMPLETE);
2964
2965 makeAdditionalAllocation();
2966 }
2967
2968 TEST(res == VK_SUCCESS);
2969 VmaDefragmentationStats stats = {};
2970 vmaEndDefragmentation(g_hAllocator, ctx, &stats);
2971
2972 // If corruption detection is enabled, GPU defragmentation may not work on
2973 // memory types that have this detection active, e.g. on Intel.
2974 #if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0
2975 TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0);
2976 TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0);
2977 #endif
2978 }
2979
2980 //ValidateGpuData(allocations.data(), allocations.size());
2981
2982 swprintf_s(fileName, L"GPU_defragmentation_incremental_complex_B_after.json");
2983 SaveAllocatorStatsToFile(fileName);
2984
2985 // Destroy all remaining buffers.
2986 for(size_t i = allocations.size(); i--; )
2987 {
2988 allocations[i].Destroy();
2989 }
2990
2991 for(size_t i = additionalAllocations.size(); i--; )
2992 {
2993 additionalAllocations[i].Destroy();
2994 }
2995 }
2996
TestUserData()2997 static void TestUserData()
2998 {
2999 VkResult res;
3000
3001 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
3002 bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
3003 bufCreateInfo.size = 0x10000;
3004
3005 for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
3006 {
3007 // Opaque pointer
3008 {
3009
3010 void* numberAsPointer = (void*)(size_t)0xC2501FF3u;
3011 void* pointerToSomething = &res;
3012
3013 VmaAllocationCreateInfo allocCreateInfo = {};
3014 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
3015 allocCreateInfo.pUserData = numberAsPointer;
3016 if(testIndex == 1)
3017 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
3018
3019 VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo;
3020 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
3021 TEST(res == VK_SUCCESS);
3022 TEST(allocInfo.pUserData == numberAsPointer);
3023
3024 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
3025 TEST(allocInfo.pUserData == numberAsPointer);
3026
3027 vmaSetAllocationUserData(g_hAllocator, alloc, pointerToSomething);
3028 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
3029 TEST(allocInfo.pUserData == pointerToSomething);
3030
3031 vmaDestroyBuffer(g_hAllocator, buf, alloc);
3032 }
3033
3034 // String
3035 {
3036 const char* name1 = "Buffer name \\\"\'<>&% \nSecond line .,;=";
3037 const char* name2 = "2";
3038 const size_t name1Len = strlen(name1);
3039
3040 char* name1Buf = new char[name1Len + 1];
3041 strcpy_s(name1Buf, name1Len + 1, name1);
3042
3043 VmaAllocationCreateInfo allocCreateInfo = {};
3044 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
3045 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
3046 allocCreateInfo.pUserData = name1Buf;
3047 if(testIndex == 1)
3048 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
3049
3050 VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo;
3051 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
3052 TEST(res == VK_SUCCESS);
3053 TEST(allocInfo.pName != nullptr && allocInfo.pName != name1Buf);
3054 TEST(strcmp(name1, allocInfo.pName) == 0);
3055
3056 delete[] name1Buf;
3057
3058 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
3059 TEST(strcmp(name1, allocInfo.pName) == 0);
3060
3061 vmaSetAllocationName(g_hAllocator, alloc, name2);
3062 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
3063 TEST(strcmp(name2, allocInfo.pName) == 0);
3064
3065 vmaSetAllocationName(g_hAllocator, alloc, nullptr);
3066 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
3067 TEST(allocInfo.pName == nullptr);
3068
3069 vmaDestroyBuffer(g_hAllocator, buf, alloc);
3070 }
3071 }
3072 }
3073
TestInvalidAllocations()3074 static void TestInvalidAllocations()
3075 {
3076 VkResult res;
3077
3078 VmaAllocationCreateInfo allocCreateInfo = {};
3079
3080 // Try to allocate 0 bytes.
3081 {
3082 VkMemoryRequirements memReq = {};
3083 memReq.size = 0; // !!!
3084 memReq.alignment = 4;
3085 memReq.memoryTypeBits = UINT32_MAX;
3086 VmaAllocation alloc = VK_NULL_HANDLE;
3087 res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr);
3088 TEST(res == VK_ERROR_INITIALIZATION_FAILED && alloc == VK_NULL_HANDLE);
3089 }
3090
3091 // Try to create buffer with size = 0.
3092 {
3093 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
3094 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
3095 bufCreateInfo.size = 0; // !!!
3096 VkBuffer buf = VK_NULL_HANDLE;
3097 VmaAllocation alloc = VK_NULL_HANDLE;
3098 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, nullptr);
3099 TEST(res == VK_ERROR_INITIALIZATION_FAILED && buf == VK_NULL_HANDLE && alloc == VK_NULL_HANDLE);
3100 }
3101
3102 // Try to create image with one dimension = 0.
3103 {
3104 VkImageCreateInfo imageCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
3105 imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
3106 imageCreateInfo.format = VK_FORMAT_B8G8R8A8_UNORM;
3107 imageCreateInfo.extent.width = 128;
3108 imageCreateInfo.extent.height = 0; // !!!
3109 imageCreateInfo.extent.depth = 1;
3110 imageCreateInfo.mipLevels = 1;
3111 imageCreateInfo.arrayLayers = 1;
3112 imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
3113 imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
3114 imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
3115 imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
3116 VkImage image = VK_NULL_HANDLE;
3117 VmaAllocation alloc = VK_NULL_HANDLE;
3118 res = vmaCreateImage(g_hAllocator, &imageCreateInfo, &allocCreateInfo, &image, &alloc, nullptr);
3119 TEST(res == VK_ERROR_INITIALIZATION_FAILED && image == VK_NULL_HANDLE && alloc == VK_NULL_HANDLE);
3120 }
3121 }
3122
TestMemoryRequirements()3123 static void TestMemoryRequirements()
3124 {
3125 VkResult res;
3126 VkBuffer buf;
3127 VmaAllocation alloc;
3128 VmaAllocationInfo allocInfo;
3129
3130 const VkPhysicalDeviceMemoryProperties* memProps;
3131 vmaGetMemoryProperties(g_hAllocator, &memProps);
3132
3133 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
3134 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
3135 bufInfo.size = 128;
3136
3137 VmaAllocationCreateInfo allocCreateInfo = {};
3138
3139 // No requirements.
3140 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
3141 TEST(res == VK_SUCCESS);
3142 vmaDestroyBuffer(g_hAllocator, buf, alloc);
3143
3144 // Usage = auto + host access.
3145 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
3146 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
3147 allocCreateInfo.requiredFlags = 0;
3148 allocCreateInfo.preferredFlags = 0;
3149 allocCreateInfo.memoryTypeBits = UINT32_MAX;
3150
3151 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
3152 TEST(res == VK_SUCCESS);
3153 TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
3154 vmaDestroyBuffer(g_hAllocator, buf, alloc);
3155
3156 // Required flags, preferred flags.
3157 allocCreateInfo.usage = VMA_MEMORY_USAGE_UNKNOWN;
3158 allocCreateInfo.flags = 0;
3159 allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
3160 allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
3161 allocCreateInfo.memoryTypeBits = 0;
3162
3163 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
3164 TEST(res == VK_SUCCESS);
3165 TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
3166 TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
3167 vmaDestroyBuffer(g_hAllocator, buf, alloc);
3168
3169 // memoryTypeBits.
3170 const uint32_t memType = allocInfo.memoryType;
3171 allocCreateInfo.usage = VMA_MEMORY_USAGE_UNKNOWN;
3172 allocCreateInfo.flags = 0;
3173 allocCreateInfo.requiredFlags = 0;
3174 allocCreateInfo.preferredFlags = 0;
3175 allocCreateInfo.memoryTypeBits = 1u << memType;
3176
3177 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
3178 TEST(res == VK_SUCCESS);
3179 TEST(allocInfo.memoryType == memType);
3180 vmaDestroyBuffer(g_hAllocator, buf, alloc);
3181
3182 }
3183
TestGetAllocatorInfo()3184 static void TestGetAllocatorInfo()
3185 {
3186 wprintf(L"Test vnaGetAllocatorInfo\n");
3187
3188 VmaAllocatorInfo allocInfo = {};
3189 vmaGetAllocatorInfo(g_hAllocator, &allocInfo);
3190 TEST(allocInfo.instance == g_hVulkanInstance);
3191 TEST(allocInfo.physicalDevice == g_hPhysicalDevice);
3192 TEST(allocInfo.device == g_hDevice);
3193 }
3194
TestBasics()3195 static void TestBasics()
3196 {
3197 wprintf(L"Test basics\n");
3198
3199 VkResult res;
3200
3201 TestGetAllocatorInfo();
3202
3203 TestMemoryRequirements();
3204
3205 // Allocation that is MAPPED and not necessarily HOST_VISIBLE.
3206 {
3207 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
3208 bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
3209 bufCreateInfo.size = 128;
3210
3211 VmaAllocationCreateInfo allocCreateInfo = {};
3212 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
3213 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
3214
3215 VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo;
3216 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
3217 TEST(res == VK_SUCCESS);
3218
3219 vmaDestroyBuffer(g_hAllocator, buf, alloc);
3220
3221 // Same with DEDICATED_MEMORY.
3222 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
3223
3224 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
3225 TEST(res == VK_SUCCESS);
3226
3227 vmaDestroyBuffer(g_hAllocator, buf, alloc);
3228 }
3229
3230 TestUserData();
3231
3232 TestInvalidAllocations();
3233 }
3234
TestVirtualBlocks()3235 static void TestVirtualBlocks()
3236 {
3237 wprintf(L"Test virtual blocks\n");
3238
3239 const VkDeviceSize blockSize = 16 * MEGABYTE;
3240 const VkDeviceSize alignment = 256;
3241 VkDeviceSize offset;
3242
3243 // # Create block 16 MB
3244
3245 VmaVirtualBlockCreateInfo blockCreateInfo = {};
3246 blockCreateInfo.pAllocationCallbacks = g_Allocs;
3247 blockCreateInfo.size = blockSize;
3248 VmaVirtualBlock block;
3249 TEST(vmaCreateVirtualBlock(&blockCreateInfo, &block) == VK_SUCCESS && block);
3250
3251 // # Allocate 8 MB (also fetch offset from the allocation)
3252
3253 VmaVirtualAllocationCreateInfo allocCreateInfo = {};
3254 allocCreateInfo.alignment = alignment;
3255 allocCreateInfo.pUserData = (void*)(uintptr_t)1;
3256 allocCreateInfo.size = 8 * MEGABYTE;
3257 VmaVirtualAllocation allocation0 = VK_NULL_HANDLE;
3258 TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation0, &offset) == VK_SUCCESS);
3259 TEST(allocation0 != VK_NULL_HANDLE);
3260
3261 // # Validate the allocation
3262
3263 VmaVirtualAllocationInfo allocInfo0 = {};
3264 vmaGetVirtualAllocationInfo(block, allocation0, &allocInfo0);
3265 TEST(allocInfo0.offset < blockSize);
3266 TEST(allocInfo0.offset == offset);
3267 TEST(allocInfo0.size == allocCreateInfo.size);
3268 TEST(allocInfo0.pUserData == allocCreateInfo.pUserData);
3269
3270 // # Check SetUserData
3271
3272 vmaSetVirtualAllocationUserData(block, allocation0, (void*)(uintptr_t)2);
3273 vmaGetVirtualAllocationInfo(block, allocation0, &allocInfo0);
3274 TEST(allocInfo0.pUserData == (void*)(uintptr_t)2);
3275
3276 // # Allocate 4 MB (also test passing null as pOffset during allocation)
3277
3278 allocCreateInfo.size = 4 * MEGABYTE;
3279 VmaVirtualAllocation allocation1 = VK_NULL_HANDLE;
3280 TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation1, nullptr) == VK_SUCCESS);
3281 TEST(allocation1 != VK_NULL_HANDLE);
3282 VmaVirtualAllocationInfo allocInfo1 = {};
3283 vmaGetVirtualAllocationInfo(block, allocation1, &allocInfo1);
3284 TEST(allocInfo1.offset < blockSize);
3285 TEST(allocInfo1.offset + 4 * MEGABYTE <= allocInfo0.offset || allocInfo0.offset + 8 * MEGABYTE <= allocInfo1.offset); // Check if they don't overlap.
3286
3287 // # Allocate another 8 MB - it should fail
3288
3289 allocCreateInfo.size = 8 * MEGABYTE;
3290 VmaVirtualAllocation allocation2 = VK_NULL_HANDLE;
3291 TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation2, &offset) < 0);
3292 TEST(allocation2 == VK_NULL_HANDLE);
3293 TEST(offset == UINT64_MAX);
3294
3295 // # Free the 4 MB block. Now allocation of 8 MB should succeed.
3296
3297 vmaVirtualFree(block, allocation1);
3298 TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation2, nullptr) == VK_SUCCESS);
3299 TEST(allocation2 != VK_NULL_HANDLE);
3300 VmaVirtualAllocationInfo allocInfo2 = {};
3301 vmaGetVirtualAllocationInfo(block, allocation2, &allocInfo2);
3302 TEST(allocInfo2.offset < blockSize);
3303 TEST(allocInfo2.offset + 4 * MEGABYTE <= allocInfo0.offset || allocInfo0.offset + 8 * MEGABYTE <= allocInfo2.offset); // Check if they don't overlap.
3304
3305 // # Calculate statistics
3306
3307 VmaDetailedStatistics statInfo = {};
3308 vmaCalculateVirtualBlockStatistics(block, &statInfo);
3309 TEST(statInfo.statistics.allocationCount == 2);
3310 TEST(statInfo.statistics.blockCount == 1);
3311 TEST(statInfo.statistics.allocationBytes == blockSize);
3312 TEST(statInfo.statistics.blockBytes == blockSize);
3313
3314 // # Generate JSON dump
3315
3316 #if !defined(VMA_STATS_STRING_ENABLED) || VMA_STATS_STRING_ENABLED
3317 char* json = nullptr;
3318 vmaBuildVirtualBlockStatsString(block, &json, VK_TRUE);
3319 {
3320 std::string str(json);
3321 TEST( str.find("\"CustomData\": \"0000000000000001\"") != std::string::npos );
3322 TEST( str.find("\"CustomData\": \"0000000000000002\"") != std::string::npos );
3323 }
3324 vmaFreeVirtualBlockStatsString(block, json);
3325 #endif
3326
3327 // # Free alloc0, leave alloc2 unfreed.
3328
3329 vmaVirtualFree(block, allocation0);
3330
3331 // # Test free of null allocation.
3332 vmaVirtualFree(block, VK_NULL_HANDLE);
3333
3334 // # Test alignment
3335
3336 {
3337 constexpr size_t allocCount = 10;
3338 VmaVirtualAllocation allocations[allocCount] = {};
3339 for(size_t i = 0; i < allocCount; ++i)
3340 {
3341 const bool alignment0 = i == allocCount - 1;
3342 allocCreateInfo.size = i * 3 + 15;
3343 allocCreateInfo.alignment = alignment0 ? 0 : 8;
3344 TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocations[i], nullptr) == VK_SUCCESS);
3345 TEST(allocations[i] != VK_NULL_HANDLE);
3346 if(!alignment0)
3347 {
3348 VmaVirtualAllocationInfo info;
3349 vmaGetVirtualAllocationInfo(block, allocations[i], &info);
3350 TEST(info.offset % allocCreateInfo.alignment == 0);
3351 }
3352 }
3353
3354 for(size_t i = allocCount; i--; )
3355 {
3356 vmaVirtualFree(block, allocations[i]);
3357 }
3358 }
3359
3360 // # Final cleanup
3361
3362 vmaVirtualFree(block, allocation2);
3363 vmaDestroyVirtualBlock(block);
3364
3365 {
3366 // Another virtual block, using Clear this time.
3367 TEST(vmaCreateVirtualBlock(&blockCreateInfo, &block) == VK_SUCCESS);
3368
3369 allocCreateInfo = VmaVirtualAllocationCreateInfo{};
3370 allocCreateInfo.size = MEGABYTE;
3371
3372 for(size_t i = 0; i < 8; ++i)
3373 {
3374 VmaVirtualAllocation allocation;
3375 TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation, nullptr) == VK_SUCCESS);
3376 }
3377
3378 vmaClearVirtualBlock(block);
3379 vmaDestroyVirtualBlock(block);
3380 }
3381 }
3382
TestVirtualBlocksAlgorithms()3383 static void TestVirtualBlocksAlgorithms()
3384 {
3385 wprintf(L"Test virtual blocks algorithms\n");
3386
3387 RandomNumberGenerator rand{3454335};
3388 auto calcRandomAllocSize = [&rand]() -> VkDeviceSize { return rand.Generate() % 20 + 5; };
3389
3390 for(size_t algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex)
3391 {
3392 // Create the block
3393 VmaVirtualBlockCreateInfo blockCreateInfo = {};
3394 blockCreateInfo.pAllocationCallbacks = g_Allocs;
3395 blockCreateInfo.size = 10'000;
3396 switch(algorithmIndex)
3397 {
3398 case 1: blockCreateInfo.flags = VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT; break;
3399 }
3400 VmaVirtualBlock block = nullptr;
3401 VkResult res = vmaCreateVirtualBlock(&blockCreateInfo, &block);
3402 TEST(res == VK_SUCCESS);
3403
3404 struct AllocData
3405 {
3406 VmaVirtualAllocation allocation;
3407 VkDeviceSize allocOffset, requestedSize, allocationSize;
3408 };
3409 std::vector<AllocData> allocations;
3410
3411 // Make some allocations
3412 for(size_t i = 0; i < 20; ++i)
3413 {
3414 VmaVirtualAllocationCreateInfo allocCreateInfo = {};
3415 allocCreateInfo.size = calcRandomAllocSize();
3416 allocCreateInfo.pUserData = (void*)(uintptr_t)(allocCreateInfo.size * 10);
3417 if(i < 10) { }
3418 else if(i < 12) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT;
3419 else if(i < 14) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT;
3420 else if(i < 16) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT;
3421 else if(i < 18 && algorithmIndex == 1) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
3422
3423 AllocData alloc = {};
3424 alloc.requestedSize = allocCreateInfo.size;
3425 res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.allocation, nullptr);
3426 TEST(res == VK_SUCCESS);
3427
3428 VmaVirtualAllocationInfo allocInfo;
3429 vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
3430 TEST(allocInfo.size >= allocCreateInfo.size);
3431 alloc.allocOffset = allocInfo.offset;
3432 alloc.allocationSize = allocInfo.size;
3433
3434 allocations.push_back(alloc);
3435 }
3436
3437 // Free some of the allocations
3438 for(size_t i = 0; i < 5; ++i)
3439 {
3440 const size_t index = rand.Generate() % allocations.size();
3441 vmaVirtualFree(block, allocations[index].allocation);
3442 allocations.erase(allocations.begin() + index);
3443 }
3444
3445 // Allocate some more
3446 for(size_t i = 0; i < 6; ++i)
3447 {
3448 VmaVirtualAllocationCreateInfo allocCreateInfo = {};
3449 allocCreateInfo.size = calcRandomAllocSize();
3450 allocCreateInfo.pUserData = (void*)(uintptr_t)(allocCreateInfo.size * 10);
3451
3452 AllocData alloc = {};
3453 alloc.requestedSize = allocCreateInfo.size;
3454 res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.allocation, nullptr);
3455 TEST(res == VK_SUCCESS);
3456
3457 VmaVirtualAllocationInfo allocInfo;
3458 vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
3459 TEST(allocInfo.size >= allocCreateInfo.size);
3460 alloc.allocOffset = allocInfo.offset;
3461 alloc.allocationSize = allocInfo.size;
3462
3463 allocations.push_back(alloc);
3464 }
3465
3466 // Allocate some with extra alignment
3467 for(size_t i = 0; i < 3; ++i)
3468 {
3469 VmaVirtualAllocationCreateInfo allocCreateInfo = {};
3470 allocCreateInfo.size = calcRandomAllocSize();
3471 allocCreateInfo.alignment = 16;
3472 allocCreateInfo.pUserData = (void*)(uintptr_t)(allocCreateInfo.size * 10);
3473
3474 AllocData alloc = {};
3475 alloc.requestedSize = allocCreateInfo.size;
3476 res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.allocation, nullptr);
3477 TEST(res == VK_SUCCESS);
3478
3479 VmaVirtualAllocationInfo allocInfo;
3480 vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
3481 TEST(allocInfo.offset % 16 == 0);
3482 TEST(allocInfo.size >= allocCreateInfo.size);
3483 alloc.allocOffset = allocInfo.offset;
3484 alloc.allocationSize = allocInfo.size;
3485
3486 allocations.push_back(alloc);
3487 }
3488
3489 // Check if the allocations don't overlap
3490 std::sort(allocations.begin(), allocations.end(), [](const AllocData& lhs, const AllocData& rhs) {
3491 return lhs.allocOffset < rhs.allocOffset; });
3492 for(size_t i = 0; i < allocations.size() - 1; ++i)
3493 {
3494 TEST(allocations[i+1].allocOffset >= allocations[i].allocOffset + allocations[i].allocationSize);
3495 }
3496
3497 // Check pUserData
3498 {
3499 const AllocData& alloc = allocations.back();
3500 VmaVirtualAllocationInfo allocInfo = {};
3501 vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
3502 TEST((uintptr_t)allocInfo.pUserData == alloc.requestedSize * 10);
3503
3504 vmaSetVirtualAllocationUserData(block, alloc.allocation, (void*)(uintptr_t)666);
3505 vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
3506 TEST((uintptr_t)allocInfo.pUserData == 666);
3507 }
3508
3509 // Calculate statistics
3510 {
3511 VkDeviceSize actualAllocSizeMin = VK_WHOLE_SIZE, actualAllocSizeMax = 0, actualAllocSizeSum = 0;
3512 std::for_each(allocations.begin(), allocations.end(), [&](const AllocData& a) {
3513 actualAllocSizeMin = std::min(actualAllocSizeMin, a.allocationSize);
3514 actualAllocSizeMax = std::max(actualAllocSizeMax, a.allocationSize);
3515 actualAllocSizeSum += a.allocationSize;
3516 });
3517
3518 VmaDetailedStatistics statInfo = {};
3519 vmaCalculateVirtualBlockStatistics(block, &statInfo);
3520 TEST(statInfo.statistics.allocationCount == allocations.size());
3521 TEST(statInfo.statistics.blockCount == 1);
3522 TEST(statInfo.statistics.blockBytes == blockCreateInfo.size);
3523 TEST(statInfo.allocationSizeMax == actualAllocSizeMax);
3524 TEST(statInfo.allocationSizeMin == actualAllocSizeMin);
3525 TEST(statInfo.statistics.allocationBytes >= actualAllocSizeSum);
3526 }
3527
3528 #if !defined(VMA_STATS_STRING_ENABLED) || VMA_STATS_STRING_ENABLED
3529 // Build JSON dump string
3530 {
3531 char* json = nullptr;
3532 vmaBuildVirtualBlockStatsString(block, &json, VK_TRUE);
3533 int I = 0; // put a breakpoint here to debug
3534 vmaFreeVirtualBlockStatsString(block, json);
3535 }
3536 #endif
3537
3538 // Final cleanup
3539 vmaClearVirtualBlock(block);
3540 vmaDestroyVirtualBlock(block);
3541 }
3542 }
3543
TestAllocationVersusResourceSize()3544 static void TestAllocationVersusResourceSize()
3545 {
3546 wprintf(L"Test allocation versus resource size\n");
3547
3548 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
3549 bufCreateInfo.size = 22921; // Prime number
3550 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
3551
3552 VmaAllocationCreateInfo allocCreateInfo = {};
3553 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
3554 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
3555
3556 for(uint32_t i = 0; i < 2; ++i)
3557 {
3558 if(i == 1)
3559 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
3560 else
3561 allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
3562
3563 AllocInfo info;
3564 info.CreateBuffer(bufCreateInfo, allocCreateInfo);
3565
3566 VmaAllocationInfo allocInfo = {};
3567 vmaGetAllocationInfo(g_hAllocator, info.m_Allocation, &allocInfo);
3568 //wprintf(L" Buffer size = %llu, allocation size = %llu\n", bufCreateInfo.size, allocInfo.size);
3569
3570 // Map and test accessing entire area of the allocation, not only the buffer.
3571 void* mappedPtr = nullptr;
3572 VkResult res = vmaMapMemory(g_hAllocator, info.m_Allocation, &mappedPtr);
3573 TEST(res == VK_SUCCESS);
3574
3575 memset(mappedPtr, 0xCC, (size_t)allocInfo.size);
3576
3577 vmaUnmapMemory(g_hAllocator, info.m_Allocation);
3578
3579 info.Destroy();
3580 }
3581 }
3582
TestPool_MinBlockCount()3583 static void TestPool_MinBlockCount()
3584 {
3585 #if defined(VMA_DEBUG_MARGIN) && VMA_DEBUG_MARGIN > 0
3586 return;
3587 #endif
3588
3589 wprintf(L"Test Pool MinBlockCount\n");
3590 VkResult res;
3591
3592 static const VkDeviceSize ALLOC_SIZE = 512ull * 1024;
3593 static const VkDeviceSize BLOCK_SIZE = ALLOC_SIZE * 2; // Each block can fit 2 allocations.
3594
3595 VmaAllocationCreateInfo allocCreateInfo = {};
3596 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
3597
3598 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
3599 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
3600 bufCreateInfo.size = ALLOC_SIZE;
3601
3602 VmaPoolCreateInfo poolCreateInfo = {};
3603 poolCreateInfo.blockSize = BLOCK_SIZE;
3604 poolCreateInfo.minBlockCount = 2; // At least 2 blocks always present.
3605 res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex);
3606 TEST(res == VK_SUCCESS);
3607
3608 VmaPool pool = VK_NULL_HANDLE;
3609 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
3610 TEST(res == VK_SUCCESS && pool != VK_NULL_HANDLE);
3611
3612 // Check that there are 2 blocks preallocated as requested.
3613 VmaDetailedStatistics begPoolStats = {};
3614 vmaCalculatePoolStatistics(g_hAllocator, pool, &begPoolStats);
3615 TEST(begPoolStats.statistics.blockCount == 2 &&
3616 begPoolStats.statistics.allocationCount == 0 &&
3617 begPoolStats.statistics.blockBytes == BLOCK_SIZE * 2);
3618
3619 // Allocate 5 buffers to create 3 blocks.
3620 static const uint32_t BUF_COUNT = 5;
3621 allocCreateInfo.pool = pool;
3622 std::vector<AllocInfo> allocs(BUF_COUNT);
3623 for(uint32_t i = 0; i < BUF_COUNT; ++i)
3624 {
3625 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &allocs[i].m_Buffer, &allocs[i].m_Allocation, nullptr);
3626 TEST(res == VK_SUCCESS && allocs[i].m_Buffer != VK_NULL_HANDLE && allocs[i].m_Allocation != VK_NULL_HANDLE);
3627 }
3628
3629 // Check that there are really 3 blocks.
3630 VmaDetailedStatistics poolStats2 = {};
3631 vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats2);
3632 TEST(poolStats2.statistics.blockCount == 3 &&
3633 poolStats2.statistics.allocationCount == BUF_COUNT &&
3634 poolStats2.statistics.blockBytes == BLOCK_SIZE * 3);
3635
3636 // Free two first allocations to make one block empty.
3637 allocs[0].Destroy();
3638 allocs[1].Destroy();
3639
3640 // Check that there are still 3 blocks due to hysteresis.
3641 VmaDetailedStatistics poolStats3 = {};
3642 vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats3);
3643 TEST(poolStats3.statistics.blockCount == 3 &&
3644 poolStats3.statistics.allocationCount == BUF_COUNT - 2 &&
3645 poolStats2.statistics.blockBytes == BLOCK_SIZE * 3);
3646
3647 // Free the last allocation to make second block empty.
3648 allocs[BUF_COUNT - 1].Destroy();
3649
3650 // Check that there are now 2 blocks only.
3651 VmaDetailedStatistics poolStats4 = {};
3652 vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats4);
3653 TEST(poolStats4.statistics.blockCount == 2 &&
3654 poolStats4.statistics.allocationCount == BUF_COUNT - 3 &&
3655 poolStats4.statistics.blockBytes == BLOCK_SIZE * 2);
3656
3657 // Cleanup.
3658 for(size_t i = allocs.size(); i--; )
3659 {
3660 allocs[i].Destroy();
3661 }
3662 vmaDestroyPool(g_hAllocator, pool);
3663 }
3664
TestPool_MinAllocationAlignment()3665 static void TestPool_MinAllocationAlignment()
3666 {
3667 wprintf(L"Test Pool MinAllocationAlignment\n");
3668 VkResult res;
3669
3670 static const VkDeviceSize ALLOC_SIZE = 32;
3671 static const VkDeviceSize BLOCK_SIZE = 1024 * 1024;
3672 static const VkDeviceSize MIN_ALLOCATION_ALIGNMENT = 64 * 1024;
3673
3674 VmaAllocationCreateInfo allocCreateInfo = {};
3675 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
3676
3677 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
3678 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
3679 bufCreateInfo.size = ALLOC_SIZE;
3680
3681 VmaPoolCreateInfo poolCreateInfo = {};
3682 poolCreateInfo.blockSize = BLOCK_SIZE;
3683 poolCreateInfo.minAllocationAlignment = MIN_ALLOCATION_ALIGNMENT;
3684 res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex);
3685 TEST(res == VK_SUCCESS);
3686
3687 VmaPool pool = VK_NULL_HANDLE;
3688 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
3689 TEST(res == VK_SUCCESS && pool != VK_NULL_HANDLE);
3690
3691 static const uint32_t BUF_COUNT = 4;
3692 allocCreateInfo = {};
3693 allocCreateInfo.pool = pool;
3694 std::vector<AllocInfo> allocs(BUF_COUNT);
3695 for(uint32_t i = 0; i < BUF_COUNT; ++i)
3696 {
3697 VmaAllocationInfo allocInfo = {};
3698 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &allocs[i].m_Buffer, &allocs[i].m_Allocation, &allocInfo);
3699 TEST(res == VK_SUCCESS && allocs[i].m_Buffer != VK_NULL_HANDLE && allocs[i].m_Allocation != VK_NULL_HANDLE);
3700 TEST(allocInfo.offset % MIN_ALLOCATION_ALIGNMENT == 0);
3701 }
3702
3703 // Cleanup.
3704 for(size_t i = allocs.size(); i--; )
3705 {
3706 allocs[i].Destroy();
3707 }
3708 vmaDestroyPool(g_hAllocator, pool);
3709 }
3710
TestPoolsAndAllocationParameters()3711 static void TestPoolsAndAllocationParameters()
3712 {
3713 wprintf(L"Test pools and allocation parameters\n");
3714
3715 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
3716 bufCreateInfo.size = 1 * MEGABYTE;
3717 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
3718
3719 VmaAllocationCreateInfo allocCreateInfo = {};
3720
3721 uint32_t memTypeIndex = UINT32_MAX;
3722 VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &memTypeIndex);
3723 TEST(res == VK_SUCCESS);
3724
3725 VmaPool pool1 = nullptr, pool2 = nullptr;
3726 std::vector<BufferInfo> bufs;
3727
3728 uint32_t totalNewAllocCount = 0, totalNewBlockCount = 0;
3729 VmaTotalStatistics statsBeg, statsEnd;
3730 vmaCalculateStatistics(g_hAllocator, &statsBeg);
3731
3732 // poolTypeI:
3733 // 0 = default pool
3734 // 1 = custom pool, default (flexible) block size and block count
3735 // 2 = custom pool, fixed block size and limited block count
3736 for(size_t poolTypeI = 0; poolTypeI < 3; ++poolTypeI)
3737 {
3738 if(poolTypeI == 0)
3739 {
3740 allocCreateInfo.pool = nullptr;
3741 }
3742 else if(poolTypeI == 1)
3743 {
3744 VmaPoolCreateInfo poolCreateInfo = {};
3745 poolCreateInfo.memoryTypeIndex = memTypeIndex;
3746 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool1);
3747 TEST(res == VK_SUCCESS);
3748 allocCreateInfo.pool = pool1;
3749 }
3750 else if(poolTypeI == 2)
3751 {
3752 VmaPoolCreateInfo poolCreateInfo = {};
3753 poolCreateInfo.memoryTypeIndex = memTypeIndex;
3754 poolCreateInfo.maxBlockCount = 1;
3755 poolCreateInfo.blockSize = 2 * MEGABYTE + MEGABYTE / 2; // 2.5 MB
3756 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool2);
3757 TEST(res == VK_SUCCESS);
3758 allocCreateInfo.pool = pool2;
3759 }
3760
3761 uint32_t poolAllocCount = 0, poolBlockCount = 0;
3762 BufferInfo bufInfo = {};
3763 VmaAllocationInfo allocInfo[4] = {};
3764
3765 // Default parameters
3766 allocCreateInfo.flags = 0;
3767 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo[0]);
3768 TEST(res == VK_SUCCESS && bufInfo.Allocation && bufInfo.Buffer);
3769 bufs.push_back(std::move(bufInfo));
3770 ++poolAllocCount;
3771
3772 // DEDICATED. Should not try pool2 as it asserts on invalid call.
3773 if(poolTypeI != 2)
3774 {
3775 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
3776 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo[1]);
3777 TEST(res == VK_SUCCESS && bufInfo.Allocation && bufInfo.Buffer);
3778 TEST(allocInfo[1].offset == 0); // Dedicated
3779 TEST(allocInfo[1].deviceMemory != allocInfo[0].deviceMemory); // Dedicated
3780 bufs.push_back(std::move(bufInfo));
3781 ++poolAllocCount;
3782 }
3783
3784 // NEVER_ALLOCATE #1
3785 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT;
3786 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo[2]);
3787 TEST(res == VK_SUCCESS && bufInfo.Allocation && bufInfo.Buffer);
3788 TEST(allocInfo[2].deviceMemory == allocInfo[0].deviceMemory); // Same memory block as default one.
3789 TEST(allocInfo[2].offset != allocInfo[0].offset);
3790 bufs.push_back(std::move(bufInfo));
3791 ++poolAllocCount;
3792
3793 // NEVER_ALLOCATE #2. Should fail in pool2 as it has no space.
3794 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT;
3795 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo[3]);
3796 if(poolTypeI == 2)
3797 TEST(res < 0);
3798 else
3799 {
3800 TEST(res == VK_SUCCESS && bufInfo.Allocation && bufInfo.Buffer);
3801 bufs.push_back(std::move(bufInfo));
3802 ++poolAllocCount;
3803 }
3804
3805 // Pool stats
3806 switch(poolTypeI)
3807 {
3808 case 0: poolBlockCount = 1; break; // At least 1 added for dedicated allocation.
3809 case 1: poolBlockCount = 2; break; // 1 for custom pool block and 1 for dedicated allocation.
3810 case 2: poolBlockCount = 1; break; // Only custom pool, no dedicated allocation.
3811 }
3812
3813 if(poolTypeI > 0)
3814 {
3815 VmaDetailedStatistics poolStats = {};
3816 vmaCalculatePoolStatistics(g_hAllocator, poolTypeI == 2 ? pool2 : pool1, &poolStats);
3817 TEST(poolStats.statistics.allocationCount == poolAllocCount);
3818 const VkDeviceSize usedSize = poolStats.statistics.allocationBytes;
3819 TEST(usedSize == poolAllocCount * MEGABYTE);
3820 TEST(poolStats.statistics.blockCount == poolBlockCount);
3821 }
3822
3823 totalNewAllocCount += poolAllocCount;
3824 totalNewBlockCount += poolBlockCount;
3825 }
3826
3827 vmaCalculateStatistics(g_hAllocator, &statsEnd);
3828 TEST(statsEnd.total.statistics.allocationCount == statsBeg.total.statistics.allocationCount + totalNewAllocCount);
3829 TEST(statsEnd.total.statistics.blockCount >= statsBeg.total.statistics.blockCount + totalNewBlockCount);
3830 TEST(statsEnd.total.statistics.allocationBytes == statsBeg.total.statistics.allocationBytes + totalNewAllocCount * MEGABYTE);
3831
3832 for(auto& bufInfo : bufs)
3833 vmaDestroyBuffer(g_hAllocator, bufInfo.Buffer, bufInfo.Allocation);
3834
3835 vmaDestroyPool(g_hAllocator, pool2);
3836 vmaDestroyPool(g_hAllocator, pool1);
3837 }
3838
TestHeapSizeLimit()3839 void TestHeapSizeLimit()
3840 {
3841 wprintf(L"Test heap size limit\n");
3842
3843 const VkDeviceSize HEAP_SIZE_LIMIT = 100ull * 1024 * 1024; // 100 MB
3844 const VkDeviceSize BLOCK_SIZE = 10ull * 1024 * 1024; // 10 MB
3845
3846 VkDeviceSize heapSizeLimit[VK_MAX_MEMORY_HEAPS];
3847 for(uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i)
3848 {
3849 heapSizeLimit[i] = HEAP_SIZE_LIMIT;
3850 }
3851
3852 VmaAllocatorCreateInfo allocatorCreateInfo = {};
3853 allocatorCreateInfo.physicalDevice = g_hPhysicalDevice;
3854 allocatorCreateInfo.device = g_hDevice;
3855 allocatorCreateInfo.instance = g_hVulkanInstance;
3856 allocatorCreateInfo.pHeapSizeLimit = heapSizeLimit;
3857 #if VMA_DYNAMIC_VULKAN_FUNCTIONS
3858 VmaVulkanFunctions vulkanFunctions = {};
3859 vulkanFunctions.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
3860 vulkanFunctions.vkGetDeviceProcAddr = vkGetDeviceProcAddr;
3861 allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions;
3862 #endif
3863
3864 VmaAllocator hAllocator;
3865 VkResult res = vmaCreateAllocator(&allocatorCreateInfo, &hAllocator);
3866 TEST(res == VK_SUCCESS);
3867
3868 struct Item
3869 {
3870 VkBuffer hBuf;
3871 VmaAllocation hAlloc;
3872 };
3873 std::vector<Item> items;
3874
3875 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
3876 bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
3877
3878 // 1. Allocate two blocks of dedicated memory, half the size of BLOCK_SIZE.
3879 VmaAllocationInfo dedicatedAllocInfo;
3880 {
3881 VmaAllocationCreateInfo allocCreateInfo = {};
3882 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
3883 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
3884
3885 bufCreateInfo.size = BLOCK_SIZE / 2;
3886
3887 for(size_t i = 0; i < 2; ++i)
3888 {
3889 Item item;
3890 res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, &dedicatedAllocInfo);
3891 TEST(res == VK_SUCCESS);
3892 items.push_back(item);
3893 }
3894 }
3895
3896 // Create pool to make sure allocations must be out of this memory type.
3897 VmaPoolCreateInfo poolCreateInfo = {};
3898 poolCreateInfo.memoryTypeIndex = dedicatedAllocInfo.memoryType;
3899 poolCreateInfo.blockSize = BLOCK_SIZE;
3900
3901 VmaPool hPool;
3902 res = vmaCreatePool(hAllocator, &poolCreateInfo, &hPool);
3903 TEST(res == VK_SUCCESS);
3904
3905 // 2. Allocate normal buffers from all the remaining memory.
3906 {
3907 VmaAllocationCreateInfo allocCreateInfo = {};
3908 allocCreateInfo.pool = hPool;
3909
3910 bufCreateInfo.size = BLOCK_SIZE / 2;
3911
3912 const size_t bufCount = ((HEAP_SIZE_LIMIT / BLOCK_SIZE) - 1) * 2;
3913 for(size_t i = 0; i < bufCount; ++i)
3914 {
3915 Item item;
3916 res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, nullptr);
3917 TEST(res == VK_SUCCESS);
3918 items.push_back(item);
3919 }
3920 }
3921
3922 // 3. Allocation of one more (even small) buffer should fail.
3923 {
3924 VmaAllocationCreateInfo allocCreateInfo = {};
3925 allocCreateInfo.pool = hPool;
3926
3927 bufCreateInfo.size = 128;
3928
3929 VkBuffer hBuf;
3930 VmaAllocation hAlloc;
3931 res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &hBuf, &hAlloc, nullptr);
3932 TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
3933 }
3934
3935 // Destroy everything.
3936 for(size_t i = items.size(); i--; )
3937 {
3938 vmaDestroyBuffer(hAllocator, items[i].hBuf, items[i].hAlloc);
3939 }
3940
3941 vmaDestroyPool(hAllocator, hPool);
3942
3943 vmaDestroyAllocator(hAllocator);
3944 }
3945
3946 #ifndef VMA_DEBUG_MARGIN
3947 #define VMA_DEBUG_MARGIN (0)
3948 #endif
3949
TestDebugMargin()3950 static void TestDebugMargin()
3951 {
3952 if(VMA_DEBUG_MARGIN == 0)
3953 {
3954 return;
3955 }
3956
3957 wprintf(L"Test VMA_DEBUG_MARGIN = %u\n", (uint32_t)VMA_DEBUG_MARGIN);
3958
3959 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
3960 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
3961 bufInfo.size = 256; // Doesn't matter
3962
3963 VmaAllocationCreateInfo allocCreateInfo = {};
3964 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
3965 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
3966
3967 VmaPoolCreateInfo poolCreateInfo = {};
3968 TEST(vmaFindMemoryTypeIndexForBufferInfo(
3969 g_hAllocator, &bufInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex) == VK_SUCCESS);
3970
3971 for(size_t algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex)
3972 {
3973 switch(algorithmIndex)
3974 {
3975 case 0: poolCreateInfo.flags = 0; break;
3976 case 1: poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT; break;
3977 default: assert(0);
3978 }
3979 VmaPool pool = VK_NULL_HANDLE;
3980 TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS && pool);
3981
3982 allocCreateInfo.pool = pool;
3983
3984 // Create few buffers of different size.
3985 const size_t BUF_COUNT = 10;
3986 BufferInfo buffers[BUF_COUNT];
3987 VmaAllocationInfo allocInfo[BUF_COUNT];
3988 for(size_t allocIndex = 0; allocIndex < 10; ++allocIndex)
3989 {
3990 const bool isLast = allocIndex == BUF_COUNT - 1;
3991 bufInfo.size = (VkDeviceSize)(allocIndex + 1) * 256;
3992 // Last one will be mapped.
3993 allocCreateInfo.flags = isLast ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0;
3994
3995 VkResult res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buffers[allocIndex].Buffer, &buffers[allocIndex].Allocation, &allocInfo[allocIndex]);
3996 TEST(res == VK_SUCCESS);
3997
3998 if(isLast)
3999 {
4000 // Fill with data.
4001 TEST(allocInfo[allocIndex].pMappedData != nullptr);
4002 // Uncomment this "+ 1" to overwrite past end of allocation and check corruption detection.
4003 memset(allocInfo[allocIndex].pMappedData, 0xFF, bufInfo.size /* + 1 */);
4004 }
4005 }
4006
4007 // Check if their offsets preserve margin between them.
4008 std::sort(allocInfo, allocInfo + BUF_COUNT, [](const VmaAllocationInfo& lhs, const VmaAllocationInfo& rhs) -> bool
4009 {
4010 if(lhs.deviceMemory != rhs.deviceMemory)
4011 {
4012 return lhs.deviceMemory < rhs.deviceMemory;
4013 }
4014 return lhs.offset < rhs.offset;
4015 });
4016 for(size_t i = 1; i < BUF_COUNT; ++i)
4017 {
4018 if(allocInfo[i].deviceMemory == allocInfo[i - 1].deviceMemory)
4019 {
4020 TEST(allocInfo[i].offset >=
4021 allocInfo[i - 1].offset + allocInfo[i - 1].size + VMA_DEBUG_MARGIN);
4022 }
4023 }
4024
4025 VkResult res = vmaCheckCorruption(g_hAllocator, UINT32_MAX);
4026 TEST(res == VK_SUCCESS || res == VK_ERROR_FEATURE_NOT_PRESENT);
4027
4028 // JSON dump
4029 char* json = nullptr;
4030 vmaBuildStatsString(g_hAllocator, &json, VK_TRUE);
4031 int I = 1; // Put breakpoint here to manually inspect json in a debugger.
4032 vmaFreeStatsString(g_hAllocator, json);
4033
4034 // Destroy all buffers.
4035 for(size_t i = BUF_COUNT; i--; )
4036 {
4037 vmaDestroyBuffer(g_hAllocator, buffers[i].Buffer, buffers[i].Allocation);
4038 }
4039
4040 vmaDestroyPool(g_hAllocator, pool);
4041 }
4042 }
4043
TestDebugMarginNotInVirtualAllocator()4044 static void TestDebugMarginNotInVirtualAllocator()
4045 {
4046 wprintf(L"Test VMA_DEBUG_MARGIN not applied to virtual allocator\n");
4047
4048 constexpr size_t ALLOCATION_COUNT = 10;
4049 for(size_t algorithm = 0; algorithm < 2; ++algorithm)
4050 {
4051 VmaVirtualBlockCreateInfo blockCreateInfo = {};
4052 blockCreateInfo.size = ALLOCATION_COUNT * MEGABYTE;
4053 blockCreateInfo.flags = (algorithm == 1 ? VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT : 0);
4054
4055 VmaVirtualBlock block = VK_NULL_HANDLE;
4056 TEST(vmaCreateVirtualBlock(&blockCreateInfo, &block) == VK_SUCCESS);
4057
4058 // Fill the entire block
4059 VmaVirtualAllocation allocs[ALLOCATION_COUNT];
4060 for(size_t i = 0; i < ALLOCATION_COUNT; ++i)
4061 {
4062 VmaVirtualAllocationCreateInfo allocCreateInfo = {};
4063 allocCreateInfo.size = 1 * MEGABYTE;
4064 TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocs[i], nullptr) == VK_SUCCESS);
4065 }
4066
4067 vmaClearVirtualBlock(block);
4068 vmaDestroyVirtualBlock(block);
4069 }
4070 }
4071
TestLinearAllocator()4072 static void TestLinearAllocator()
4073 {
4074 wprintf(L"Test linear allocator\n");
4075
4076 RandomNumberGenerator rand{645332};
4077
4078 VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
4079 sampleBufCreateInfo.size = 1024; // Whatever.
4080 sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
4081
4082 VmaAllocationCreateInfo sampleAllocCreateInfo = {};
4083 sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
4084
4085 VmaPoolCreateInfo poolCreateInfo = {};
4086 VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
4087 TEST(res == VK_SUCCESS);
4088
4089 poolCreateInfo.blockSize = 1024 * 300;
4090 poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT;
4091 poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
4092
4093 VmaPool pool = nullptr;
4094 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
4095 TEST(res == VK_SUCCESS);
4096
4097 VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo;
4098
4099 VmaAllocationCreateInfo allocCreateInfo = {};
4100 allocCreateInfo.pool = pool;
4101
4102 constexpr size_t maxBufCount = 100;
4103 std::vector<BufferInfo> bufInfo;
4104
4105 constexpr VkDeviceSize bufSizeMin = 64;
4106 constexpr VkDeviceSize bufSizeMax = 1024;
4107 VmaAllocationInfo allocInfo;
4108 VkDeviceSize prevOffset = 0;
4109
4110 // Test one-time free.
4111 for(size_t i = 0; i < 2; ++i)
4112 {
4113 // Allocate number of buffers of varying size that surely fit into this block.
4114 VkDeviceSize bufSumSize = 0;
4115 for(size_t i = 0; i < maxBufCount; ++i)
4116 {
4117 bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
4118 BufferInfo newBufInfo;
4119 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4120 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4121 TEST(res == VK_SUCCESS);
4122 TEST(i == 0 || allocInfo.offset > prevOffset);
4123 bufInfo.push_back(newBufInfo);
4124 prevOffset = allocInfo.offset;
4125 TEST(allocInfo.size >= bufCreateInfo.size);
4126 bufSumSize += allocInfo.size;
4127 }
4128
4129 // Validate pool stats.
4130 VmaDetailedStatistics stats;
4131 vmaCalculatePoolStatistics(g_hAllocator, pool, &stats);
4132 TEST(stats.statistics.blockBytes == poolCreateInfo.blockSize);
4133 TEST(stats.statistics.blockBytes - stats.statistics.allocationBytes == poolCreateInfo.blockSize - bufSumSize);
4134 TEST(stats.statistics.allocationCount == bufInfo.size());
4135
4136 // Destroy the buffers in random order.
4137 while(!bufInfo.empty())
4138 {
4139 const size_t indexToDestroy = rand.Generate() % bufInfo.size();
4140 const BufferInfo& currBufInfo = bufInfo[indexToDestroy];
4141 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4142 bufInfo.erase(bufInfo.begin() + indexToDestroy);
4143 }
4144 }
4145
4146 // Test stack.
4147 {
4148 // Allocate number of buffers of varying size that surely fit into this block.
4149 for(size_t i = 0; i < maxBufCount; ++i)
4150 {
4151 bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
4152 BufferInfo newBufInfo;
4153 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4154 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4155 TEST(res == VK_SUCCESS);
4156 TEST(i == 0 || allocInfo.offset > prevOffset);
4157 bufInfo.push_back(newBufInfo);
4158 prevOffset = allocInfo.offset;
4159 }
4160
4161 // Destroy few buffers from top of the stack.
4162 for(size_t i = 0; i < maxBufCount / 5; ++i)
4163 {
4164 const BufferInfo& currBufInfo = bufInfo.back();
4165 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4166 bufInfo.pop_back();
4167 }
4168
4169 // Create some more
4170 for(size_t i = 0; i < maxBufCount / 5; ++i)
4171 {
4172 bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
4173 BufferInfo newBufInfo;
4174 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4175 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4176 TEST(res == VK_SUCCESS);
4177 TEST(i == 0 || allocInfo.offset > prevOffset);
4178 bufInfo.push_back(newBufInfo);
4179 prevOffset = allocInfo.offset;
4180 }
4181
4182 // Destroy the buffers in reverse order.
4183 while(!bufInfo.empty())
4184 {
4185 const BufferInfo& currBufInfo = bufInfo.back();
4186 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4187 bufInfo.pop_back();
4188 }
4189 }
4190
4191 // Test ring buffer.
4192 {
4193 // Allocate number of buffers that surely fit into this block.
4194 bufCreateInfo.size = bufSizeMax;
4195 for(size_t i = 0; i < maxBufCount; ++i)
4196 {
4197 BufferInfo newBufInfo;
4198 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4199 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4200 TEST(res == VK_SUCCESS);
4201 TEST(i == 0 || allocInfo.offset > prevOffset);
4202 bufInfo.push_back(newBufInfo);
4203 prevOffset = allocInfo.offset;
4204 }
4205
4206 // Free and allocate new buffers so many times that we make sure we wrap-around at least once.
4207 const size_t buffersPerIter = maxBufCount / 10 - 1;
4208 const size_t iterCount = poolCreateInfo.blockSize / bufCreateInfo.size / buffersPerIter * 2;
4209 for(size_t iter = 0; iter < iterCount; ++iter)
4210 {
4211 for(size_t bufPerIter = 0; bufPerIter < buffersPerIter; ++bufPerIter)
4212 {
4213 const BufferInfo& currBufInfo = bufInfo.front();
4214 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4215 bufInfo.erase(bufInfo.begin());
4216 }
4217 for(size_t bufPerIter = 0; bufPerIter < buffersPerIter; ++bufPerIter)
4218 {
4219 BufferInfo newBufInfo;
4220 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4221 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4222 TEST(res == VK_SUCCESS);
4223 bufInfo.push_back(newBufInfo);
4224 }
4225 }
4226
4227 // Allocate buffers until we reach out-of-memory.
4228 uint32_t debugIndex = 0;
4229 while(res == VK_SUCCESS)
4230 {
4231 BufferInfo newBufInfo;
4232 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4233 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4234 if(res == VK_SUCCESS)
4235 {
4236 bufInfo.push_back(newBufInfo);
4237 }
4238 else
4239 {
4240 TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
4241 }
4242 ++debugIndex;
4243 }
4244
4245 // Destroy the buffers in random order.
4246 while(!bufInfo.empty())
4247 {
4248 const size_t indexToDestroy = rand.Generate() % bufInfo.size();
4249 const BufferInfo& currBufInfo = bufInfo[indexToDestroy];
4250 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4251 bufInfo.erase(bufInfo.begin() + indexToDestroy);
4252 }
4253 }
4254
4255 // Test double stack.
4256 {
4257 // Allocate number of buffers of varying size that surely fit into this block, alternate from bottom/top.
4258 VkDeviceSize prevOffsetLower = 0;
4259 VkDeviceSize prevOffsetUpper = poolCreateInfo.blockSize;
4260 for(size_t i = 0; i < maxBufCount; ++i)
4261 {
4262 const bool upperAddress = (i % 2) != 0;
4263 if(upperAddress)
4264 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
4265 else
4266 allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
4267 bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
4268 BufferInfo newBufInfo;
4269 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4270 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4271 TEST(res == VK_SUCCESS);
4272 if(upperAddress)
4273 {
4274 TEST(allocInfo.offset < prevOffsetUpper);
4275 prevOffsetUpper = allocInfo.offset;
4276 }
4277 else
4278 {
4279 TEST(allocInfo.offset >= prevOffsetLower);
4280 prevOffsetLower = allocInfo.offset;
4281 }
4282 TEST(prevOffsetLower < prevOffsetUpper);
4283 bufInfo.push_back(newBufInfo);
4284 }
4285
4286 // Destroy few buffers from top of the stack.
4287 for(size_t i = 0; i < maxBufCount / 5; ++i)
4288 {
4289 const BufferInfo& currBufInfo = bufInfo.back();
4290 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4291 bufInfo.pop_back();
4292 }
4293
4294 // Create some more
4295 for(size_t i = 0; i < maxBufCount / 5; ++i)
4296 {
4297 const bool upperAddress = (i % 2) != 0;
4298 if(upperAddress)
4299 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
4300 else
4301 allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
4302 bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
4303 BufferInfo newBufInfo;
4304 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4305 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4306 TEST(res == VK_SUCCESS);
4307 bufInfo.push_back(newBufInfo);
4308 }
4309
4310 // Destroy the buffers in reverse order.
4311 while(!bufInfo.empty())
4312 {
4313 const BufferInfo& currBufInfo = bufInfo.back();
4314 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4315 bufInfo.pop_back();
4316 }
4317
4318 // Create buffers on both sides until we reach out of memory.
4319 prevOffsetLower = 0;
4320 prevOffsetUpper = poolCreateInfo.blockSize;
4321 res = VK_SUCCESS;
4322 for(size_t i = 0; res == VK_SUCCESS; ++i)
4323 {
4324 const bool upperAddress = (i % 2) != 0;
4325 if(upperAddress)
4326 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
4327 else
4328 allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
4329 bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
4330 BufferInfo newBufInfo;
4331 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4332 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4333 if(res == VK_SUCCESS)
4334 {
4335 if(upperAddress)
4336 {
4337 TEST(allocInfo.offset < prevOffsetUpper);
4338 prevOffsetUpper = allocInfo.offset;
4339 }
4340 else
4341 {
4342 TEST(allocInfo.offset >= prevOffsetLower);
4343 prevOffsetLower = allocInfo.offset;
4344 }
4345 TEST(prevOffsetLower < prevOffsetUpper);
4346 bufInfo.push_back(newBufInfo);
4347 }
4348 }
4349
4350 // Destroy the buffers in random order.
4351 while(!bufInfo.empty())
4352 {
4353 const size_t indexToDestroy = rand.Generate() % bufInfo.size();
4354 const BufferInfo& currBufInfo = bufInfo[indexToDestroy];
4355 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4356 bufInfo.erase(bufInfo.begin() + indexToDestroy);
4357 }
4358
4359 // Create buffers on upper side only, constant size, until we reach out of memory.
4360 prevOffsetUpper = poolCreateInfo.blockSize;
4361 res = VK_SUCCESS;
4362 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
4363 bufCreateInfo.size = bufSizeMax;
4364 for(size_t i = 0; res == VK_SUCCESS; ++i)
4365 {
4366 BufferInfo newBufInfo;
4367 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4368 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4369 if(res == VK_SUCCESS)
4370 {
4371 TEST(allocInfo.offset < prevOffsetUpper);
4372 prevOffsetUpper = allocInfo.offset;
4373 bufInfo.push_back(newBufInfo);
4374 }
4375 }
4376
4377 // Destroy the buffers in reverse order.
4378 while(!bufInfo.empty())
4379 {
4380 const BufferInfo& currBufInfo = bufInfo.back();
4381 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4382 bufInfo.pop_back();
4383 }
4384 }
4385
4386 vmaDestroyPool(g_hAllocator, pool);
4387 }
4388
TestLinearAllocatorMultiBlock()4389 static void TestLinearAllocatorMultiBlock()
4390 {
4391 wprintf(L"Test linear allocator multi block\n");
4392
4393 RandomNumberGenerator rand{345673};
4394
4395 VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
4396 sampleBufCreateInfo.size = 1024 * 1024;
4397 sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
4398
4399 VmaAllocationCreateInfo sampleAllocCreateInfo = {};
4400 sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
4401
4402 VmaPoolCreateInfo poolCreateInfo = {};
4403 poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT;
4404 VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
4405 TEST(res == VK_SUCCESS);
4406
4407 VmaPool pool = nullptr;
4408 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
4409 TEST(res == VK_SUCCESS);
4410
4411 VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo;
4412
4413 VmaAllocationCreateInfo allocCreateInfo = {};
4414 allocCreateInfo.pool = pool;
4415
4416 std::vector<BufferInfo> bufInfo;
4417 VmaAllocationInfo allocInfo;
4418
4419 // Test one-time free.
4420 {
4421 // Allocate buffers until we move to a second block.
4422 VkDeviceMemory lastMem = VK_NULL_HANDLE;
4423 for(uint32_t i = 0; ; ++i)
4424 {
4425 BufferInfo newBufInfo;
4426 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4427 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4428 TEST(res == VK_SUCCESS);
4429 bufInfo.push_back(newBufInfo);
4430 if(lastMem && allocInfo.deviceMemory != lastMem)
4431 {
4432 break;
4433 }
4434 lastMem = allocInfo.deviceMemory;
4435 }
4436
4437 TEST(bufInfo.size() > 2);
4438
4439 // Make sure that pool has now two blocks.
4440 VmaDetailedStatistics poolStats = {};
4441 vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
4442 TEST(poolStats.statistics.blockCount == 2);
4443
4444 // Destroy all the buffers in random order.
4445 while(!bufInfo.empty())
4446 {
4447 const size_t indexToDestroy = rand.Generate() % bufInfo.size();
4448 const BufferInfo& currBufInfo = bufInfo[indexToDestroy];
4449 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4450 bufInfo.erase(bufInfo.begin() + indexToDestroy);
4451 }
4452
4453 // Make sure that pool has now at most one block.
4454 vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
4455 TEST(poolStats.statistics.blockCount <= 1);
4456 }
4457
4458 // Test stack.
4459 {
4460 // Allocate buffers until we move to a second block.
4461 VkDeviceMemory lastMem = VK_NULL_HANDLE;
4462 for(uint32_t i = 0; ; ++i)
4463 {
4464 BufferInfo newBufInfo;
4465 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4466 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4467 TEST(res == VK_SUCCESS);
4468 bufInfo.push_back(newBufInfo);
4469 if(lastMem && allocInfo.deviceMemory != lastMem)
4470 {
4471 break;
4472 }
4473 lastMem = allocInfo.deviceMemory;
4474 }
4475
4476 TEST(bufInfo.size() > 2);
4477
4478 // Add few more buffers.
4479 for(uint32_t i = 0; i < 5; ++i)
4480 {
4481 BufferInfo newBufInfo;
4482 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4483 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4484 TEST(res == VK_SUCCESS);
4485 bufInfo.push_back(newBufInfo);
4486 }
4487
4488 // Make sure that pool has now two blocks.
4489 VmaDetailedStatistics poolStats = {};
4490 vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
4491 TEST(poolStats.statistics.blockCount == 2);
4492
4493 // Delete half of buffers, LIFO.
4494 for(size_t i = 0, countToDelete = bufInfo.size() / 2; i < countToDelete; ++i)
4495 {
4496 const BufferInfo& currBufInfo = bufInfo.back();
4497 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4498 bufInfo.pop_back();
4499 }
4500
4501 // Add one more buffer.
4502 BufferInfo newBufInfo;
4503 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4504 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4505 TEST(res == VK_SUCCESS);
4506 bufInfo.push_back(newBufInfo);
4507
4508 // Make sure that pool has now one block.
4509 vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
4510 TEST(poolStats.statistics.blockCount == 1);
4511
4512 // Delete all the remaining buffers, LIFO.
4513 while(!bufInfo.empty())
4514 {
4515 const BufferInfo& currBufInfo = bufInfo.back();
4516 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4517 bufInfo.pop_back();
4518 }
4519 }
4520
4521 vmaDestroyPool(g_hAllocator, pool);
4522 }
4523
TestAllocationAlgorithmsCorrectness()4524 static void TestAllocationAlgorithmsCorrectness()
4525 {
4526 wprintf(L"Test allocation algorithm correctness\n");
4527
4528 constexpr uint32_t LEVEL_COUNT = 12;
4529 RandomNumberGenerator rand{2342435};
4530
4531 for(uint32_t isVirtual = 0; isVirtual < 3; ++isVirtual)
4532 {
4533 // isVirtual == 0: Use VmaPool, unit is 64 KB.
4534 // isVirtual == 1: Use VmaVirtualBlock, unit is 64 KB.
4535 // isVirtual == 2: Use VmaVirtualBlock, unit is 1 B.
4536 const VkDeviceSize sizeUnit = isVirtual == 2 ? 1 : 0x10000;
4537 const VkDeviceSize blockSize = (1llu << (LEVEL_COUNT - 1)) * sizeUnit;
4538
4539 for(uint32_t algorithmIndex = 0; algorithmIndex < 1; ++algorithmIndex)
4540 {
4541 VmaPool pool = VK_NULL_HANDLE;
4542 VmaVirtualBlock virtualBlock = VK_NULL_HANDLE;
4543
4544 uint32_t algorithm;
4545 switch (algorithmIndex)
4546 {
4547 case 0:
4548 algorithm = 0;
4549 break;
4550 default:
4551 break;
4552 }
4553
4554 if(isVirtual)
4555 {
4556 VmaVirtualBlockCreateInfo blockCreateInfo = {};
4557 blockCreateInfo.pAllocationCallbacks = g_Allocs;
4558 blockCreateInfo.flags = algorithm;
4559 blockCreateInfo.size = blockSize;
4560 TEST(vmaCreateVirtualBlock(&blockCreateInfo, &virtualBlock) == VK_SUCCESS);
4561 }
4562 else
4563 {
4564 VmaPoolCreateInfo poolCreateInfo = {};
4565 poolCreateInfo.blockSize = blockSize;
4566 poolCreateInfo.flags = algorithm;
4567 poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
4568
4569 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
4570 bufCreateInfo.size = 0x10000; // Doesn't matter.
4571 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
4572 VmaAllocationCreateInfo allocCreateInfo = {};
4573 TEST(vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex) == VK_SUCCESS);
4574
4575 TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
4576 }
4577
4578 for(uint32_t strategyIndex = 0; strategyIndex < 3; ++strategyIndex)
4579 {
4580 struct AllocData
4581 {
4582 VmaAllocation alloc = VK_NULL_HANDLE;
4583 VkBuffer buf = VK_NULL_HANDLE;
4584 VmaVirtualAllocation virtualAlloc = VK_NULL_HANDLE;
4585 };
4586 std::vector<AllocData> allocationsPerLevel[LEVEL_COUNT];
4587
4588 auto createAllocation = [&](uint32_t level) -> void
4589 {
4590 AllocData allocData;
4591 const VkDeviceSize allocSize = (1llu << level) * sizeUnit;
4592 if(isVirtual)
4593 {
4594 VmaVirtualAllocationCreateInfo allocCreateInfo = {};
4595 allocCreateInfo.size = allocSize;
4596 switch(strategyIndex)
4597 {
4598 case 1: allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; break;
4599 case 2: allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT; break;
4600 }
4601 TEST(vmaVirtualAllocate(virtualBlock, &allocCreateInfo, &allocData.virtualAlloc, nullptr) == VK_SUCCESS);
4602 }
4603 else
4604 {
4605 VmaAllocationCreateInfo allocCreateInfo = {};
4606 allocCreateInfo.pool = pool;
4607 switch(strategyIndex)
4608 {
4609 case 1: allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; break;
4610 case 2: allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT; break;
4611 }
4612 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
4613 bufCreateInfo.size = allocSize;
4614 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
4615 TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &allocData.buf, &allocData.alloc, nullptr) == VK_SUCCESS);
4616 }
4617 allocationsPerLevel[level].push_back(allocData);
4618 };
4619
4620 auto destroyAllocation = [&](uint32_t level, size_t index) -> void
4621 {
4622 const AllocData& allocData = allocationsPerLevel[level][index];
4623 if(isVirtual)
4624 vmaVirtualFree(virtualBlock, allocData.virtualAlloc);
4625 else
4626 vmaDestroyBuffer(g_hAllocator, allocData.buf, allocData.alloc);
4627 allocationsPerLevel[level].erase(allocationsPerLevel[level].begin() + index);
4628 };
4629
4630 // Fill entire block with one big allocation.
4631 createAllocation(LEVEL_COUNT - 1);
4632
4633 // For each level, remove one allocation and refill it with 2 allocations at lower level.
4634 for(uint32_t level = LEVEL_COUNT; level-- > 1; )
4635 {
4636 size_t indexToDestroy = rand.Generate() % allocationsPerLevel[level].size();
4637 destroyAllocation(level, indexToDestroy);
4638 createAllocation(level - 1);
4639 createAllocation(level - 1);
4640 }
4641
4642 // Test statistics.
4643 {
4644 uint32_t actualAllocCount = 0, statAllocCount = 0;
4645 VkDeviceSize actualAllocSize = 0, statAllocSize = 0;
4646 // Calculate actual statistics.
4647 for(uint32_t level = 0; level < LEVEL_COUNT; ++level)
4648 {
4649 for(size_t index = allocationsPerLevel[level].size(); index--; )
4650 {
4651 if(isVirtual)
4652 {
4653 VmaVirtualAllocationInfo allocInfo = {};
4654 vmaGetVirtualAllocationInfo(virtualBlock, allocationsPerLevel[level][index].virtualAlloc, &allocInfo);
4655 actualAllocSize += allocInfo.size;
4656 }
4657 else
4658 {
4659 VmaAllocationInfo allocInfo = {};
4660 vmaGetAllocationInfo(g_hAllocator, allocationsPerLevel[level][index].alloc, &allocInfo);
4661 actualAllocSize += allocInfo.size;
4662 }
4663 }
4664 actualAllocCount += (uint32_t)allocationsPerLevel[level].size();
4665 }
4666 // Fetch reported statistics.
4667 if(isVirtual)
4668 {
4669 VmaDetailedStatistics info = {};
4670 vmaCalculateVirtualBlockStatistics(virtualBlock, &info);
4671 statAllocCount = info.statistics.allocationCount;
4672 statAllocSize = info.statistics.allocationBytes;
4673 TEST(info.statistics.blockCount == 1);
4674 TEST(info.statistics.blockBytes == blockSize);
4675 }
4676 else
4677 {
4678 VmaDetailedStatistics stats = {};
4679 vmaCalculatePoolStatistics(g_hAllocator, pool, &stats);
4680 statAllocCount = (uint32_t)stats.statistics.allocationCount;
4681 statAllocSize = stats.statistics.allocationBytes;
4682 TEST(stats.statistics.blockCount == 1);
4683 TEST(stats.statistics.blockBytes == blockSize);
4684 }
4685 // Compare them.
4686 TEST(actualAllocCount == statAllocCount);
4687 TEST(actualAllocSize == statAllocSize);
4688 }
4689
4690 // Test JSON dump - for manual inspection.
4691 {
4692 char* json = nullptr;
4693 if(isVirtual)
4694 {
4695 vmaBuildVirtualBlockStatsString(virtualBlock, &json, VK_TRUE);
4696 int I = 1; // Put breakpoint here to inspect `json`.
4697 vmaFreeVirtualBlockStatsString(virtualBlock, json);
4698 }
4699 else
4700 {
4701 vmaBuildStatsString(g_hAllocator, &json, VK_TRUE);
4702 int I = 1; // Put breakpoint here to inspect `json`.
4703 vmaFreeStatsString(g_hAllocator, json);
4704 }
4705 }
4706
4707 // Free all remaining allocations
4708 for(uint32_t level = 0; level < LEVEL_COUNT; ++level)
4709 for(size_t index = allocationsPerLevel[level].size(); index--; )
4710 destroyAllocation(level, index);
4711 }
4712
4713 vmaDestroyVirtualBlock(virtualBlock);
4714 vmaDestroyPool(g_hAllocator, pool);
4715 }
4716 }
4717 }
4718
ManuallyTestLinearAllocator()4719 static void ManuallyTestLinearAllocator()
4720 {
4721 VmaTotalStatistics origStats;
4722 vmaCalculateStatistics(g_hAllocator, &origStats);
4723
4724 wprintf(L"Manually test linear allocator\n");
4725
4726 RandomNumberGenerator rand{645332};
4727
4728 VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
4729 sampleBufCreateInfo.size = 1024; // Whatever.
4730 sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
4731
4732 VmaAllocationCreateInfo sampleAllocCreateInfo = {};
4733 sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
4734
4735 VmaPoolCreateInfo poolCreateInfo = {};
4736 VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
4737 TEST(res == VK_SUCCESS);
4738
4739 poolCreateInfo.blockSize = 10 * 1024;
4740 poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT;
4741 poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
4742
4743 VmaPool pool = nullptr;
4744 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
4745 TEST(res == VK_SUCCESS);
4746
4747 VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo;
4748
4749 VmaAllocationCreateInfo allocCreateInfo = {};
4750 allocCreateInfo.pool = pool;
4751
4752 std::vector<BufferInfo> bufInfo;
4753 VmaAllocationInfo allocInfo;
4754 BufferInfo newBufInfo;
4755
4756 // Test double stack.
4757 {
4758 /*
4759 Lower: Buffer 32 B, Buffer 1024 B, Buffer 32 B
4760 Upper: Buffer 16 B, Buffer 1024 B, Buffer 128 B
4761
4762 Totally:
4763 1 block allocated
4764 10240 Vulkan bytes
4765 6 new allocations
4766 2256 bytes in allocations
4767 */
4768
4769 bufCreateInfo.size = 32;
4770 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4771 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4772 TEST(res == VK_SUCCESS);
4773 bufInfo.push_back(newBufInfo);
4774
4775 bufCreateInfo.size = 1024;
4776 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4777 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4778 TEST(res == VK_SUCCESS);
4779 bufInfo.push_back(newBufInfo);
4780
4781 bufCreateInfo.size = 32;
4782 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4783 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4784 TEST(res == VK_SUCCESS);
4785 bufInfo.push_back(newBufInfo);
4786
4787 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
4788
4789 bufCreateInfo.size = 128;
4790 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4791 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4792 TEST(res == VK_SUCCESS);
4793 bufInfo.push_back(newBufInfo);
4794
4795 bufCreateInfo.size = 1024;
4796 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4797 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4798 TEST(res == VK_SUCCESS);
4799 bufInfo.push_back(newBufInfo);
4800
4801 bufCreateInfo.size = 16;
4802 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
4803 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
4804 TEST(res == VK_SUCCESS);
4805 bufInfo.push_back(newBufInfo);
4806
4807 VmaTotalStatistics currStats;
4808 vmaCalculateStatistics(g_hAllocator, &currStats);
4809 VmaDetailedStatistics poolStats;
4810 vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
4811
4812 #if !defined(VMA_STATS_STRING_ENABLED) || VMA_STATS_STRING_ENABLED
4813 char* statsStr = nullptr;
4814 vmaBuildStatsString(g_hAllocator, &statsStr, VK_TRUE);
4815
4816 // PUT BREAKPOINT HERE TO CHECK.
4817 // Inspect: currStats versus origStats, poolStats, statsStr.
4818 int I = 0;
4819
4820 vmaFreeStatsString(g_hAllocator, statsStr);
4821 #endif
4822
4823 // Destroy the buffers in reverse order.
4824 while(!bufInfo.empty())
4825 {
4826 const BufferInfo& currBufInfo = bufInfo.back();
4827 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
4828 bufInfo.pop_back();
4829 }
4830 }
4831
4832 vmaDestroyPool(g_hAllocator, pool);
4833 }
4834
BenchmarkAlgorithmsCase(FILE * file,uint32_t algorithm,bool empty,VmaAllocationCreateFlags allocStrategy,FREE_ORDER freeOrder)4835 static void BenchmarkAlgorithmsCase(FILE* file,
4836 uint32_t algorithm,
4837 bool empty,
4838 VmaAllocationCreateFlags allocStrategy,
4839 FREE_ORDER freeOrder)
4840 {
4841 RandomNumberGenerator rand{16223};
4842
4843 const VkDeviceSize bufSizeMin = 32;
4844 const VkDeviceSize bufSizeMax = 1024;
4845 const size_t maxBufCapacity = 10000;
4846 const uint32_t iterationCount = 10;
4847
4848 VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
4849 sampleBufCreateInfo.size = bufSizeMax;
4850 sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
4851
4852 VmaAllocationCreateInfo sampleAllocCreateInfo = {};
4853 sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
4854
4855 VmaPoolCreateInfo poolCreateInfo = {};
4856 VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
4857 TEST(res == VK_SUCCESS);
4858
4859 poolCreateInfo.blockSize = bufSizeMax * maxBufCapacity;
4860 poolCreateInfo.flags = VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT;//TODO remove this
4861 poolCreateInfo.flags |= algorithm;
4862 poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
4863
4864 VmaPool pool = nullptr;
4865 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
4866 TEST(res == VK_SUCCESS);
4867
4868 // Buffer created just to get memory requirements. Never bound to any memory.
4869 VkBuffer dummyBuffer = VK_NULL_HANDLE;
4870 res = vkCreateBuffer(g_hDevice, &sampleBufCreateInfo, g_Allocs, &dummyBuffer);
4871 TEST(res == VK_SUCCESS && dummyBuffer);
4872
4873 VkMemoryRequirements memReq = {};
4874 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
4875
4876 vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs);
4877
4878 VmaAllocationCreateInfo allocCreateInfo = {};
4879 allocCreateInfo.pool = pool;
4880 allocCreateInfo.flags = allocStrategy;
4881
4882 VmaAllocation alloc;
4883 std::vector<VmaAllocation> baseAllocations;
4884
4885 if(!empty)
4886 {
4887 // Make allocations up to 1/3 of pool size.
4888 VkDeviceSize totalSize = 0;
4889 while(totalSize < poolCreateInfo.blockSize / 3)
4890 {
4891 // This test intentionally allows sizes that are aligned to 4 or 16 bytes.
4892 // This is theoretically allowed and already uncovered one bug.
4893 memReq.size = bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin);
4894 res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr);
4895 TEST(res == VK_SUCCESS);
4896 baseAllocations.push_back(alloc);
4897 totalSize += memReq.size;
4898 }
4899
4900 // Delete half of them, choose randomly.
4901 size_t allocsToDelete = baseAllocations.size() / 2;
4902 for(size_t i = 0; i < allocsToDelete; ++i)
4903 {
4904 const size_t index = (size_t)rand.Generate() % baseAllocations.size();
4905 vmaFreeMemory(g_hAllocator, baseAllocations[index]);
4906 baseAllocations.erase(baseAllocations.begin() + index);
4907 }
4908 }
4909
4910 // BENCHMARK
4911 const size_t allocCount = maxBufCapacity / 3;
4912 std::vector<VmaAllocation> testAllocations;
4913 testAllocations.reserve(allocCount);
4914 duration allocTotalDuration = duration::zero();
4915 duration freeTotalDuration = duration::zero();
4916 for(uint32_t iterationIndex = 0; iterationIndex < iterationCount; ++iterationIndex)
4917 {
4918 // Allocations
4919 time_point allocTimeBeg = std::chrono::high_resolution_clock::now();
4920 for(size_t i = 0; i < allocCount; ++i)
4921 {
4922 memReq.size = bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin);
4923 res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr);
4924 TEST(res == VK_SUCCESS);
4925 testAllocations.push_back(alloc);
4926 }
4927 allocTotalDuration += std::chrono::high_resolution_clock::now() - allocTimeBeg;
4928
4929 // Deallocations
4930 switch(freeOrder)
4931 {
4932 case FREE_ORDER::FORWARD:
4933 // Leave testAllocations unchanged.
4934 break;
4935 case FREE_ORDER::BACKWARD:
4936 std::reverse(testAllocations.begin(), testAllocations.end());
4937 break;
4938 case FREE_ORDER::RANDOM:
4939 std::shuffle(testAllocations.begin(), testAllocations.end(), MyUniformRandomNumberGenerator(rand));
4940 break;
4941 default: assert(0);
4942 }
4943
4944 time_point freeTimeBeg = std::chrono::high_resolution_clock::now();
4945 for(size_t i = 0; i < allocCount; ++i)
4946 vmaFreeMemory(g_hAllocator, testAllocations[i]);
4947 freeTotalDuration += std::chrono::high_resolution_clock::now() - freeTimeBeg;
4948
4949 testAllocations.clear();
4950 }
4951
4952 // Delete baseAllocations
4953 while(!baseAllocations.empty())
4954 {
4955 vmaFreeMemory(g_hAllocator, baseAllocations.back());
4956 baseAllocations.pop_back();
4957 }
4958
4959 vmaDestroyPool(g_hAllocator, pool);
4960
4961 const float allocTotalSeconds = ToFloatSeconds(allocTotalDuration);
4962 const float freeTotalSeconds = ToFloatSeconds(freeTotalDuration);
4963
4964 printf(" Algorithm=%s %s Allocation=%s FreeOrder=%s: allocations %g s, free %g s\n",
4965 AlgorithmToStr(algorithm),
4966 empty ? "Empty" : "Not empty",
4967 GetAllocationStrategyName(allocStrategy),
4968 FREE_ORDER_NAMES[(size_t)freeOrder],
4969 allocTotalSeconds,
4970 freeTotalSeconds);
4971
4972 if(file)
4973 {
4974 std::string currTime;
4975 CurrentTimeToStr(currTime);
4976
4977 fprintf(file, "%s,%s,%s,%u,%s,%s,%g,%g\n",
4978 CODE_DESCRIPTION, currTime.c_str(),
4979 AlgorithmToStr(algorithm),
4980 empty ? 1 : 0,
4981 GetAllocationStrategyName(allocStrategy),
4982 FREE_ORDER_NAMES[(uint32_t)freeOrder],
4983 allocTotalSeconds,
4984 freeTotalSeconds);
4985 }
4986 }
4987
TestBufferDeviceAddress()4988 static void TestBufferDeviceAddress()
4989 {
4990 wprintf(L"Test buffer device address\n");
4991
4992 assert(VK_KHR_buffer_device_address_enabled);
4993
4994 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
4995 bufCreateInfo.size = 0x10000;
4996 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
4997 VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; // !!!
4998
4999 VmaAllocationCreateInfo allocCreateInfo = {};
5000 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
5001
5002 for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
5003 {
5004 // 1st is placed, 2nd is dedicated.
5005 if(testIndex == 1)
5006 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
5007
5008 BufferInfo bufInfo = {};
5009 VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
5010 &bufInfo.Buffer, &bufInfo.Allocation, nullptr);
5011 TEST(res == VK_SUCCESS);
5012
5013 VkBufferDeviceAddressInfoEXT bufferDeviceAddressInfo = { VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_EXT };
5014 bufferDeviceAddressInfo.buffer = bufInfo.Buffer;
5015 TEST(g_vkGetBufferDeviceAddressKHR != nullptr);
5016 VkDeviceAddress addr = g_vkGetBufferDeviceAddressKHR(g_hDevice, &bufferDeviceAddressInfo);
5017 TEST(addr != 0);
5018
5019 vmaDestroyBuffer(g_hAllocator, bufInfo.Buffer, bufInfo.Allocation);
5020 }
5021 }
5022
TestMemoryPriority()5023 static void TestMemoryPriority()
5024 {
5025 wprintf(L"Test memory priority\n");
5026
5027 assert(VK_EXT_memory_priority_enabled);
5028
5029 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
5030 bufCreateInfo.size = 0x10000;
5031 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
5032
5033 VmaAllocationCreateInfo allocCreateInfo = {};
5034 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
5035 allocCreateInfo.priority = 1.f;
5036
5037 for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
5038 {
5039 // 1st is placed, 2nd is dedicated.
5040 if(testIndex == 1)
5041 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
5042
5043 BufferInfo bufInfo = {};
5044 VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
5045 &bufInfo.Buffer, &bufInfo.Allocation, nullptr);
5046 TEST(res == VK_SUCCESS);
5047
5048 // There is nothing we can do to validate the priority.
5049
5050 vmaDestroyBuffer(g_hAllocator, bufInfo.Buffer, bufInfo.Allocation);
5051 }
5052 }
5053
BenchmarkAlgorithms(FILE * file)5054 static void BenchmarkAlgorithms(FILE* file)
5055 {
5056 wprintf(L"Benchmark algorithms\n");
5057
5058 if(file)
5059 {
5060 fprintf(file,
5061 "Code,Time,"
5062 "Algorithm,Empty,Allocation strategy,Free order,"
5063 "Allocation time (s),Deallocation time (s)\n");
5064 }
5065
5066 uint32_t freeOrderCount = 1;
5067 if(ConfigType >= CONFIG_TYPE::CONFIG_TYPE_LARGE)
5068 freeOrderCount = 3;
5069 else if(ConfigType >= CONFIG_TYPE::CONFIG_TYPE_SMALL)
5070 freeOrderCount = 2;
5071
5072 const uint32_t emptyCount = ConfigType >= CONFIG_TYPE::CONFIG_TYPE_SMALL ? 2 : 1;
5073 const uint32_t allocStrategyCount = GetAllocationStrategyCount();
5074
5075 for(uint32_t freeOrderIndex = 0; freeOrderIndex < freeOrderCount; ++freeOrderIndex)
5076 {
5077 FREE_ORDER freeOrder = FREE_ORDER::COUNT;
5078 switch(freeOrderIndex)
5079 {
5080 case 0: freeOrder = FREE_ORDER::BACKWARD; break;
5081 case 1: freeOrder = FREE_ORDER::FORWARD; break;
5082 case 2: freeOrder = FREE_ORDER::RANDOM; break;
5083 default: assert(0);
5084 }
5085
5086 for(uint32_t emptyIndex = 0; emptyIndex < emptyCount; ++emptyIndex)
5087 {
5088 for(uint32_t algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex)
5089 {
5090 uint32_t algorithm = 0;
5091 switch(algorithmIndex)
5092 {
5093 case 0:
5094 break;
5095 case 1:
5096 algorithm = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT;
5097 break;
5098 default:
5099 assert(0);
5100 }
5101
5102 uint32_t currAllocStrategyCount = algorithm != 0 ? 1 : allocStrategyCount;
5103 for(uint32_t allocStrategyIndex = 0; allocStrategyIndex < currAllocStrategyCount; ++allocStrategyIndex)
5104 {
5105 VmaAllocatorCreateFlags strategy = 0;
5106 if(currAllocStrategyCount > 1)
5107 {
5108 switch(allocStrategyIndex)
5109 {
5110 case 0: strategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT; break;
5111 case 1: strategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; break;
5112 default: assert(0);
5113 }
5114 }
5115
5116 BenchmarkAlgorithmsCase(
5117 file,
5118 algorithm,
5119 (emptyIndex == 0), // empty
5120 strategy,
5121 freeOrder); // freeOrder
5122 }
5123 }
5124 }
5125 }
5126 }
5127
TestPool_SameSize()5128 static void TestPool_SameSize()
5129 {
5130 const VkDeviceSize BUF_SIZE = 1024 * 1024;
5131 const size_t BUF_COUNT = 100;
5132 VkResult res;
5133
5134 RandomNumberGenerator rand{123};
5135
5136 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
5137 bufferInfo.size = BUF_SIZE;
5138 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
5139
5140 uint32_t memoryTypeBits = UINT32_MAX;
5141 {
5142 VkBuffer dummyBuffer;
5143 res = vkCreateBuffer(g_hDevice, &bufferInfo, g_Allocs, &dummyBuffer);
5144 TEST(res == VK_SUCCESS);
5145
5146 VkMemoryRequirements memReq;
5147 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
5148 memoryTypeBits = memReq.memoryTypeBits;
5149
5150 vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs);
5151 }
5152
5153 VmaAllocationCreateInfo poolAllocInfo = {};
5154 poolAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
5155 uint32_t memTypeIndex;
5156 res = vmaFindMemoryTypeIndex(
5157 g_hAllocator,
5158 memoryTypeBits,
5159 &poolAllocInfo,
5160 &memTypeIndex);
5161
5162 VmaPoolCreateInfo poolCreateInfo = {};
5163 poolCreateInfo.memoryTypeIndex = memTypeIndex;
5164 poolCreateInfo.blockSize = BUF_SIZE * BUF_COUNT / 4;
5165 poolCreateInfo.minBlockCount = 1;
5166 poolCreateInfo.maxBlockCount = 4;
5167
5168 VmaPool pool;
5169 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
5170 TEST(res == VK_SUCCESS);
5171
5172 // Test pool name
5173 {
5174 static const char* const POOL_NAME = "Pool name";
5175 vmaSetPoolName(g_hAllocator, pool, POOL_NAME);
5176
5177 const char* fetchedPoolName = nullptr;
5178 vmaGetPoolName(g_hAllocator, pool, &fetchedPoolName);
5179 TEST(strcmp(fetchedPoolName, POOL_NAME) == 0);
5180
5181 // Generate JSON dump. There was a bug with this...
5182 char* json = nullptr;
5183 vmaBuildStatsString(g_hAllocator, &json, VK_TRUE);
5184 vmaFreeStatsString(g_hAllocator, json);
5185
5186 vmaSetPoolName(g_hAllocator, pool, nullptr);
5187 }
5188
5189 vmaSetCurrentFrameIndex(g_hAllocator, 1);
5190
5191 VmaAllocationCreateInfo allocInfo = {};
5192 allocInfo.pool = pool;
5193
5194 struct BufItem
5195 {
5196 VkBuffer Buf;
5197 VmaAllocation Alloc;
5198 };
5199 std::vector<BufItem> items;
5200
5201 // Fill entire pool.
5202 for(size_t i = 0; i < BUF_COUNT; ++i)
5203 {
5204 BufItem item;
5205 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
5206 TEST(res == VK_SUCCESS);
5207 items.push_back(item);
5208 }
5209
5210 // Make sure that another allocation would fail.
5211 {
5212 BufItem item;
5213 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
5214 TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
5215 }
5216
5217 // Validate allocations.
5218 for(size_t i = 0; i < items.size(); ++i)
5219 {
5220 VmaAllocationInfo allocInfo;
5221 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
5222 TEST(allocInfo.deviceMemory != VK_NULL_HANDLE);
5223 TEST(allocInfo.pMappedData == nullptr);
5224 }
5225
5226 // Free some percent of random items.
5227 {
5228 const size_t PERCENT_TO_FREE = 10;
5229 size_t itemsToFree = items.size() * PERCENT_TO_FREE / 100;
5230 for(size_t i = 0; i < itemsToFree; ++i)
5231 {
5232 size_t index = (size_t)rand.Generate() % items.size();
5233 vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc);
5234 items.erase(items.begin() + index);
5235 }
5236 }
5237
5238 // Randomly allocate and free items.
5239 {
5240 const size_t OPERATION_COUNT = BUF_COUNT;
5241 for(size_t i = 0; i < OPERATION_COUNT; ++i)
5242 {
5243 bool allocate = rand.Generate() % 2 != 0;
5244 if(allocate)
5245 {
5246 if(items.size() < BUF_COUNT)
5247 {
5248 BufItem item;
5249 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
5250 if(res == VK_SUCCESS)
5251 items.push_back(item);
5252 }
5253 }
5254 else // Free
5255 {
5256 if(!items.empty())
5257 {
5258 size_t index = (size_t)rand.Generate() % items.size();
5259 vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc);
5260 items.erase(items.begin() + index);
5261 }
5262 }
5263 }
5264 }
5265
5266 // Allocate up to maximum.
5267 while(items.size() < BUF_COUNT)
5268 {
5269 BufItem item;
5270 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
5271 TEST(res == VK_SUCCESS);
5272 items.push_back(item);
5273 }
5274
5275 // Free one item.
5276 vmaDestroyBuffer(g_hAllocator, items.back().Buf, items.back().Alloc);
5277 items.pop_back();
5278
5279 // Validate statistics.
5280 {
5281 VmaDetailedStatistics poolStats = {};
5282 vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
5283 TEST(poolStats.statistics.allocationCount == items.size());
5284 TEST(poolStats.statistics.blockBytes == BUF_COUNT * BUF_SIZE);
5285 TEST(poolStats.unusedRangeCount == 1);
5286 TEST(poolStats.statistics.blockBytes - poolStats.statistics.allocationBytes == BUF_SIZE);
5287 }
5288
5289 // Free all remaining items.
5290 for(size_t i = items.size(); i--; )
5291 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
5292 items.clear();
5293
5294 // Allocate maximum items again.
5295 for(size_t i = 0; i < BUF_COUNT; ++i)
5296 {
5297 BufItem item;
5298 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
5299 TEST(res == VK_SUCCESS);
5300 items.push_back(item);
5301 }
5302
5303 // Delete every other item.
5304 for(size_t i = 0; i < BUF_COUNT / 2; ++i)
5305 {
5306 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
5307 items.erase(items.begin() + i);
5308 }
5309
5310 // Defragment!
5311 {
5312 VmaDefragmentationInfo defragmentationInfo = {};
5313 defragmentationInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT;
5314 defragmentationInfo.pool = pool;
5315
5316 VmaDefragmentationContext defragCtx = nullptr;
5317 VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragmentationInfo, &defragCtx);
5318 TEST(res == VK_SUCCESS);
5319
5320 VmaDefragmentationPassMoveInfo pass = {};
5321 while ((res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_INCOMPLETE)
5322 {
5323 if ((res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_SUCCESS)
5324 break;
5325 TEST(res == VK_INCOMPLETE);
5326 }
5327 TEST(res == VK_SUCCESS);
5328
5329 VmaDefragmentationStats defragmentationStats;
5330 vmaEndDefragmentation(g_hAllocator, defragCtx, &defragmentationStats);
5331 TEST(defragmentationStats.allocationsMoved == 24);
5332 }
5333
5334 // Free all remaining items.
5335 for(size_t i = items.size(); i--; )
5336 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
5337 items.clear();
5338
5339 ////////////////////////////////////////////////////////////////////////////////
5340 // Test for allocation too large for pool
5341
5342 {
5343 VmaAllocationCreateInfo allocCreateInfo = {};
5344 allocCreateInfo.pool = pool;
5345
5346 VkMemoryRequirements memReq;
5347 memReq.memoryTypeBits = UINT32_MAX;
5348 memReq.alignment = 1;
5349 memReq.size = poolCreateInfo.blockSize + 4;
5350
5351 VmaAllocation alloc = nullptr;
5352 res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr);
5353 TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY && alloc == nullptr);
5354 }
5355
5356 vmaDestroyPool(g_hAllocator, pool);
5357 }
5358
ValidatePattern(const void * pMemory,size_t size,uint8_t pattern)5359 static bool ValidatePattern(const void* pMemory, size_t size, uint8_t pattern)
5360 {
5361 const uint8_t* pBytes = (const uint8_t*)pMemory;
5362 for(size_t i = 0; i < size; ++i)
5363 {
5364 if(pBytes[i] != pattern)
5365 {
5366 return false;
5367 }
5368 }
5369 return true;
5370 }
5371
TestAllocationsInitialization()5372 static void TestAllocationsInitialization()
5373 {
5374 VkResult res;
5375
5376 const size_t BUF_SIZE = 1024;
5377
5378 // Create pool.
5379
5380 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
5381 bufInfo.size = BUF_SIZE;
5382 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
5383
5384 VmaAllocationCreateInfo dummyBufAllocCreateInfo = {};
5385 dummyBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
5386 dummyBufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
5387
5388 VmaPoolCreateInfo poolCreateInfo = {};
5389 poolCreateInfo.blockSize = BUF_SIZE * 10;
5390 poolCreateInfo.minBlockCount = 1; // To keep memory alive while pool exists.
5391 poolCreateInfo.maxBlockCount = 1;
5392 res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufInfo, &dummyBufAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
5393 TEST(res == VK_SUCCESS);
5394
5395 VmaAllocationCreateInfo bufAllocCreateInfo = {};
5396 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &bufAllocCreateInfo.pool);
5397 TEST(res == VK_SUCCESS);
5398
5399 // Create one persistently mapped buffer to keep memory of this block mapped,
5400 // so that pointer to mapped data will remain (more or less...) valid even
5401 // after destruction of other allocations.
5402
5403 bufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
5404 VkBuffer firstBuf;
5405 VmaAllocation firstAlloc;
5406 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &firstBuf, &firstAlloc, nullptr);
5407 TEST(res == VK_SUCCESS);
5408
5409 // Test buffers.
5410
5411 for(uint32_t i = 0; i < 2; ++i)
5412 {
5413 const bool persistentlyMapped = i == 0;
5414 bufAllocCreateInfo.flags = persistentlyMapped ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0;
5415 VkBuffer buf;
5416 VmaAllocation alloc;
5417 VmaAllocationInfo allocInfo;
5418 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &buf, &alloc, &allocInfo);
5419 TEST(res == VK_SUCCESS);
5420
5421 void* pMappedData;
5422 if(!persistentlyMapped)
5423 {
5424 res = vmaMapMemory(g_hAllocator, alloc, &pMappedData);
5425 TEST(res == VK_SUCCESS);
5426 }
5427 else
5428 {
5429 pMappedData = allocInfo.pMappedData;
5430 }
5431
5432 // Validate initialized content
5433 bool valid = ValidatePattern(pMappedData, BUF_SIZE, 0xDC);
5434 TEST(valid);
5435
5436 if(!persistentlyMapped)
5437 {
5438 vmaUnmapMemory(g_hAllocator, alloc);
5439 }
5440
5441 vmaDestroyBuffer(g_hAllocator, buf, alloc);
5442
5443 // Validate freed content
5444 valid = ValidatePattern(pMappedData, BUF_SIZE, 0xEF);
5445 TEST(valid);
5446 }
5447
5448 vmaDestroyBuffer(g_hAllocator, firstBuf, firstAlloc);
5449 vmaDestroyPool(g_hAllocator, bufAllocCreateInfo.pool);
5450 }
5451
TestPool_Benchmark(PoolTestResult & outResult,const PoolTestConfig & config)5452 static void TestPool_Benchmark(
5453 PoolTestResult& outResult,
5454 const PoolTestConfig& config)
5455 {
5456 TEST(config.ThreadCount > 0);
5457
5458 RandomNumberGenerator mainRand{config.RandSeed};
5459
5460 uint32_t allocationSizeProbabilitySum = std::accumulate(
5461 config.AllocationSizes.begin(),
5462 config.AllocationSizes.end(),
5463 0u,
5464 [](uint32_t sum, const AllocationSize& allocSize) {
5465 return sum + allocSize.Probability;
5466 });
5467
5468 VkBufferCreateInfo bufferTemplateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
5469 bufferTemplateInfo.size = 256; // Whatever.
5470 bufferTemplateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
5471
5472 VkImageCreateInfo imageTemplateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
5473 imageTemplateInfo.imageType = VK_IMAGE_TYPE_2D;
5474 imageTemplateInfo.extent.width = 256; // Whatever.
5475 imageTemplateInfo.extent.height = 256; // Whatever.
5476 imageTemplateInfo.extent.depth = 1;
5477 imageTemplateInfo.mipLevels = 1;
5478 imageTemplateInfo.arrayLayers = 1;
5479 imageTemplateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
5480 imageTemplateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // LINEAR if CPU memory.
5481 imageTemplateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
5482 imageTemplateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // TRANSFER_SRC if CPU memory.
5483 imageTemplateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
5484
5485 uint32_t bufferMemoryTypeBits = UINT32_MAX;
5486 {
5487 VkBuffer dummyBuffer;
5488 VkResult res = vkCreateBuffer(g_hDevice, &bufferTemplateInfo, g_Allocs, &dummyBuffer);
5489 TEST(res == VK_SUCCESS);
5490
5491 VkMemoryRequirements memReq;
5492 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
5493 bufferMemoryTypeBits = memReq.memoryTypeBits;
5494
5495 vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs);
5496 }
5497
5498 uint32_t imageMemoryTypeBits = UINT32_MAX;
5499 {
5500 VkImage dummyImage;
5501 VkResult res = vkCreateImage(g_hDevice, &imageTemplateInfo, g_Allocs, &dummyImage);
5502 TEST(res == VK_SUCCESS);
5503
5504 VkMemoryRequirements memReq;
5505 vkGetImageMemoryRequirements(g_hDevice, dummyImage, &memReq);
5506 imageMemoryTypeBits = memReq.memoryTypeBits;
5507
5508 vkDestroyImage(g_hDevice, dummyImage, g_Allocs);
5509 }
5510
5511 uint32_t memoryTypeBits = 0;
5512 if(config.UsesBuffers() && config.UsesImages())
5513 {
5514 memoryTypeBits = bufferMemoryTypeBits & imageMemoryTypeBits;
5515 if(memoryTypeBits == 0)
5516 {
5517 PrintWarning(L"Cannot test buffers + images in the same memory pool on this GPU.");
5518 return;
5519 }
5520 }
5521 else if(config.UsesBuffers())
5522 memoryTypeBits = bufferMemoryTypeBits;
5523 else if(config.UsesImages())
5524 memoryTypeBits = imageMemoryTypeBits;
5525 else
5526 TEST(0);
5527
5528 VmaPoolCreateInfo poolCreateInfo = {};
5529 poolCreateInfo.minBlockCount = 1;
5530 poolCreateInfo.maxBlockCount = 1;
5531 poolCreateInfo.blockSize = config.PoolSize;
5532
5533 const VkPhysicalDeviceMemoryProperties* memProps = nullptr;
5534 vmaGetMemoryProperties(g_hAllocator, &memProps);
5535
5536 VmaPool pool = VK_NULL_HANDLE;
5537 VkResult res;
5538 // Loop over memory types because we sometimes allocate a big block here,
5539 // while the most eligible DEVICE_LOCAL heap may be only 256 MB on some GPUs.
5540 while(memoryTypeBits)
5541 {
5542 VmaAllocationCreateInfo dummyAllocCreateInfo = {};
5543 dummyAllocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
5544 vmaFindMemoryTypeIndex(g_hAllocator, memoryTypeBits, &dummyAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
5545
5546 const uint32_t heapIndex = memProps->memoryTypes[poolCreateInfo.memoryTypeIndex].heapIndex;
5547 // Protection against validation layer error when trying to allocate a block larger than entire heap size,
5548 // which may be only 256 MB on some platforms.
5549 if(poolCreateInfo.blockSize * poolCreateInfo.minBlockCount < memProps->memoryHeaps[heapIndex].size)
5550 {
5551 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
5552 if(res == VK_SUCCESS)
5553 break;
5554 }
5555 memoryTypeBits &= ~(1u << poolCreateInfo.memoryTypeIndex);
5556 }
5557 TEST(pool);
5558
5559 // Start time measurement - after creating pool and initializing data structures.
5560 time_point timeBeg = std::chrono::high_resolution_clock::now();
5561
5562 ////////////////////////////////////////////////////////////////////////////////
5563 // ThreadProc
5564 auto ThreadProc = [&config, allocationSizeProbabilitySum, pool](
5565 PoolTestThreadResult* outThreadResult,
5566 uint32_t randSeed,
5567 HANDLE frameStartEvent,
5568 HANDLE frameEndEvent) -> void
5569 {
5570 RandomNumberGenerator threadRand{randSeed};
5571 VkResult res = VK_SUCCESS;
5572
5573 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
5574 bufferInfo.size = 256; // Whatever.
5575 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
5576
5577 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
5578 imageInfo.imageType = VK_IMAGE_TYPE_2D;
5579 imageInfo.extent.width = 256; // Whatever.
5580 imageInfo.extent.height = 256; // Whatever.
5581 imageInfo.extent.depth = 1;
5582 imageInfo.mipLevels = 1;
5583 imageInfo.arrayLayers = 1;
5584 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
5585 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // LINEAR if CPU memory.
5586 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
5587 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // TRANSFER_SRC if CPU memory.
5588 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
5589
5590 outThreadResult->AllocationTimeMin = duration::max();
5591 outThreadResult->AllocationTimeSum = duration::zero();
5592 outThreadResult->AllocationTimeMax = duration::min();
5593 outThreadResult->DeallocationTimeMin = duration::max();
5594 outThreadResult->DeallocationTimeSum = duration::zero();
5595 outThreadResult->DeallocationTimeMax = duration::min();
5596 outThreadResult->AllocationCount = 0;
5597 outThreadResult->DeallocationCount = 0;
5598 outThreadResult->FailedAllocationCount = 0;
5599 outThreadResult->FailedAllocationTotalSize = 0;
5600
5601 struct Item
5602 {
5603 VkDeviceSize BufferSize = 0;
5604 VkExtent2D ImageSize = { 0, 0 };
5605 VkBuffer Buf = VK_NULL_HANDLE;
5606 VkImage Image = VK_NULL_HANDLE;
5607 VmaAllocation Alloc = VK_NULL_HANDLE;
5608
5609 Item() { }
5610 Item(Item&& src) :
5611 BufferSize(src.BufferSize), ImageSize(src.ImageSize), Buf(src.Buf), Image(src.Image), Alloc(src.Alloc)
5612 {
5613 src.BufferSize = 0;
5614 src.ImageSize = {0, 0};
5615 src.Buf = VK_NULL_HANDLE;
5616 src.Image = VK_NULL_HANDLE;
5617 src.Alloc = VK_NULL_HANDLE;
5618 }
5619 Item(const Item& src) = delete;
5620 ~Item()
5621 {
5622 DestroyResources();
5623 }
5624 Item& operator=(Item&& src)
5625 {
5626 if(&src != this)
5627 {
5628 DestroyResources();
5629 BufferSize = src.BufferSize; ImageSize = src.ImageSize;
5630 Buf = src.Buf; Image = src.Image; Alloc = src.Alloc;
5631 src.BufferSize = 0;
5632 src.ImageSize = {0, 0};
5633 src.Buf = VK_NULL_HANDLE;
5634 src.Image = VK_NULL_HANDLE;
5635 src.Alloc = VK_NULL_HANDLE;
5636 }
5637 return *this;
5638 }
5639 Item& operator=(const Item& src) = delete;
5640 void DestroyResources()
5641 {
5642 if(Buf)
5643 {
5644 assert(Image == VK_NULL_HANDLE);
5645 vmaDestroyBuffer(g_hAllocator, Buf, Alloc);
5646 Buf = VK_NULL_HANDLE;
5647 }
5648 else
5649 {
5650 vmaDestroyImage(g_hAllocator, Image, Alloc);
5651 Image = VK_NULL_HANDLE;
5652 }
5653 Alloc = VK_NULL_HANDLE;
5654 }
5655 VkDeviceSize CalcSizeBytes() const
5656 {
5657 return BufferSize +
5658 4ull * ImageSize.width * ImageSize.height;
5659 }
5660 };
5661 std::vector<Item> unusedItems, usedItems;
5662
5663 const size_t threadTotalItemCount = config.TotalItemCount / config.ThreadCount;
5664
5665 // Create all items - all unused, not yet allocated.
5666 for(size_t i = 0; i < threadTotalItemCount; ++i)
5667 {
5668 Item item = {};
5669
5670 uint32_t allocSizeIndex = 0;
5671 uint32_t r = threadRand.Generate() % allocationSizeProbabilitySum;
5672 while(r >= config.AllocationSizes[allocSizeIndex].Probability)
5673 r -= config.AllocationSizes[allocSizeIndex++].Probability;
5674
5675 const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex];
5676 if(allocSize.BufferSizeMax > 0)
5677 {
5678 TEST(allocSize.BufferSizeMin > 0);
5679 TEST(allocSize.ImageSizeMin == 0 && allocSize.ImageSizeMax == 0);
5680 if(allocSize.BufferSizeMax == allocSize.BufferSizeMin)
5681 item.BufferSize = allocSize.BufferSizeMin;
5682 else
5683 {
5684 item.BufferSize = allocSize.BufferSizeMin + threadRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin);
5685 item.BufferSize = item.BufferSize / 16 * 16;
5686 }
5687 }
5688 else
5689 {
5690 TEST(allocSize.ImageSizeMin > 0 && allocSize.ImageSizeMax > 0);
5691 if(allocSize.ImageSizeMax == allocSize.ImageSizeMin)
5692 item.ImageSize.width = item.ImageSize.height = allocSize.ImageSizeMax;
5693 else
5694 {
5695 item.ImageSize.width = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
5696 item.ImageSize.height = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
5697 }
5698 }
5699
5700 unusedItems.push_back(std::move(item));
5701 }
5702
5703 auto Allocate = [&](Item& item) -> VkResult
5704 {
5705 assert(item.Buf == VK_NULL_HANDLE && item.Image == VK_NULL_HANDLE && item.Alloc == VK_NULL_HANDLE);
5706
5707 VmaAllocationCreateInfo allocCreateInfo = {};
5708 allocCreateInfo.pool = pool;
5709
5710 if(item.BufferSize)
5711 {
5712 bufferInfo.size = item.BufferSize;
5713 VkResult res = VK_SUCCESS;
5714 {
5715 PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
5716 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocCreateInfo, &item.Buf, &item.Alloc, nullptr);
5717 }
5718 if(res == VK_SUCCESS)
5719 SetDebugUtilsObjectName(VK_OBJECT_TYPE_BUFFER, (uint64_t)item.Buf, "TestPool_Benchmark_Buffer");
5720 return res;
5721 }
5722 else
5723 {
5724 TEST(item.ImageSize.width && item.ImageSize.height);
5725
5726 imageInfo.extent.width = item.ImageSize.width;
5727 imageInfo.extent.height = item.ImageSize.height;
5728 VkResult res = VK_SUCCESS;
5729 {
5730 PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
5731 res = vmaCreateImage(g_hAllocator, &imageInfo, &allocCreateInfo, &item.Image, &item.Alloc, nullptr);
5732 }
5733 if(res == VK_SUCCESS)
5734 SetDebugUtilsObjectName(VK_OBJECT_TYPE_IMAGE, (uint64_t)item.Image, "TestPool_Benchmark_Image");
5735 return res;
5736 }
5737 };
5738
5739 ////////////////////////////////////////////////////////////////////////////////
5740 // Frames
5741 for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex)
5742 {
5743 WaitForSingleObject(frameStartEvent, INFINITE);
5744
5745 // Always make some percent of used bufs unused, to choose different used ones.
5746 const size_t bufsToMakeUnused = usedItems.size() * config.ItemsToMakeUnusedPercent / 100;
5747 for(size_t i = 0; i < bufsToMakeUnused; ++i)
5748 {
5749 size_t index = threadRand.Generate() % usedItems.size();
5750 auto it = usedItems.begin() + index;
5751 Item item = std::move(*it);
5752 usedItems.erase(it);
5753 unusedItems.push_back(std::move(item));
5754 }
5755
5756 // Determine which bufs we want to use in this frame.
5757 const size_t usedBufCount = (threadRand.Generate() % (config.UsedItemCountMax - config.UsedItemCountMin) + config.UsedItemCountMin)
5758 / config.ThreadCount;
5759 TEST(usedBufCount < usedItems.size() + unusedItems.size());
5760 // Move some used to unused.
5761 while(usedBufCount < usedItems.size())
5762 {
5763 size_t index = threadRand.Generate() % usedItems.size();
5764 auto it = usedItems.begin() + index;
5765 Item item = std::move(*it);
5766 usedItems.erase(it);
5767 unusedItems.push_back(std::move(item));
5768 }
5769 // Move some unused to used.
5770 while(usedBufCount > usedItems.size())
5771 {
5772 size_t index = threadRand.Generate() % unusedItems.size();
5773 auto it = unusedItems.begin() + index;
5774 Item item = std::move(*it);
5775 unusedItems.erase(it);
5776 usedItems.push_back(std::move(item));
5777 }
5778
5779 uint32_t touchExistingCount = 0;
5780 uint32_t touchLostCount = 0;
5781 uint32_t createSucceededCount = 0;
5782 uint32_t createFailedCount = 0;
5783
5784 // Touch all used bufs. If not created or lost, allocate.
5785 for(size_t i = 0; i < usedItems.size(); ++i)
5786 {
5787 Item& item = usedItems[i];
5788 // Not yet created.
5789 if(item.Alloc == VK_NULL_HANDLE)
5790 {
5791 res = Allocate(item);
5792 ++outThreadResult->AllocationCount;
5793 if(res != VK_SUCCESS)
5794 {
5795 assert(item.Alloc == VK_NULL_HANDLE && item.Buf == VK_NULL_HANDLE && item.Image == VK_NULL_HANDLE);
5796 ++outThreadResult->FailedAllocationCount;
5797 outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes();
5798 ++createFailedCount;
5799 }
5800 else
5801 ++createSucceededCount;
5802 }
5803 else
5804 {
5805 // Touch. TODO remove, refactor, there is no allocation touching any more.
5806 VmaAllocationInfo allocInfo;
5807 vmaGetAllocationInfo(g_hAllocator, item.Alloc, &allocInfo);
5808 ++touchExistingCount;
5809 }
5810 }
5811
5812 /*
5813 printf("Thread %u frame %u: Touch existing %u, create succeeded %u failed %u\n",
5814 randSeed, frameIndex,
5815 touchExistingCount,
5816 createSucceededCount, createFailedCount);
5817 */
5818
5819 SetEvent(frameEndEvent);
5820 }
5821
5822 // Free all remaining items.
5823 for(size_t i = usedItems.size(); i--; )
5824 {
5825 PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult);
5826 usedItems[i].DestroyResources();
5827 ++outThreadResult->DeallocationCount;
5828 }
5829 for(size_t i = unusedItems.size(); i--; )
5830 {
5831 PoolDeallocationTimeRegisterObj timeRegisterOb(*outThreadResult);
5832 unusedItems[i].DestroyResources();
5833 ++outThreadResult->DeallocationCount;
5834 }
5835 };
5836
5837 // Launch threads.
5838 uint32_t threadRandSeed = mainRand.Generate();
5839 std::vector<HANDLE> frameStartEvents{config.ThreadCount};
5840 std::vector<HANDLE> frameEndEvents{config.ThreadCount};
5841 std::vector<std::thread> bkgThreads;
5842 std::vector<PoolTestThreadResult> threadResults{config.ThreadCount};
5843 for(uint32_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
5844 {
5845 frameStartEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL);
5846 frameEndEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL);
5847 bkgThreads.emplace_back(std::bind(
5848 ThreadProc,
5849 &threadResults[threadIndex],
5850 threadRandSeed + threadIndex,
5851 frameStartEvents[threadIndex],
5852 frameEndEvents[threadIndex]));
5853 }
5854
5855 // Execute frames.
5856 TEST(config.ThreadCount <= MAXIMUM_WAIT_OBJECTS);
5857 for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex)
5858 {
5859 vmaSetCurrentFrameIndex(g_hAllocator, frameIndex);
5860 for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
5861 SetEvent(frameStartEvents[threadIndex]);
5862 WaitForMultipleObjects(config.ThreadCount, &frameEndEvents[0], TRUE, INFINITE);
5863 }
5864
5865 // Wait for threads finished
5866 for(size_t i = 0; i < bkgThreads.size(); ++i)
5867 {
5868 bkgThreads[i].join();
5869 CloseHandle(frameEndEvents[i]);
5870 CloseHandle(frameStartEvents[i]);
5871 }
5872 bkgThreads.clear();
5873
5874 // Finish time measurement - before destroying pool.
5875 outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg;
5876
5877 vmaDestroyPool(g_hAllocator, pool);
5878
5879 outResult.AllocationTimeMin = duration::max();
5880 outResult.AllocationTimeAvg = duration::zero();
5881 outResult.AllocationTimeMax = duration::min();
5882 outResult.DeallocationTimeMin = duration::max();
5883 outResult.DeallocationTimeAvg = duration::zero();
5884 outResult.DeallocationTimeMax = duration::min();
5885 outResult.FailedAllocationCount = 0;
5886 outResult.FailedAllocationTotalSize = 0;
5887 size_t allocationCount = 0;
5888 size_t deallocationCount = 0;
5889 for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
5890 {
5891 const PoolTestThreadResult& threadResult = threadResults[threadIndex];
5892 outResult.AllocationTimeMin = std::min(outResult.AllocationTimeMin, threadResult.AllocationTimeMin);
5893 outResult.AllocationTimeMax = std::max(outResult.AllocationTimeMax, threadResult.AllocationTimeMax);
5894 outResult.AllocationTimeAvg += threadResult.AllocationTimeSum;
5895 outResult.DeallocationTimeMin = std::min(outResult.DeallocationTimeMin, threadResult.DeallocationTimeMin);
5896 outResult.DeallocationTimeMax = std::max(outResult.DeallocationTimeMax, threadResult.DeallocationTimeMax);
5897 outResult.DeallocationTimeAvg += threadResult.DeallocationTimeSum;
5898 allocationCount += threadResult.AllocationCount;
5899 deallocationCount += threadResult.DeallocationCount;
5900 outResult.FailedAllocationCount += threadResult.FailedAllocationCount;
5901 outResult.FailedAllocationTotalSize += threadResult.FailedAllocationTotalSize;
5902 }
5903 if(allocationCount)
5904 outResult.AllocationTimeAvg /= allocationCount;
5905 if(deallocationCount)
5906 outResult.DeallocationTimeAvg /= deallocationCount;
5907 }
5908
MemoryRegionsOverlap(char * ptr1,size_t size1,char * ptr2,size_t size2)5909 static inline bool MemoryRegionsOverlap(char* ptr1, size_t size1, char* ptr2, size_t size2)
5910 {
5911 if(ptr1 < ptr2)
5912 return ptr1 + size1 > ptr2;
5913 else if(ptr2 < ptr1)
5914 return ptr2 + size2 > ptr1;
5915 else
5916 return true;
5917 }
5918
TestMemoryUsage()5919 static void TestMemoryUsage()
5920 {
5921 wprintf(L"Testing memory usage:\n");
5922
5923 static const VmaMemoryUsage lastUsage = VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED;
5924 for(uint32_t usage = 0; usage <= lastUsage; ++usage)
5925 {
5926 switch(usage)
5927 {
5928 case VMA_MEMORY_USAGE_UNKNOWN: printf(" VMA_MEMORY_USAGE_UNKNOWN:\n"); break;
5929 case VMA_MEMORY_USAGE_GPU_ONLY: printf(" VMA_MEMORY_USAGE_GPU_ONLY:\n"); break;
5930 case VMA_MEMORY_USAGE_CPU_ONLY: printf(" VMA_MEMORY_USAGE_CPU_ONLY:\n"); break;
5931 case VMA_MEMORY_USAGE_CPU_TO_GPU: printf(" VMA_MEMORY_USAGE_CPU_TO_GPU:\n"); break;
5932 case VMA_MEMORY_USAGE_GPU_TO_CPU: printf(" VMA_MEMORY_USAGE_GPU_TO_CPU:\n"); break;
5933 case VMA_MEMORY_USAGE_CPU_COPY: printf(" VMA_MEMORY_USAGE_CPU_COPY:\n"); break;
5934 case VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED: printf(" VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED:\n"); break;
5935 default: assert(0);
5936 }
5937
5938 auto printResult = [](const char* testName, VkResult res, uint32_t memoryTypeBits, uint32_t memoryTypeIndex)
5939 {
5940 if(res == VK_SUCCESS)
5941 printf(" %s: memoryTypeBits=0x%X, memoryTypeIndex=%u\n", testName, memoryTypeBits, memoryTypeIndex);
5942 else
5943 printf(" %s: memoryTypeBits=0x%X, FAILED with res=%d\n", testName, memoryTypeBits, (int32_t)res);
5944 };
5945
5946 // 1: Buffer for copy
5947 {
5948 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
5949 bufCreateInfo.size = 65536;
5950 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
5951
5952 VkBuffer buf = VK_NULL_HANDLE;
5953 VkResult res = vkCreateBuffer(g_hDevice, &bufCreateInfo, g_Allocs, &buf);
5954 TEST(res == VK_SUCCESS && buf != VK_NULL_HANDLE);
5955
5956 VkMemoryRequirements memReq = {};
5957 vkGetBufferMemoryRequirements(g_hDevice, buf, &memReq);
5958
5959 VmaAllocationCreateInfo allocCreateInfo = {};
5960 allocCreateInfo.usage = (VmaMemoryUsage)usage;
5961 VmaAllocation alloc = VK_NULL_HANDLE;
5962 VmaAllocationInfo allocInfo = {};
5963 res = vmaAllocateMemoryForBuffer(g_hAllocator, buf, &allocCreateInfo, &alloc, &allocInfo);
5964 if(res == VK_SUCCESS)
5965 {
5966 TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0);
5967 res = vkBindBufferMemory(g_hDevice, buf, allocInfo.deviceMemory, allocInfo.offset);
5968 TEST(res == VK_SUCCESS);
5969 }
5970 printResult("Buffer TRANSFER_DST + TRANSFER_SRC", res, memReq.memoryTypeBits, allocInfo.memoryType);
5971 vmaDestroyBuffer(g_hAllocator, buf, alloc);
5972 }
5973
5974 // 2: Vertex buffer
5975 {
5976 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
5977 bufCreateInfo.size = 65536;
5978 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
5979
5980 VkBuffer buf = VK_NULL_HANDLE;
5981 VkResult res = vkCreateBuffer(g_hDevice, &bufCreateInfo, g_Allocs, &buf);
5982 TEST(res == VK_SUCCESS && buf != VK_NULL_HANDLE);
5983
5984 VkMemoryRequirements memReq = {};
5985 vkGetBufferMemoryRequirements(g_hDevice, buf, &memReq);
5986
5987 VmaAllocationCreateInfo allocCreateInfo = {};
5988 allocCreateInfo.usage = (VmaMemoryUsage)usage;
5989 VmaAllocation alloc = VK_NULL_HANDLE;
5990 VmaAllocationInfo allocInfo = {};
5991 res = vmaAllocateMemoryForBuffer(g_hAllocator, buf, &allocCreateInfo, &alloc, &allocInfo);
5992 if(res == VK_SUCCESS)
5993 {
5994 TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0);
5995 res = vkBindBufferMemory(g_hDevice, buf, allocInfo.deviceMemory, allocInfo.offset);
5996 TEST(res == VK_SUCCESS);
5997 }
5998 printResult("Buffer TRANSFER_DST + VERTEX_BUFFER", res, memReq.memoryTypeBits, allocInfo.memoryType);
5999 vmaDestroyBuffer(g_hAllocator, buf, alloc);
6000 }
6001
6002 // 3: Image for copy, OPTIMAL
6003 {
6004 VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
6005 imgCreateInfo.imageType = VK_IMAGE_TYPE_2D;
6006 imgCreateInfo.extent.width = 256;
6007 imgCreateInfo.extent.height = 256;
6008 imgCreateInfo.extent.depth = 1;
6009 imgCreateInfo.mipLevels = 1;
6010 imgCreateInfo.arrayLayers = 1;
6011 imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
6012 imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
6013 imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
6014 imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
6015 imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
6016
6017 VkImage img = VK_NULL_HANDLE;
6018 VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img);
6019 TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE);
6020
6021 VkMemoryRequirements memReq = {};
6022 vkGetImageMemoryRequirements(g_hDevice, img, &memReq);
6023
6024 VmaAllocationCreateInfo allocCreateInfo = {};
6025 allocCreateInfo.usage = (VmaMemoryUsage)usage;
6026 VmaAllocation alloc = VK_NULL_HANDLE;
6027 VmaAllocationInfo allocInfo = {};
6028 res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo);
6029 if(res == VK_SUCCESS)
6030 {
6031 TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0);
6032 res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset);
6033 TEST(res == VK_SUCCESS);
6034 }
6035 printResult("Image OPTIMAL TRANSFER_DST + TRANSFER_SRC", res, memReq.memoryTypeBits, allocInfo.memoryType);
6036
6037 vmaDestroyImage(g_hAllocator, img, alloc);
6038 }
6039
6040 // 4: Image SAMPLED, OPTIMAL
6041 {
6042 VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
6043 imgCreateInfo.imageType = VK_IMAGE_TYPE_2D;
6044 imgCreateInfo.extent.width = 256;
6045 imgCreateInfo.extent.height = 256;
6046 imgCreateInfo.extent.depth = 1;
6047 imgCreateInfo.mipLevels = 1;
6048 imgCreateInfo.arrayLayers = 1;
6049 imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
6050 imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
6051 imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
6052 imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
6053 imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
6054
6055 VkImage img = VK_NULL_HANDLE;
6056 VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img);
6057 TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE);
6058
6059 VkMemoryRequirements memReq = {};
6060 vkGetImageMemoryRequirements(g_hDevice, img, &memReq);
6061
6062 VmaAllocationCreateInfo allocCreateInfo = {};
6063 allocCreateInfo.usage = (VmaMemoryUsage)usage;
6064 VmaAllocation alloc = VK_NULL_HANDLE;
6065 VmaAllocationInfo allocInfo = {};
6066 res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo);
6067 if(res == VK_SUCCESS)
6068 {
6069 TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0);
6070 res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset);
6071 TEST(res == VK_SUCCESS);
6072 }
6073 printResult("Image OPTIMAL TRANSFER_DST + SAMPLED", res, memReq.memoryTypeBits, allocInfo.memoryType);
6074 vmaDestroyImage(g_hAllocator, img, alloc);
6075 }
6076
6077 // 5: Image COLOR_ATTACHMENT, OPTIMAL
6078 {
6079 VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
6080 imgCreateInfo.imageType = VK_IMAGE_TYPE_2D;
6081 imgCreateInfo.extent.width = 256;
6082 imgCreateInfo.extent.height = 256;
6083 imgCreateInfo.extent.depth = 1;
6084 imgCreateInfo.mipLevels = 1;
6085 imgCreateInfo.arrayLayers = 1;
6086 imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
6087 imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
6088 imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
6089 imgCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
6090 imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
6091
6092 VkImage img = VK_NULL_HANDLE;
6093 VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img);
6094 TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE);
6095
6096 VkMemoryRequirements memReq = {};
6097 vkGetImageMemoryRequirements(g_hDevice, img, &memReq);
6098
6099 VmaAllocationCreateInfo allocCreateInfo = {};
6100 allocCreateInfo.usage = (VmaMemoryUsage)usage;
6101 VmaAllocation alloc = VK_NULL_HANDLE;
6102 VmaAllocationInfo allocInfo = {};
6103 res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo);
6104 if(res == VK_SUCCESS)
6105 {
6106 TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0);
6107 res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset);
6108 TEST(res == VK_SUCCESS);
6109 }
6110 printResult("Image OPTIMAL SAMPLED + COLOR_ATTACHMENT", res, memReq.memoryTypeBits, allocInfo.memoryType);
6111 vmaDestroyImage(g_hAllocator, img, alloc);
6112 }
6113 }
6114 }
6115
FindDeviceCoherentMemoryTypeBits()6116 static uint32_t FindDeviceCoherentMemoryTypeBits()
6117 {
6118 VkPhysicalDeviceMemoryProperties memProps;
6119 vkGetPhysicalDeviceMemoryProperties(g_hPhysicalDevice, &memProps);
6120
6121 uint32_t memTypeBits = 0;
6122 for(uint32_t i = 0; i < memProps.memoryTypeCount; ++i)
6123 {
6124 if(memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD)
6125 memTypeBits |= 1u << i;
6126 }
6127 return memTypeBits;
6128 }
6129
TestDeviceCoherentMemory()6130 static void TestDeviceCoherentMemory()
6131 {
6132 if(!VK_AMD_device_coherent_memory_enabled)
6133 return;
6134
6135 uint32_t deviceCoherentMemoryTypeBits = FindDeviceCoherentMemoryTypeBits();
6136 // Extension is enabled, feature is enabled, and the device still doesn't support any such memory type?
6137 // OK then, so it's just fake!
6138 if(deviceCoherentMemoryTypeBits == 0)
6139 return;
6140
6141 wprintf(L"Testing device coherent memory...\n");
6142
6143 // 1. Try to allocate buffer from a memory type that is DEVICE_COHERENT.
6144
6145 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
6146 bufCreateInfo.size = 0x10000;
6147 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
6148
6149 VmaAllocationCreateInfo allocCreateInfo = {};
6150 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
6151 allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD;
6152
6153 AllocInfo alloc = {};
6154 VmaAllocationInfo allocInfo = {};
6155 VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &alloc.m_Buffer, &alloc.m_Allocation, &allocInfo);
6156
6157 // Make sure it succeeded and was really created in such memory type.
6158 TEST(res == VK_SUCCESS);
6159 TEST((1u << allocInfo.memoryType) & deviceCoherentMemoryTypeBits);
6160
6161 alloc.Destroy();
6162
6163 // 2. Try to create a pool in such memory type.
6164 {
6165 VmaPoolCreateInfo poolCreateInfo = {};
6166
6167 res = vmaFindMemoryTypeIndex(g_hAllocator, UINT32_MAX, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex);
6168 TEST(res == VK_SUCCESS);
6169 TEST((1u << poolCreateInfo.memoryTypeIndex) & deviceCoherentMemoryTypeBits);
6170
6171 VmaPool pool = VK_NULL_HANDLE;
6172 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
6173 TEST(res == VK_SUCCESS);
6174
6175 vmaDestroyPool(g_hAllocator, pool);
6176 }
6177
6178 // 3. Try the same with a local allocator created without VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT.
6179
6180 VmaAllocatorCreateInfo allocatorCreateInfo = {};
6181 SetAllocatorCreateInfo(allocatorCreateInfo);
6182 allocatorCreateInfo.flags &= ~VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT;
6183
6184 VmaAllocator localAllocator = VK_NULL_HANDLE;
6185 res = vmaCreateAllocator(&allocatorCreateInfo, &localAllocator);
6186 TEST(res == VK_SUCCESS && localAllocator);
6187
6188 res = vmaCreateBuffer(localAllocator, &bufCreateInfo, &allocCreateInfo, &alloc.m_Buffer, &alloc.m_Allocation, &allocInfo);
6189
6190 // Make sure it failed.
6191 TEST(res != VK_SUCCESS && !alloc.m_Buffer && !alloc.m_Allocation);
6192
6193 // 4. Try to find memory type.
6194 {
6195 uint32_t memTypeIndex = UINT_MAX;
6196 res = vmaFindMemoryTypeIndex(localAllocator, UINT32_MAX, &allocCreateInfo, &memTypeIndex);
6197 TEST(res != VK_SUCCESS);
6198 }
6199
6200 vmaDestroyAllocator(localAllocator);
6201 }
6202
TestBudget()6203 static void TestBudget()
6204 {
6205 wprintf(L"Testing budget...\n");
6206
6207 static const VkDeviceSize BUF_SIZE = 10ull * 1024 * 1024;
6208 static const uint32_t BUF_COUNT = 4;
6209
6210 const VkPhysicalDeviceMemoryProperties* memProps = {};
6211 vmaGetMemoryProperties(g_hAllocator, &memProps);
6212
6213 for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
6214 {
6215 vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex);
6216
6217 VmaBudget budgetBeg[VK_MAX_MEMORY_HEAPS] = {};
6218 vmaGetHeapBudgets(g_hAllocator, budgetBeg);
6219
6220 for(uint32_t i = 0; i < memProps->memoryHeapCount; ++i)
6221 {
6222 TEST(budgetBeg[i].budget > 0);
6223 TEST(budgetBeg[i].budget <= memProps->memoryHeaps[i].size);
6224 TEST(budgetBeg[i].statistics.allocationBytes <= budgetBeg[i].statistics.blockBytes);
6225 }
6226
6227 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
6228 bufInfo.size = BUF_SIZE;
6229 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
6230
6231 VmaAllocationCreateInfo allocCreateInfo = {};
6232 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
6233 if(testIndex == 0)
6234 {
6235 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
6236 }
6237
6238 // CREATE BUFFERS
6239 uint32_t heapIndex = 0;
6240 BufferInfo bufInfos[BUF_COUNT] = {};
6241 for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex)
6242 {
6243 VmaAllocationInfo allocInfo;
6244 VkResult res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
6245 &bufInfos[bufIndex].Buffer, &bufInfos[bufIndex].Allocation, &allocInfo);
6246 TEST(res == VK_SUCCESS);
6247 if(bufIndex == 0)
6248 {
6249 heapIndex = MemoryTypeToHeap(allocInfo.memoryType);
6250 }
6251 else
6252 {
6253 // All buffers need to fall into the same heap.
6254 TEST(MemoryTypeToHeap(allocInfo.memoryType) == heapIndex);
6255 }
6256 }
6257
6258 VmaBudget budgetWithBufs[VK_MAX_MEMORY_HEAPS] = {};
6259 vmaGetHeapBudgets(g_hAllocator, budgetWithBufs);
6260
6261 // DESTROY BUFFERS
6262 for(size_t bufIndex = BUF_COUNT; bufIndex--; )
6263 {
6264 vmaDestroyBuffer(g_hAllocator, bufInfos[bufIndex].Buffer, bufInfos[bufIndex].Allocation);
6265 }
6266
6267 VmaBudget budgetEnd[VK_MAX_MEMORY_HEAPS] = {};
6268 vmaGetHeapBudgets(g_hAllocator, budgetEnd);
6269
6270 // CHECK
6271 for(uint32_t i = 0; i < memProps->memoryHeapCount; ++i)
6272 {
6273 TEST(budgetEnd[i].statistics.allocationBytes <= budgetEnd[i].statistics.blockBytes);
6274 if(i == heapIndex)
6275 {
6276 TEST(budgetEnd[i].statistics.allocationBytes == budgetBeg[i].statistics.allocationBytes);
6277 TEST(budgetWithBufs[i].statistics.allocationBytes == budgetBeg[i].statistics.allocationBytes + BUF_SIZE * BUF_COUNT);
6278 TEST(budgetWithBufs[i].statistics.blockBytes >= budgetEnd[i].statistics.blockBytes);
6279 }
6280 else
6281 {
6282 TEST(budgetEnd[i].statistics.allocationBytes == budgetEnd[i].statistics.allocationBytes &&
6283 budgetEnd[i].statistics.allocationBytes == budgetWithBufs[i].statistics.allocationBytes);
6284 TEST(budgetEnd[i].statistics.blockBytes == budgetEnd[i].statistics.blockBytes &&
6285 budgetEnd[i].statistics.blockBytes == budgetWithBufs[i].statistics.blockBytes);
6286 }
6287 }
6288 }
6289 }
6290
TestAliasing()6291 static void TestAliasing()
6292 {
6293 wprintf(L"Testing aliasing...\n");
6294
6295 /*
6296 This is just a simple test, more like a code sample to demonstrate it's possible.
6297 */
6298
6299 // A 512x512 texture to be sampled.
6300 VkImageCreateInfo img1CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
6301 img1CreateInfo.imageType = VK_IMAGE_TYPE_2D;
6302 img1CreateInfo.extent.width = 512;
6303 img1CreateInfo.extent.height = 512;
6304 img1CreateInfo.extent.depth = 1;
6305 img1CreateInfo.mipLevels = 10;
6306 img1CreateInfo.arrayLayers = 1;
6307 img1CreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
6308 img1CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
6309 img1CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
6310 img1CreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
6311 img1CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
6312
6313 // A full screen texture to be used as color attachment.
6314 VkImageCreateInfo img2CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
6315 img2CreateInfo.imageType = VK_IMAGE_TYPE_2D;
6316 img2CreateInfo.extent.width = 1920;
6317 img2CreateInfo.extent.height = 1080;
6318 img2CreateInfo.extent.depth = 1;
6319 img2CreateInfo.mipLevels = 1;
6320 img2CreateInfo.arrayLayers = 1;
6321 img2CreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
6322 img2CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
6323 img2CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
6324 img2CreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
6325 img2CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
6326
6327 VkImage img1 = VK_NULL_HANDLE;
6328 ERR_GUARD_VULKAN(vkCreateImage(g_hDevice, &img1CreateInfo, g_Allocs, &img1));
6329 VkImage img2 = VK_NULL_HANDLE;
6330 ERR_GUARD_VULKAN(vkCreateImage(g_hDevice, &img2CreateInfo, g_Allocs, &img2));
6331
6332 VkMemoryRequirements img1MemReq = {};
6333 vkGetImageMemoryRequirements(g_hDevice, img1, &img1MemReq);
6334 VkMemoryRequirements img2MemReq = {};
6335 vkGetImageMemoryRequirements(g_hDevice, img2, &img2MemReq);
6336
6337 VkMemoryRequirements finalMemReq = {};
6338 finalMemReq.size = std::max(img1MemReq.size, img2MemReq.size);
6339 finalMemReq.alignment = std::max(img1MemReq.alignment, img2MemReq.alignment);
6340 finalMemReq.memoryTypeBits = img1MemReq.memoryTypeBits & img2MemReq.memoryTypeBits;
6341 if(finalMemReq.memoryTypeBits != 0)
6342 {
6343 wprintf(L" size: max(%llu, %llu) = %llu\n",
6344 img1MemReq.size, img2MemReq.size, finalMemReq.size);
6345 wprintf(L" alignment: max(%llu, %llu) = %llu\n",
6346 img1MemReq.alignment, img2MemReq.alignment, finalMemReq.alignment);
6347 wprintf(L" memoryTypeBits: %u & %u = %u\n",
6348 img1MemReq.memoryTypeBits, img2MemReq.memoryTypeBits, finalMemReq.memoryTypeBits);
6349
6350 VmaAllocationCreateInfo allocCreateInfo = {};
6351 allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
6352
6353 VmaAllocation alloc = VK_NULL_HANDLE;
6354 ERR_GUARD_VULKAN(vmaAllocateMemory(g_hAllocator, &finalMemReq, &allocCreateInfo, &alloc, nullptr));
6355
6356 ERR_GUARD_VULKAN(vmaBindImageMemory(g_hAllocator, alloc, img1));
6357 ERR_GUARD_VULKAN(vmaBindImageMemory(g_hAllocator, alloc, img2));
6358
6359 // You can use img1, img2 here, but not at the same time!
6360
6361 vmaFreeMemory(g_hAllocator, alloc);
6362 }
6363 else
6364 {
6365 wprintf(L" Textures cannot alias!\n");
6366 }
6367
6368 vkDestroyImage(g_hDevice, img2, g_Allocs);
6369 vkDestroyImage(g_hDevice, img1, g_Allocs);
6370 }
6371
TestAllocationAliasing()6372 static void TestAllocationAliasing()
6373 {
6374 wprintf(L"Testing allocation aliasing...\n");
6375
6376 /*
6377 * Test whether using VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT suppress validation layer error
6378 * by don't supplying VkMemoryDedicatedAllocateInfoKHR to creation of dedicated memory
6379 * that will be used to alias with some other textures.
6380 */
6381
6382 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
6383 imageInfo.imageType = VK_IMAGE_TYPE_2D;
6384 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
6385 imageInfo.extent.depth = 1;
6386 imageInfo.mipLevels = 1;
6387 imageInfo.arrayLayers = 1;
6388 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
6389 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
6390 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
6391 imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
6392
6393 VmaAllocationCreateInfo allocationCreateInfo = {};
6394 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
6395 allocationCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
6396
6397 // Bind 2 textures together into same memory without VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT and then with flag set
6398 /*
6399 {
6400 VkImage originalImage;
6401 VmaAllocation allocation;
6402 imageInfo.extent.width = 640;
6403 imageInfo.extent.height = 480;
6404 VkResult res = vmaCreateImage(g_hAllocator, &imageInfo, &allocationCreateInfo, &originalImage, &allocation, nullptr);
6405 TEST(res == VK_SUCCESS);
6406
6407 VkImage aliasingImage;
6408 imageInfo.extent.width = 480;
6409 imageInfo.extent.height = 256;
6410 res = vkCreateImage(g_hDevice, &imageInfo, nullptr, &aliasingImage);
6411 TEST(res == VK_SUCCESS);
6412 // After binding there should be inevitable validation layer error VUID-vkBindImageMemory-memory-01509
6413 res = vmaBindImageMemory(g_hAllocator, allocation, aliasingImage);
6414 TEST(res == VK_SUCCESS);
6415
6416 vkDestroyImage(g_hDevice, aliasingImage, nullptr);
6417 vmaDestroyImage(g_hAllocator, originalImage, allocation);
6418 }
6419 */
6420 allocationCreateInfo.flags |= VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT;
6421 {
6422 VkImage originalImage;
6423 VmaAllocation allocation;
6424 imageInfo.extent.width = 640;
6425 imageInfo.extent.height = 480;
6426 VkResult res = vmaCreateImage(g_hAllocator, &imageInfo, &allocationCreateInfo, &originalImage, &allocation, nullptr);
6427 TEST(res == VK_SUCCESS);
6428
6429 VkImage aliasingImage;
6430 imageInfo.extent.width = 480;
6431 imageInfo.extent.height = 256;
6432 res = vkCreateImage(g_hDevice, &imageInfo, g_Allocs, &aliasingImage);
6433 TEST(res == VK_SUCCESS);
6434 // Now with VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT flag validation error is no more
6435 res = vmaBindImageMemory(g_hAllocator, allocation, aliasingImage);
6436 TEST(res == VK_SUCCESS);
6437
6438 vkDestroyImage(g_hDevice, aliasingImage, g_Allocs);
6439 vmaDestroyImage(g_hAllocator, originalImage, allocation);
6440 }
6441
6442 // Test creating buffer without DEDICATED flag, but large enought to end up as dedicated.
6443 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT;
6444
6445 VkBufferCreateInfo bufCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
6446 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT;
6447 bufCreateInfo.size = 300 * MEGABYTE;
6448
6449 {
6450 VkBuffer origBuf;
6451 VmaAllocation alloc;
6452 VmaAllocationInfo allocInfo;
6453 VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocationCreateInfo, &origBuf, &alloc, &allocInfo);
6454 TEST(res == VK_SUCCESS && origBuf && alloc);
6455 TEST(allocInfo.offset == 0); // Dedicated
6456
6457 VkBuffer aliasingBuf;
6458 bufCreateInfo.size = 200 * MEGABYTE;
6459 res = vmaCreateAliasingBuffer(g_hAllocator, alloc, &bufCreateInfo, &aliasingBuf);
6460 TEST(res == VK_SUCCESS && aliasingBuf);
6461
6462 vkDestroyBuffer(g_hDevice, aliasingBuf, g_Allocs);
6463 vmaDestroyBuffer(g_hAllocator, origBuf, alloc);
6464 }
6465 }
6466
TestMapping()6467 static void TestMapping()
6468 {
6469 wprintf(L"Testing mapping...\n");
6470
6471 VkResult res;
6472 uint32_t memTypeIndex = UINT32_MAX;
6473
6474 enum TEST
6475 {
6476 TEST_NORMAL,
6477 TEST_POOL,
6478 TEST_DEDICATED,
6479 TEST_COUNT
6480 };
6481 for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex)
6482 {
6483 VmaPool pool = nullptr;
6484 if(testIndex == TEST_POOL)
6485 {
6486 TEST(memTypeIndex != UINT32_MAX);
6487 VmaPoolCreateInfo poolInfo = {};
6488 poolInfo.memoryTypeIndex = memTypeIndex;
6489 res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
6490 TEST(res == VK_SUCCESS);
6491 }
6492
6493 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
6494 bufInfo.size = 0x10000;
6495 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
6496
6497 VmaAllocationCreateInfo allocCreateInfo = {};
6498 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
6499 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
6500 allocCreateInfo.pool = pool;
6501 if(testIndex == TEST_DEDICATED)
6502 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
6503
6504 VmaAllocationInfo allocInfo;
6505
6506 // Mapped manually
6507
6508 // Create 2 buffers.
6509 BufferInfo bufferInfos[3];
6510 for(size_t i = 0; i < 2; ++i)
6511 {
6512 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
6513 &bufferInfos[i].Buffer, &bufferInfos[i].Allocation, &allocInfo);
6514 TEST(res == VK_SUCCESS);
6515 TEST(allocInfo.pMappedData == nullptr);
6516 memTypeIndex = allocInfo.memoryType;
6517 }
6518
6519 // Map buffer 0.
6520 char* data00 = nullptr;
6521 res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data00);
6522 TEST(res == VK_SUCCESS && data00 != nullptr);
6523 data00[0xFFFF] = data00[0];
6524
6525 // Map buffer 0 second time.
6526 char* data01 = nullptr;
6527 res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data01);
6528 TEST(res == VK_SUCCESS && data01 == data00);
6529
6530 // Map buffer 1.
6531 char* data1 = nullptr;
6532 res = vmaMapMemory(g_hAllocator, bufferInfos[1].Allocation, (void**)&data1);
6533 TEST(res == VK_SUCCESS && data1 != nullptr);
6534 TEST(!MemoryRegionsOverlap(data00, (size_t)bufInfo.size, data1, (size_t)bufInfo.size));
6535 data1[0xFFFF] = data1[0];
6536
6537 // Unmap buffer 0 two times.
6538 vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
6539 vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
6540 vmaGetAllocationInfo(g_hAllocator, bufferInfos[0].Allocation, &allocInfo);
6541 TEST(allocInfo.pMappedData == nullptr);
6542
6543 // Unmap buffer 1.
6544 vmaUnmapMemory(g_hAllocator, bufferInfos[1].Allocation);
6545 vmaGetAllocationInfo(g_hAllocator, bufferInfos[1].Allocation, &allocInfo);
6546 TEST(allocInfo.pMappedData == nullptr);
6547
6548 // Create 3rd buffer - persistently mapped.
6549 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
6550 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
6551 &bufferInfos[2].Buffer, &bufferInfos[2].Allocation, &allocInfo);
6552 TEST(res == VK_SUCCESS && allocInfo.pMappedData != nullptr);
6553
6554 // Map buffer 2.
6555 char* data2 = nullptr;
6556 res = vmaMapMemory(g_hAllocator, bufferInfos[2].Allocation, (void**)&data2);
6557 TEST(res == VK_SUCCESS && data2 == allocInfo.pMappedData);
6558 data2[0xFFFF] = data2[0];
6559
6560 // Unmap buffer 2.
6561 vmaUnmapMemory(g_hAllocator, bufferInfos[2].Allocation);
6562 vmaGetAllocationInfo(g_hAllocator, bufferInfos[2].Allocation, &allocInfo);
6563 TEST(allocInfo.pMappedData == data2);
6564
6565 // Destroy all buffers.
6566 for(size_t i = 3; i--; )
6567 vmaDestroyBuffer(g_hAllocator, bufferInfos[i].Buffer, bufferInfos[i].Allocation);
6568
6569 vmaDestroyPool(g_hAllocator, pool);
6570 }
6571 }
6572
6573 // Test CREATE_MAPPED with required DEVICE_LOCAL. There was a bug with it.
TestDeviceLocalMapped()6574 static void TestDeviceLocalMapped()
6575 {
6576 VkResult res;
6577
6578 for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
6579 {
6580 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
6581 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
6582 bufCreateInfo.size = 4096;
6583
6584 VmaPool pool = VK_NULL_HANDLE;
6585 VmaAllocationCreateInfo allocCreateInfo = {};
6586 allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
6587 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
6588 if(testIndex == 1)
6589 {
6590 VmaPoolCreateInfo poolCreateInfo = {};
6591 res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex);
6592 TEST(res == VK_SUCCESS);
6593 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
6594 TEST(res == VK_SUCCESS);
6595 allocCreateInfo.pool = pool;
6596 }
6597
6598 VkBuffer buf = VK_NULL_HANDLE;
6599 VmaAllocation alloc = VK_NULL_HANDLE;
6600 VmaAllocationInfo allocInfo = {};
6601 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
6602 TEST(res == VK_SUCCESS && alloc);
6603
6604 VkMemoryPropertyFlags memTypeFlags = 0;
6605 vmaGetMemoryTypeProperties(g_hAllocator, allocInfo.memoryType, &memTypeFlags);
6606 const bool shouldBeMapped = (memTypeFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0;
6607 TEST((allocInfo.pMappedData != nullptr) == shouldBeMapped);
6608
6609 vmaDestroyBuffer(g_hAllocator, buf, alloc);
6610 vmaDestroyPool(g_hAllocator, pool);
6611 }
6612 }
6613
TestMappingMultithreaded()6614 static void TestMappingMultithreaded()
6615 {
6616 wprintf(L"Testing mapping multithreaded...\n");
6617
6618 static const uint32_t threadCount = 16;
6619 static const uint32_t bufferCount = 1024;
6620 static const uint32_t threadBufferCount = bufferCount / threadCount;
6621
6622 VkResult res;
6623 volatile uint32_t memTypeIndex = UINT32_MAX;
6624
6625 enum TEST
6626 {
6627 TEST_NORMAL,
6628 TEST_POOL,
6629 TEST_DEDICATED,
6630 TEST_COUNT
6631 };
6632 for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex)
6633 {
6634 VmaPool pool = nullptr;
6635 if(testIndex == TEST_POOL)
6636 {
6637 TEST(memTypeIndex != UINT32_MAX);
6638 VmaPoolCreateInfo poolInfo = {};
6639 poolInfo.memoryTypeIndex = memTypeIndex;
6640 res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
6641 TEST(res == VK_SUCCESS);
6642 }
6643
6644 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
6645 bufCreateInfo.size = 0x10000;
6646 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
6647
6648 VmaAllocationCreateInfo allocCreateInfo = {};
6649 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
6650 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
6651 allocCreateInfo.pool = pool;
6652 if(testIndex == TEST_DEDICATED)
6653 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
6654
6655 std::thread threads[threadCount];
6656 for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex)
6657 {
6658 threads[threadIndex] = std::thread([=, &memTypeIndex](){
6659 // ======== THREAD FUNCTION ========
6660
6661 RandomNumberGenerator rand{threadIndex};
6662
6663 enum class MODE
6664 {
6665 // Don't map this buffer at all.
6666 DONT_MAP,
6667 // Map and quickly unmap.
6668 MAP_FOR_MOMENT,
6669 // Map and unmap before destruction.
6670 MAP_FOR_LONGER,
6671 // Map two times. Quickly unmap, second unmap before destruction.
6672 MAP_TWO_TIMES,
6673 // Create this buffer as persistently mapped.
6674 PERSISTENTLY_MAPPED,
6675 COUNT
6676 };
6677 std::vector<BufferInfo> bufInfos{threadBufferCount};
6678 std::vector<MODE> bufModes{threadBufferCount};
6679
6680 for(uint32_t bufferIndex = 0; bufferIndex < threadBufferCount; ++bufferIndex)
6681 {
6682 BufferInfo& bufInfo = bufInfos[bufferIndex];
6683 const MODE mode = (MODE)(rand.Generate() % (uint32_t)MODE::COUNT);
6684 bufModes[bufferIndex] = mode;
6685
6686 VmaAllocationCreateInfo localAllocCreateInfo = allocCreateInfo;
6687 if(mode == MODE::PERSISTENTLY_MAPPED)
6688 localAllocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
6689
6690 VmaAllocationInfo allocInfo;
6691 VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &localAllocCreateInfo,
6692 &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo);
6693 TEST(res == VK_SUCCESS);
6694
6695 if(memTypeIndex == UINT32_MAX)
6696 memTypeIndex = allocInfo.memoryType;
6697
6698 char* data = nullptr;
6699
6700 if(mode == MODE::PERSISTENTLY_MAPPED)
6701 {
6702 data = (char*)allocInfo.pMappedData;
6703 TEST(data != nullptr);
6704 }
6705 else if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_FOR_LONGER ||
6706 mode == MODE::MAP_TWO_TIMES)
6707 {
6708 TEST(data == nullptr);
6709 res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data);
6710 TEST(res == VK_SUCCESS && data != nullptr);
6711
6712 if(mode == MODE::MAP_TWO_TIMES)
6713 {
6714 char* data2 = nullptr;
6715 res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data2);
6716 TEST(res == VK_SUCCESS && data2 == data);
6717 }
6718 }
6719 else if(mode == MODE::DONT_MAP)
6720 {
6721 TEST(allocInfo.pMappedData == nullptr);
6722 }
6723 else
6724 TEST(0);
6725
6726 // Test if reading and writing from the beginning and end of mapped memory doesn't crash.
6727 if(data)
6728 data[0xFFFF] = data[0];
6729
6730 if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_TWO_TIMES)
6731 {
6732 vmaUnmapMemory(g_hAllocator, bufInfo.Allocation);
6733
6734 VmaAllocationInfo allocInfo;
6735 vmaGetAllocationInfo(g_hAllocator, bufInfo.Allocation, &allocInfo);
6736 if(mode == MODE::MAP_FOR_MOMENT)
6737 TEST(allocInfo.pMappedData == nullptr);
6738 else
6739 TEST(allocInfo.pMappedData == data);
6740 }
6741
6742 switch(rand.Generate() % 3)
6743 {
6744 case 0: Sleep(0); break; // Yield.
6745 case 1: Sleep(10); break; // 10 ms
6746 // default: No sleep.
6747 }
6748
6749 // Test if reading and writing from the beginning and end of mapped memory doesn't crash.
6750 if(data)
6751 data[0xFFFF] = data[0];
6752 }
6753
6754 for(size_t bufferIndex = threadBufferCount; bufferIndex--; )
6755 {
6756 if(bufModes[bufferIndex] == MODE::MAP_FOR_LONGER ||
6757 bufModes[bufferIndex] == MODE::MAP_TWO_TIMES)
6758 {
6759 vmaUnmapMemory(g_hAllocator, bufInfos[bufferIndex].Allocation);
6760
6761 VmaAllocationInfo allocInfo;
6762 vmaGetAllocationInfo(g_hAllocator, bufInfos[bufferIndex].Allocation, &allocInfo);
6763 TEST(allocInfo.pMappedData == nullptr);
6764 }
6765
6766 vmaDestroyBuffer(g_hAllocator, bufInfos[bufferIndex].Buffer, bufInfos[bufferIndex].Allocation);
6767 }
6768 });
6769 }
6770
6771 for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex)
6772 threads[threadIndex].join();
6773
6774 vmaDestroyPool(g_hAllocator, pool);
6775 }
6776 }
6777
WriteMainTestResultHeader(FILE * file)6778 static void WriteMainTestResultHeader(FILE* file)
6779 {
6780 fprintf(file,
6781 "Code,Time,"
6782 "Threads,Buffers and images,Sizes,Operations,Allocation strategy,Free order,"
6783 "Total Time (us),"
6784 "Allocation Time Min (us),"
6785 "Allocation Time Avg (us),"
6786 "Allocation Time Max (us),"
6787 "Deallocation Time Min (us),"
6788 "Deallocation Time Avg (us),"
6789 "Deallocation Time Max (us),"
6790 "Total Memory Allocated (B),"
6791 "Free Range Size Avg (B),"
6792 "Free Range Size Max (B)\n");
6793 }
6794
WriteMainTestResult(FILE * file,const char * codeDescription,const char * testDescription,const Config & config,const Result & result)6795 static void WriteMainTestResult(
6796 FILE* file,
6797 const char* codeDescription,
6798 const char* testDescription,
6799 const Config& config, const Result& result)
6800 {
6801 float totalTimeSeconds = ToFloatSeconds(result.TotalTime);
6802 float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin);
6803 float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg);
6804 float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax);
6805 float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin);
6806 float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg);
6807 float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax);
6808
6809 std::string currTime;
6810 CurrentTimeToStr(currTime);
6811
6812 fprintf(file,
6813 "%s,%s,%s,"
6814 "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n",
6815 codeDescription,
6816 currTime.c_str(),
6817 testDescription,
6818 totalTimeSeconds * 1e6f,
6819 allocationTimeMinSeconds * 1e6f,
6820 allocationTimeAvgSeconds * 1e6f,
6821 allocationTimeMaxSeconds * 1e6f,
6822 deallocationTimeMinSeconds * 1e6f,
6823 deallocationTimeAvgSeconds * 1e6f,
6824 deallocationTimeMaxSeconds * 1e6f,
6825 result.TotalMemoryAllocated,
6826 result.FreeRangeSizeAvg,
6827 result.FreeRangeSizeMax);
6828 }
6829
WritePoolTestResultHeader(FILE * file)6830 static void WritePoolTestResultHeader(FILE* file)
6831 {
6832 fprintf(file,
6833 "Code,Test,Time,"
6834 "Config,"
6835 "Total Time (us),"
6836 "Allocation Time Min (us),"
6837 "Allocation Time Avg (us),"
6838 "Allocation Time Max (us),"
6839 "Deallocation Time Min (us),"
6840 "Deallocation Time Avg (us),"
6841 "Deallocation Time Max (us),"
6842 "Failed Allocation Count,"
6843 "Failed Allocation Total Size (B)\n");
6844 }
6845
WritePoolTestResult(FILE * file,const char * codeDescription,const char * testDescription,const PoolTestConfig & config,const PoolTestResult & result)6846 static void WritePoolTestResult(
6847 FILE* file,
6848 const char* codeDescription,
6849 const char* testDescription,
6850 const PoolTestConfig& config,
6851 const PoolTestResult& result)
6852 {
6853 float totalTimeSeconds = ToFloatSeconds(result.TotalTime);
6854 float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin);
6855 float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg);
6856 float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax);
6857 float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin);
6858 float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg);
6859 float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax);
6860
6861 std::string currTime;
6862 CurrentTimeToStr(currTime);
6863
6864 fprintf(file,
6865 "%s,%s,%s,"
6866 "ThreadCount=%u PoolSize=%llu FrameCount=%u TotalItemCount=%u UsedItemCount=%u...%u ItemsToMakeUnusedPercent=%u,"
6867 "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u\n",
6868 // General
6869 codeDescription,
6870 testDescription,
6871 currTime.c_str(),
6872 // Config
6873 config.ThreadCount,
6874 (unsigned long long)config.PoolSize,
6875 config.FrameCount,
6876 config.TotalItemCount,
6877 config.UsedItemCountMin,
6878 config.UsedItemCountMax,
6879 config.ItemsToMakeUnusedPercent,
6880 // Results
6881 totalTimeSeconds * 1e6f,
6882 allocationTimeMinSeconds * 1e6f,
6883 allocationTimeAvgSeconds * 1e6f,
6884 allocationTimeMaxSeconds * 1e6f,
6885 deallocationTimeMinSeconds * 1e6f,
6886 deallocationTimeAvgSeconds * 1e6f,
6887 deallocationTimeMaxSeconds * 1e6f,
6888 result.FailedAllocationCount,
6889 result.FailedAllocationTotalSize);
6890 }
6891
PerformCustomMainTest(FILE * file)6892 static void PerformCustomMainTest(FILE* file)
6893 {
6894 Config config{};
6895 config.RandSeed = 65735476;
6896 //config.MaxBytesToAllocate = 4ull * 1024 * 1024; // 4 MB
6897 config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB
6898 config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY
6899 config.FreeOrder = FREE_ORDER::FORWARD;
6900 config.ThreadCount = 16;
6901 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
6902 config.AllocationStrategy = 0;
6903
6904 // Buffers
6905 //config.AllocationSizes.push_back({4, 16, 1024});
6906 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
6907
6908 // Images
6909 //config.AllocationSizes.push_back({4, 0, 0, 4, 32});
6910 //config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
6911
6912 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100;
6913 config.AdditionalOperationCount = 1024;
6914
6915 Result result{};
6916 VkResult res = MainTest(result, config);
6917 TEST(res == VK_SUCCESS);
6918 WriteMainTestResult(file, "Foo", "CustomTest", config, result);
6919 }
6920
PerformCustomPoolTest(FILE * file)6921 static void PerformCustomPoolTest(FILE* file)
6922 {
6923 PoolTestConfig config;
6924 config.PoolSize = 100 * 1024 * 1024;
6925 config.RandSeed = 2345764;
6926 config.ThreadCount = 1;
6927 config.FrameCount = 200;
6928 config.ItemsToMakeUnusedPercent = 2;
6929
6930 AllocationSize allocSize = {};
6931 allocSize.BufferSizeMin = 1024;
6932 allocSize.BufferSizeMax = 1024 * 1024;
6933 allocSize.Probability = 1;
6934 config.AllocationSizes.push_back(allocSize);
6935
6936 allocSize.BufferSizeMin = 0;
6937 allocSize.BufferSizeMax = 0;
6938 allocSize.ImageSizeMin = 128;
6939 allocSize.ImageSizeMax = 1024;
6940 allocSize.Probability = 1;
6941 config.AllocationSizes.push_back(allocSize);
6942
6943 config.PoolSize = config.CalcAvgResourceSize() * 200;
6944 config.UsedItemCountMax = 160;
6945 config.TotalItemCount = config.UsedItemCountMax * 10;
6946 config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100;
6947
6948 PoolTestResult result = {};
6949 TestPool_Benchmark(result, config);
6950
6951 WritePoolTestResult(file, "Code desc", "Test desc", config, result);
6952 }
6953
PerformMainTests(FILE * file)6954 static void PerformMainTests(FILE* file)
6955 {
6956 wprintf(L"MAIN TESTS:\n");
6957
6958 uint32_t repeatCount = 1;
6959 if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3;
6960
6961 Config config{};
6962 config.RandSeed = 65735476;
6963 config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY
6964 config.FreeOrder = FREE_ORDER::FORWARD;
6965
6966 size_t threadCountCount = 1;
6967 switch(ConfigType)
6968 {
6969 case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break;
6970 case CONFIG_TYPE_SMALL: threadCountCount = 2; break;
6971 case CONFIG_TYPE_AVERAGE: threadCountCount = 3; break;
6972 case CONFIG_TYPE_LARGE: threadCountCount = 5; break;
6973 case CONFIG_TYPE_MAXIMUM: threadCountCount = 7; break;
6974 default: assert(0);
6975 }
6976
6977 const size_t strategyCount = GetAllocationStrategyCount();
6978
6979 for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex)
6980 {
6981 std::string desc1;
6982
6983 switch(threadCountIndex)
6984 {
6985 case 0:
6986 desc1 += "1_thread";
6987 config.ThreadCount = 1;
6988 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
6989 break;
6990 case 1:
6991 desc1 += "16_threads+0%_common";
6992 config.ThreadCount = 16;
6993 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
6994 break;
6995 case 2:
6996 desc1 += "16_threads+50%_common";
6997 config.ThreadCount = 16;
6998 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
6999 break;
7000 case 3:
7001 desc1 += "16_threads+100%_common";
7002 config.ThreadCount = 16;
7003 config.ThreadsUsingCommonAllocationsProbabilityPercent = 100;
7004 break;
7005 case 4:
7006 desc1 += "2_threads+0%_common";
7007 config.ThreadCount = 2;
7008 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
7009 break;
7010 case 5:
7011 desc1 += "2_threads+50%_common";
7012 config.ThreadCount = 2;
7013 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
7014 break;
7015 case 6:
7016 desc1 += "2_threads+100%_common";
7017 config.ThreadCount = 2;
7018 config.ThreadsUsingCommonAllocationsProbabilityPercent = 100;
7019 break;
7020 default:
7021 assert(0);
7022 }
7023
7024 // 0 = buffers, 1 = images, 2 = buffers and images
7025 size_t buffersVsImagesCount = 2;
7026 if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount;
7027 for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex)
7028 {
7029 std::string desc2 = desc1;
7030 switch(buffersVsImagesIndex)
7031 {
7032 case 0: desc2 += ",Buffers"; break;
7033 case 1: desc2 += ",Images"; break;
7034 case 2: desc2 += ",Buffers+Images"; break;
7035 default: assert(0);
7036 }
7037
7038 // 0 = small, 1 = large, 2 = small and large
7039 size_t smallVsLargeCount = 2;
7040 if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount;
7041 for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex)
7042 {
7043 std::string desc3 = desc2;
7044 switch(smallVsLargeIndex)
7045 {
7046 case 0: desc3 += ",Small"; break;
7047 case 1: desc3 += ",Large"; break;
7048 case 2: desc3 += ",Small+Large"; break;
7049 default: assert(0);
7050 }
7051
7052 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
7053 config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB
7054 else
7055 config.MaxBytesToAllocate = 4ull * 1024 * 1024;
7056
7057 // 0 = varying sizes min...max, 1 = set of constant sizes
7058 size_t constantSizesCount = 1;
7059 if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount;
7060 for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex)
7061 {
7062 std::string desc4 = desc3;
7063 switch(constantSizesIndex)
7064 {
7065 case 0: desc4 += " Varying_sizes"; break;
7066 case 1: desc4 += " Constant_sizes"; break;
7067 default: assert(0);
7068 }
7069
7070 config.AllocationSizes.clear();
7071 // Buffers present
7072 if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
7073 {
7074 // Small
7075 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
7076 {
7077 // Varying size
7078 if(constantSizesIndex == 0)
7079 config.AllocationSizes.push_back({4, 16, 1024});
7080 // Constant sizes
7081 else
7082 {
7083 config.AllocationSizes.push_back({1, 16, 16});
7084 config.AllocationSizes.push_back({1, 64, 64});
7085 config.AllocationSizes.push_back({1, 256, 256});
7086 config.AllocationSizes.push_back({1, 1024, 1024});
7087 }
7088 }
7089 // Large
7090 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
7091 {
7092 // Varying size
7093 if(constantSizesIndex == 0)
7094 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
7095 // Constant sizes
7096 else
7097 {
7098 config.AllocationSizes.push_back({1, 0x10000, 0x10000});
7099 config.AllocationSizes.push_back({1, 0x80000, 0x80000});
7100 config.AllocationSizes.push_back({1, 0x200000, 0x200000});
7101 config.AllocationSizes.push_back({1, 0xA00000, 0xA00000});
7102 }
7103 }
7104 }
7105 // Images present
7106 if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
7107 {
7108 // Small
7109 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
7110 {
7111 // Varying size
7112 if(constantSizesIndex == 0)
7113 config.AllocationSizes.push_back({4, 0, 0, 4, 32});
7114 // Constant sizes
7115 else
7116 {
7117 config.AllocationSizes.push_back({1, 0, 0, 4, 4});
7118 config.AllocationSizes.push_back({1, 0, 0, 8, 8});
7119 config.AllocationSizes.push_back({1, 0, 0, 16, 16});
7120 config.AllocationSizes.push_back({1, 0, 0, 32, 32});
7121 }
7122 }
7123 // Large
7124 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
7125 {
7126 // Varying size
7127 if(constantSizesIndex == 0)
7128 config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
7129 // Constant sizes
7130 else
7131 {
7132 config.AllocationSizes.push_back({1, 0, 0, 256, 256});
7133 config.AllocationSizes.push_back({1, 0, 0, 512, 512});
7134 config.AllocationSizes.push_back({1, 0, 0, 1024, 1024});
7135 config.AllocationSizes.push_back({1, 0, 0, 2048, 2048});
7136 }
7137 }
7138 }
7139
7140 // 0 = 100%, additional_operations = 0, 1 = 50%, 2 = 5%, 3 = 95% additional_operations = a lot
7141 size_t beginBytesToAllocateCount = 1;
7142 if(ConfigType >= CONFIG_TYPE_SMALL) ++beginBytesToAllocateCount;
7143 if(ConfigType >= CONFIG_TYPE_AVERAGE) ++beginBytesToAllocateCount;
7144 if(ConfigType >= CONFIG_TYPE_LARGE) ++beginBytesToAllocateCount;
7145 for(size_t beginBytesToAllocateIndex = 0; beginBytesToAllocateIndex < beginBytesToAllocateCount; ++beginBytesToAllocateIndex)
7146 {
7147 std::string desc5 = desc4;
7148
7149 switch(beginBytesToAllocateIndex)
7150 {
7151 case 0:
7152 desc5 += ",Allocate_100%";
7153 config.BeginBytesToAllocate = config.MaxBytesToAllocate;
7154 config.AdditionalOperationCount = 0;
7155 break;
7156 case 1:
7157 desc5 += ",Allocate_50%+Operations";
7158 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 50 / 100;
7159 config.AdditionalOperationCount = 1024;
7160 break;
7161 case 2:
7162 desc5 += ",Allocate_5%+Operations";
7163 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100;
7164 config.AdditionalOperationCount = 1024;
7165 break;
7166 case 3:
7167 desc5 += ",Allocate_95%+Operations";
7168 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 95 / 100;
7169 config.AdditionalOperationCount = 1024;
7170 break;
7171 default:
7172 assert(0);
7173 }
7174
7175 for(size_t strategyIndex = 0; strategyIndex < strategyCount; ++strategyIndex)
7176 {
7177 std::string desc6 = desc5;
7178 switch(strategyIndex)
7179 {
7180 case 0:
7181 desc6 += ",MinMemory";
7182 config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT;
7183 break;
7184 case 1:
7185 desc6 += ",MinTime";
7186 config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT;
7187 break;
7188 default:
7189 assert(0);
7190 }
7191
7192 desc6 += ',';
7193 desc6 += FREE_ORDER_NAMES[(uint32_t)config.FreeOrder];
7194
7195 const char* testDescription = desc6.c_str();
7196
7197 for(size_t repeat = 0; repeat < repeatCount; ++repeat)
7198 {
7199 printf("%s #%u\n", testDescription, (uint32_t)repeat);
7200
7201 Result result{};
7202 VkResult res = MainTest(result, config);
7203 TEST(res == VK_SUCCESS);
7204 if(file)
7205 {
7206 WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
7207 }
7208 }
7209 }
7210 }
7211 }
7212 }
7213 }
7214 }
7215 }
7216
PerformPoolTests(FILE * file)7217 static void PerformPoolTests(FILE* file)
7218 {
7219 wprintf(L"POOL TESTS:\n");
7220
7221 const size_t AVG_RESOURCES_PER_POOL = 300;
7222
7223 uint32_t repeatCount = 1;
7224 if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3;
7225
7226 PoolTestConfig config{};
7227 config.RandSeed = 2346343;
7228 config.FrameCount = 200;
7229 config.ItemsToMakeUnusedPercent = 2;
7230
7231 size_t threadCountCount = 1;
7232 switch(ConfigType)
7233 {
7234 case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break;
7235 case CONFIG_TYPE_SMALL: threadCountCount = 2; break;
7236 case CONFIG_TYPE_AVERAGE: threadCountCount = 2; break;
7237 case CONFIG_TYPE_LARGE: threadCountCount = 3; break;
7238 case CONFIG_TYPE_MAXIMUM: threadCountCount = 3; break;
7239 default: assert(0);
7240 }
7241 for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex)
7242 {
7243 std::string desc1;
7244
7245 switch(threadCountIndex)
7246 {
7247 case 0:
7248 desc1 += "1_thread";
7249 config.ThreadCount = 1;
7250 break;
7251 case 1:
7252 desc1 += "16_threads";
7253 config.ThreadCount = 16;
7254 break;
7255 case 2:
7256 desc1 += "2_threads";
7257 config.ThreadCount = 2;
7258 break;
7259 default:
7260 assert(0);
7261 }
7262
7263 // 0 = buffers, 1 = images, 2 = buffers and images
7264 size_t buffersVsImagesCount = 2;
7265 if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount;
7266 for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex)
7267 {
7268 std::string desc2 = desc1;
7269 switch(buffersVsImagesIndex)
7270 {
7271 case 0: desc2 += " Buffers"; break;
7272 case 1: desc2 += " Images"; break;
7273 case 2: desc2 += " Buffers+Images"; break;
7274 default: assert(0);
7275 }
7276
7277 // 0 = small, 1 = large, 2 = small and large
7278 size_t smallVsLargeCount = 2;
7279 if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount;
7280 for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex)
7281 {
7282 std::string desc3 = desc2;
7283 switch(smallVsLargeIndex)
7284 {
7285 case 0: desc3 += " Small"; break;
7286 case 1: desc3 += " Large"; break;
7287 case 2: desc3 += " Small+Large"; break;
7288 default: assert(0);
7289 }
7290
7291 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
7292 config.PoolSize = 6ull * 1024 * 1024 * 1024; // 6 GB
7293 else
7294 config.PoolSize = 4ull * 1024 * 1024;
7295
7296 // 0 = varying sizes min...max, 1 = set of constant sizes
7297 size_t constantSizesCount = 1;
7298 if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount;
7299 for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex)
7300 {
7301 std::string desc4 = desc3;
7302 switch(constantSizesIndex)
7303 {
7304 case 0: desc4 += " Varying_sizes"; break;
7305 case 1: desc4 += " Constant_sizes"; break;
7306 default: assert(0);
7307 }
7308
7309 config.AllocationSizes.clear();
7310 // Buffers present
7311 if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
7312 {
7313 // Small
7314 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
7315 {
7316 // Varying size
7317 if(constantSizesIndex == 0)
7318 config.AllocationSizes.push_back({4, 16, 1024});
7319 // Constant sizes
7320 else
7321 {
7322 config.AllocationSizes.push_back({1, 16, 16});
7323 config.AllocationSizes.push_back({1, 64, 64});
7324 config.AllocationSizes.push_back({1, 256, 256});
7325 config.AllocationSizes.push_back({1, 1024, 1024});
7326 }
7327 }
7328 // Large
7329 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
7330 {
7331 // Varying size
7332 if(constantSizesIndex == 0)
7333 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
7334 // Constant sizes
7335 else
7336 {
7337 config.AllocationSizes.push_back({1, 0x10000, 0x10000});
7338 config.AllocationSizes.push_back({1, 0x80000, 0x80000});
7339 config.AllocationSizes.push_back({1, 0x200000, 0x200000});
7340 config.AllocationSizes.push_back({1, 0xA00000, 0xA00000});
7341 }
7342 }
7343 }
7344 // Images present
7345 if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
7346 {
7347 // Small
7348 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
7349 {
7350 // Varying size
7351 if(constantSizesIndex == 0)
7352 config.AllocationSizes.push_back({4, 0, 0, 4, 32});
7353 // Constant sizes
7354 else
7355 {
7356 config.AllocationSizes.push_back({1, 0, 0, 4, 4});
7357 config.AllocationSizes.push_back({1, 0, 0, 8, 8});
7358 config.AllocationSizes.push_back({1, 0, 0, 16, 16});
7359 config.AllocationSizes.push_back({1, 0, 0, 32, 32});
7360 }
7361 }
7362 // Large
7363 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
7364 {
7365 // Varying size
7366 if(constantSizesIndex == 0)
7367 config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
7368 // Constant sizes
7369 else
7370 {
7371 config.AllocationSizes.push_back({1, 0, 0, 256, 256});
7372 config.AllocationSizes.push_back({1, 0, 0, 512, 512});
7373 config.AllocationSizes.push_back({1, 0, 0, 1024, 1024});
7374 config.AllocationSizes.push_back({1, 0, 0, 2048, 2048});
7375 }
7376 }
7377 }
7378
7379 const VkDeviceSize avgResourceSize = config.CalcAvgResourceSize();
7380 config.PoolSize = avgResourceSize * AVG_RESOURCES_PER_POOL;
7381
7382 // 0 = 66%, 1 = 133%, 2 = 100%, 3 = 33%, 4 = 166%
7383 size_t subscriptionModeCount;
7384 switch(ConfigType)
7385 {
7386 case CONFIG_TYPE_MINIMUM: subscriptionModeCount = 2; break;
7387 case CONFIG_TYPE_SMALL: subscriptionModeCount = 2; break;
7388 case CONFIG_TYPE_AVERAGE: subscriptionModeCount = 3; break;
7389 case CONFIG_TYPE_LARGE: subscriptionModeCount = 5; break;
7390 case CONFIG_TYPE_MAXIMUM: subscriptionModeCount = 5; break;
7391 default: assert(0);
7392 }
7393 for(size_t subscriptionModeIndex = 0; subscriptionModeIndex < subscriptionModeCount; ++subscriptionModeIndex)
7394 {
7395 std::string desc5 = desc4;
7396
7397 switch(subscriptionModeIndex)
7398 {
7399 case 0:
7400 desc5 += " Subscription_66%";
7401 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 66 / 100;
7402 break;
7403 case 1:
7404 desc5 += " Subscription_133%";
7405 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 133 / 100;
7406 break;
7407 case 2:
7408 desc5 += " Subscription_100%";
7409 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL;
7410 break;
7411 case 3:
7412 desc5 += " Subscription_33%";
7413 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 33 / 100;
7414 break;
7415 case 4:
7416 desc5 += " Subscription_166%";
7417 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 166 / 100;
7418 break;
7419 default:
7420 assert(0);
7421 }
7422
7423 config.TotalItemCount = config.UsedItemCountMax * 5;
7424 config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100;
7425
7426 const char* testDescription = desc5.c_str();
7427
7428 for(size_t repeat = 0; repeat < repeatCount; ++repeat)
7429 {
7430 printf("%s #%u\n", testDescription, (uint32_t)repeat);
7431
7432 PoolTestResult result{};
7433 TestPool_Benchmark(result, config);
7434 WritePoolTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
7435 }
7436 }
7437 }
7438 }
7439 }
7440 }
7441 }
7442
BasicTestTLSF()7443 static void BasicTestTLSF()
7444 {
7445 wprintf(L"Basic test TLSF\n");
7446
7447 VmaVirtualBlock block;
7448
7449 VmaVirtualBlockCreateInfo blockInfo = {};
7450 blockInfo.flags = 0;
7451 blockInfo.size = 50331648;
7452 vmaCreateVirtualBlock(&blockInfo, &block);
7453
7454 VmaVirtualAllocationCreateInfo info = {};
7455 info.alignment = 2;
7456
7457 VmaVirtualAllocation allocation[3] = {};
7458
7459 info.size = 576;
7460 vmaVirtualAllocate(block, &info, allocation + 0, nullptr);
7461
7462 info.size = 648;
7463 vmaVirtualAllocate(block, &info, allocation + 1, nullptr);
7464
7465 vmaVirtualFree(block, allocation[0]);
7466
7467 info.size = 720;
7468 vmaVirtualAllocate(block, &info, allocation + 2, nullptr);
7469
7470 vmaVirtualFree(block, allocation[1]);
7471 vmaVirtualFree(block, allocation[2]);
7472 vmaDestroyVirtualBlock(block);
7473 }
7474
7475 #if 0
7476 static void BasicTestBuddyAllocator()
7477 {
7478 wprintf(L"Basic test buddy allocator\n");
7479
7480 RandomNumberGenerator rand{76543};
7481
7482 VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
7483 sampleBufCreateInfo.size = 1024; // Whatever.
7484 sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
7485
7486 VmaAllocationCreateInfo sampleAllocCreateInfo = {};
7487
7488 VmaPoolCreateInfo poolCreateInfo = {};
7489 VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
7490 TEST(res == VK_SUCCESS);
7491
7492 // Deliberately adding 1023 to test usable size smaller than memory block size.
7493 poolCreateInfo.blockSize = 1024 * 1024 + 1023;
7494 poolCreateInfo.flags = VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT;
7495 //poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
7496
7497 VmaPool pool = nullptr;
7498 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
7499 TEST(res == VK_SUCCESS);
7500
7501 VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo;
7502
7503 VmaAllocationCreateInfo allocCreateInfo = {};
7504 allocCreateInfo.pool = pool;
7505
7506 std::vector<BufferInfo> bufInfo;
7507 BufferInfo newBufInfo;
7508 VmaAllocationInfo allocInfo;
7509
7510 bufCreateInfo.size = 1024 * 256;
7511 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
7512 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
7513 TEST(res == VK_SUCCESS);
7514 bufInfo.push_back(newBufInfo);
7515
7516 bufCreateInfo.size = 1024 * 512;
7517 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
7518 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
7519 TEST(res == VK_SUCCESS);
7520 bufInfo.push_back(newBufInfo);
7521
7522 bufCreateInfo.size = 1024 * 128;
7523 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
7524 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
7525 TEST(res == VK_SUCCESS);
7526 bufInfo.push_back(newBufInfo);
7527
7528 // Test very small allocation, smaller than minimum node size.
7529 bufCreateInfo.size = 1;
7530 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
7531 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
7532 TEST(res == VK_SUCCESS);
7533 bufInfo.push_back(newBufInfo);
7534
7535 // Test some small allocation with alignment requirement.
7536 {
7537 VkMemoryRequirements memReq;
7538 memReq.alignment = 256;
7539 memReq.memoryTypeBits = UINT32_MAX;
7540 memReq.size = 32;
7541
7542 newBufInfo.Buffer = VK_NULL_HANDLE;
7543 res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo,
7544 &newBufInfo.Allocation, &allocInfo);
7545 TEST(res == VK_SUCCESS);
7546 TEST(allocInfo.offset % memReq.alignment == 0);
7547 bufInfo.push_back(newBufInfo);
7548 }
7549
7550 //SaveAllocatorStatsToFile(L"TEST.json");
7551
7552 VmaDetailedStatistics stats = {};
7553 vmaCalculatePoolStatistics(g_hAllocator, pool, &stats);
7554 int DBG = 0; // Set breakpoint here to inspect `stats`.
7555
7556 // Allocate enough new buffers to surely fall into second block.
7557 for(uint32_t i = 0; i < 32; ++i)
7558 {
7559 bufCreateInfo.size = 1024 * (rand.Generate() % 32 + 1);
7560 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
7561 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
7562 TEST(res == VK_SUCCESS);
7563 bufInfo.push_back(newBufInfo);
7564 }
7565
7566 SaveAllocatorStatsToFile(L"BuddyTest01.json");
7567
7568 // Destroy the buffers in random order.
7569 while(!bufInfo.empty())
7570 {
7571 const size_t indexToDestroy = rand.Generate() % bufInfo.size();
7572 const BufferInfo& currBufInfo = bufInfo[indexToDestroy];
7573 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
7574 bufInfo.erase(bufInfo.begin() + indexToDestroy);
7575 }
7576
7577 vmaDestroyPool(g_hAllocator, pool);
7578 }
7579 #endif // #if 0
7580
BasicTestAllocatePages()7581 static void BasicTestAllocatePages()
7582 {
7583 wprintf(L"Basic test allocate pages\n");
7584
7585 RandomNumberGenerator rand{765461};
7586
7587 VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
7588 sampleBufCreateInfo.size = 1024; // Whatever.
7589 sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
7590
7591 VmaAllocationCreateInfo sampleAllocCreateInfo = {};
7592 sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
7593 sampleAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
7594
7595 VmaPoolCreateInfo poolCreateInfo = {};
7596 VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
7597 TEST(res == VK_SUCCESS);
7598
7599 // 1 block of 1 MB.
7600 poolCreateInfo.blockSize = 1024 * 1024;
7601 poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
7602
7603 // Create pool.
7604 VmaPool pool = nullptr;
7605 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
7606 TEST(res == VK_SUCCESS);
7607
7608 // Make 100 allocations of 4 KB - they should fit into the pool.
7609 VkMemoryRequirements memReq;
7610 memReq.memoryTypeBits = UINT32_MAX;
7611 memReq.alignment = 4 * 1024;
7612 memReq.size = 4 * 1024;
7613
7614 VmaAllocationCreateInfo allocCreateInfo = {};
7615 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
7616 allocCreateInfo.pool = pool;
7617
7618 constexpr uint32_t allocCount = 100;
7619
7620 std::vector<VmaAllocation> alloc{allocCount};
7621 std::vector<VmaAllocationInfo> allocInfo{allocCount};
7622 res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), allocInfo.data());
7623 TEST(res == VK_SUCCESS);
7624 for(uint32_t i = 0; i < allocCount; ++i)
7625 {
7626 TEST(alloc[i] != VK_NULL_HANDLE &&
7627 allocInfo[i].pMappedData != nullptr &&
7628 allocInfo[i].deviceMemory == allocInfo[0].deviceMemory &&
7629 allocInfo[i].memoryType == allocInfo[0].memoryType);
7630 }
7631
7632 // Free the allocations.
7633 vmaFreeMemoryPages(g_hAllocator, allocCount, alloc.data());
7634 std::fill(alloc.begin(), alloc.end(), nullptr);
7635 std::fill(allocInfo.begin(), allocInfo.end(), VmaAllocationInfo{});
7636
7637 // Try to make 100 allocations of 100 KB. This call should fail due to not enough memory.
7638 // Also test optional allocationInfo = null.
7639 memReq.size = 100 * 1024;
7640 res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), nullptr);
7641 TEST(res != VK_SUCCESS);
7642 TEST(std::find_if(alloc.begin(), alloc.end(), [](VmaAllocation alloc){ return alloc != VK_NULL_HANDLE; }) == alloc.end());
7643
7644 // Make 100 allocations of 4 KB, but with required alignment of 128 KB. This should also fail.
7645 memReq.size = 4 * 1024;
7646 memReq.alignment = 128 * 1024;
7647 res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), allocInfo.data());
7648 TEST(res != VK_SUCCESS);
7649
7650 // Make 100 dedicated allocations of 4 KB.
7651 memReq.alignment = 4 * 1024;
7652 memReq.size = 4 * 1024;
7653
7654 VmaAllocationCreateInfo dedicatedAllocCreateInfo = {};
7655 dedicatedAllocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
7656 dedicatedAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
7657 res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &dedicatedAllocCreateInfo, allocCount, alloc.data(), allocInfo.data());
7658 TEST(res == VK_SUCCESS);
7659 for(uint32_t i = 0; i < allocCount; ++i)
7660 {
7661 TEST(alloc[i] != VK_NULL_HANDLE &&
7662 allocInfo[i].pMappedData != nullptr &&
7663 allocInfo[i].memoryType == allocInfo[0].memoryType &&
7664 allocInfo[i].offset == 0);
7665 if(i > 0)
7666 {
7667 TEST(allocInfo[i].deviceMemory != allocInfo[0].deviceMemory);
7668 }
7669 }
7670
7671 // Free the allocations.
7672 vmaFreeMemoryPages(g_hAllocator, allocCount, alloc.data());
7673 std::fill(alloc.begin(), alloc.end(), nullptr);
7674 std::fill(allocInfo.begin(), allocInfo.end(), VmaAllocationInfo{});
7675
7676 vmaDestroyPool(g_hAllocator, pool);
7677 }
7678
7679 // Test the testing environment.
TestGpuData()7680 static void TestGpuData()
7681 {
7682 RandomNumberGenerator rand = { 53434 };
7683
7684 std::vector<AllocInfo> allocInfo;
7685
7686 for(size_t i = 0; i < 100; ++i)
7687 {
7688 AllocInfo info = {};
7689
7690 info.m_BufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
7691 info.m_BufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT |
7692 VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
7693 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
7694 info.m_BufferInfo.size = 1024 * 1024 * (rand.Generate() % 9 + 1);
7695
7696 VmaAllocationCreateInfo allocCreateInfo = {};
7697 allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
7698
7699 VkResult res = vmaCreateBuffer(g_hAllocator, &info.m_BufferInfo, &allocCreateInfo, &info.m_Buffer, &info.m_Allocation, nullptr);
7700 TEST(res == VK_SUCCESS);
7701
7702 info.m_StartValue = rand.Generate();
7703
7704 allocInfo.push_back(std::move(info));
7705 }
7706
7707 UploadGpuData(allocInfo.data(), allocInfo.size());
7708
7709 ValidateGpuData(allocInfo.data(), allocInfo.size());
7710
7711 DestroyAllAllocations(allocInfo);
7712 }
7713
TestVirtualBlocksAlgorithmsBenchmark()7714 static void TestVirtualBlocksAlgorithmsBenchmark()
7715 {
7716 wprintf(L"Benchmark virtual blocks algorithms\n");
7717 wprintf(L"Alignment,Algorithm,Strategy,Alloc time ms,Random operation time ms,Free time ms\n");
7718
7719 const size_t ALLOCATION_COUNT = 7200;
7720 const uint32_t MAX_ALLOC_SIZE = 2056;
7721 const size_t RANDOM_OPERATION_COUNT = ALLOCATION_COUNT * 2;
7722
7723 VmaVirtualBlockCreateInfo blockCreateInfo = {};
7724 blockCreateInfo.pAllocationCallbacks = g_Allocs;
7725 blockCreateInfo.size = 0;
7726
7727 RandomNumberGenerator rand{ 20092010 };
7728
7729 uint32_t allocSizes[ALLOCATION_COUNT];
7730 for (size_t i = 0; i < ALLOCATION_COUNT; ++i)
7731 {
7732 allocSizes[i] = rand.Generate() % MAX_ALLOC_SIZE + 1;
7733 blockCreateInfo.size += allocSizes[i];
7734 }
7735 blockCreateInfo.size = static_cast<VkDeviceSize>(blockCreateInfo.size * 2.5); // 150% size margin in case of buddy fragmentation
7736
7737 for (uint8_t alignmentIndex = 0; alignmentIndex < 4; ++alignmentIndex)
7738 {
7739 VkDeviceSize alignment;
7740 switch (alignmentIndex)
7741 {
7742 case 0: alignment = 1; break;
7743 case 1: alignment = 16; break;
7744 case 2: alignment = 64; break;
7745 case 3: alignment = 256; break;
7746 default: assert(0); break;
7747 }
7748
7749 for (uint8_t allocStrategyIndex = 0; allocStrategyIndex < 3; ++allocStrategyIndex)
7750 {
7751 VmaVirtualAllocationCreateFlags allocFlags;
7752 switch (allocStrategyIndex)
7753 {
7754 case 0: allocFlags = 0; break;
7755 case 1: allocFlags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT; break;
7756 case 2: allocFlags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; break;
7757 default: assert(0);
7758 }
7759
7760 for (uint8_t algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex)
7761 {
7762 switch (algorithmIndex)
7763 {
7764 case 0:
7765 blockCreateInfo.flags = (VmaVirtualBlockCreateFlagBits)0;
7766 break;
7767 case 1:
7768 blockCreateInfo.flags = VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT;
7769 break;
7770 default:
7771 assert(0);
7772 }
7773
7774 std::vector<VmaVirtualAllocation> allocs;
7775 allocs.reserve(ALLOCATION_COUNT + RANDOM_OPERATION_COUNT);
7776 allocs.resize(ALLOCATION_COUNT);
7777 VmaVirtualBlock block;
7778 TEST(vmaCreateVirtualBlock(&blockCreateInfo, &block) == VK_SUCCESS && block);
7779
7780 // Alloc
7781 time_point timeBegin = std::chrono::high_resolution_clock::now();
7782 for (size_t i = 0; i < ALLOCATION_COUNT; ++i)
7783 {
7784 VmaVirtualAllocationCreateInfo allocCreateInfo = {};
7785 allocCreateInfo.size = allocSizes[i];
7786 allocCreateInfo.alignment = alignment;
7787 allocCreateInfo.flags = allocFlags;
7788
7789 TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocs[i], nullptr) == VK_SUCCESS);
7790 TEST(allocs[i] != VK_NULL_HANDLE);
7791 }
7792 duration allocDuration = std::chrono::high_resolution_clock::now() - timeBegin;
7793
7794 // Random operations
7795 timeBegin = std::chrono::high_resolution_clock::now();
7796 for (size_t opIndex = 0; opIndex < RANDOM_OPERATION_COUNT; ++opIndex)
7797 {
7798 if(rand.Generate() % 2)
7799 {
7800 VmaVirtualAllocationCreateInfo allocCreateInfo = {};
7801 allocCreateInfo.size = rand.Generate() % MAX_ALLOC_SIZE + 1;
7802 allocCreateInfo.alignment = alignment;
7803 allocCreateInfo.flags = allocFlags;
7804
7805 VmaVirtualAllocation alloc;
7806 TEST(vmaVirtualAllocate(block, &allocCreateInfo, &alloc, nullptr) == VK_SUCCESS);
7807 TEST(alloc != VK_NULL_HANDLE);
7808 allocs.push_back(alloc);
7809 }
7810 else
7811 {
7812 size_t index = rand.Generate() % allocs.size();
7813 vmaVirtualFree(block, allocs[index]);
7814 if(index < allocs.size())
7815 allocs[index] = allocs.back();
7816 allocs.pop_back();
7817 }
7818 }
7819 duration randomDuration = std::chrono::high_resolution_clock::now() - timeBegin;
7820
7821 // Free
7822 timeBegin = std::chrono::high_resolution_clock::now();
7823 for (size_t i = ALLOCATION_COUNT; i;)
7824 vmaVirtualFree(block, allocs[--i]);
7825 duration freeDuration = std::chrono::high_resolution_clock::now() - timeBegin;
7826
7827 vmaDestroyVirtualBlock(block);
7828
7829 printf("%llu,%s,%s,%g,%g,%g\n",
7830 alignment,
7831 VirtualAlgorithmToStr(blockCreateInfo.flags),
7832 GetVirtualAllocationStrategyName(allocFlags),
7833 ToFloatSeconds(allocDuration) * 1000.f,
7834 ToFloatSeconds(randomDuration) * 1000.f,
7835 ToFloatSeconds(freeDuration) * 1000.f);
7836 }
7837 }
7838 }
7839 }
7840
TestMappingHysteresis()7841 static void TestMappingHysteresis()
7842 {
7843 /*
7844 We have no way to check here if hysteresis worked as expected,
7845 but at least we provoke some cases and make sure it doesn't crash or assert.
7846 You can always check details with the debugger.
7847 */
7848
7849 wprintf(L"Test mapping hysteresis\n");
7850
7851 VkBufferCreateInfo bufCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
7852 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
7853 bufCreateInfo.size = 0x10000;
7854
7855 VmaAllocationCreateInfo templateAllocCreateInfo = {};
7856 templateAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
7857 templateAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
7858
7859 VmaPoolCreateInfo poolCreateInfo = {};
7860 poolCreateInfo.blockSize = 10 * MEGABYTE;
7861 poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
7862 TEST(vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator,
7863 &bufCreateInfo, &templateAllocCreateInfo, &poolCreateInfo.memoryTypeIndex) == VK_SUCCESS);
7864
7865 constexpr uint32_t BUF_COUNT = 30;
7866 bool endOfScenarios = false;
7867 for(uint32_t scenarioIndex = 0; !endOfScenarios; ++scenarioIndex)
7868 {
7869 VmaPool pool;
7870 TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
7871
7872 BufferInfo buf;
7873 VmaAllocationInfo allocInfo;
7874
7875 std::vector<BufferInfo> bufs;
7876
7877 // Scenario: Create + destroy buffers without mapping. Hysteresis should not launch.
7878 if(scenarioIndex == 0)
7879 {
7880 VmaAllocationCreateInfo allocCreateInfo = {};
7881 allocCreateInfo.pool = pool;
7882
7883 for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex)
7884 {
7885 TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
7886 TEST(allocInfo.pMappedData == nullptr);
7887 vmaDestroyBuffer(g_hAllocator, buf.Buffer, buf.Allocation);
7888 }
7889 }
7890 // Scenario:
7891 // - Create one buffer mapped that stays there.
7892 // - Create + destroy mapped buffers back and forth. Hysteresis should launch.
7893 else if(scenarioIndex == 1)
7894 {
7895 VmaAllocationCreateInfo allocCreateInfo = {};
7896 allocCreateInfo.pool = pool;
7897 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
7898
7899 TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
7900 TEST(allocInfo.pMappedData != nullptr);
7901 bufs.push_back(buf);
7902
7903 for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex)
7904 {
7905 TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
7906 TEST(allocInfo.pMappedData != nullptr);
7907 vmaDestroyBuffer(g_hAllocator, buf.Buffer, buf.Allocation);
7908 }
7909 }
7910 // Scenario: Create + destroy mapped buffers.
7911 // Hysteresis should launch as it maps and unmaps back and forth.
7912 else if(scenarioIndex == 2)
7913 {
7914 VmaAllocationCreateInfo allocCreateInfo = {};
7915 allocCreateInfo.pool = pool;
7916 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
7917
7918 for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex)
7919 {
7920 TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
7921 TEST(allocInfo.pMappedData != nullptr);
7922 vmaDestroyBuffer(g_hAllocator, buf.Buffer, buf.Allocation);
7923 }
7924 }
7925 // Scenario: Create one buffer and map it back and forth. Hysteresis should launch.
7926 else if(scenarioIndex == 3)
7927 {
7928 VmaAllocationCreateInfo allocCreateInfo = {};
7929 allocCreateInfo.pool = pool;
7930
7931 TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
7932
7933 for(uint32_t i = 0; i < BUF_COUNT; ++i)
7934 {
7935 void* mappedData = nullptr;
7936 TEST(vmaMapMemory(g_hAllocator, buf.Allocation, &mappedData) == VK_SUCCESS);
7937 TEST(mappedData != nullptr);
7938 vmaUnmapMemory(g_hAllocator, buf.Allocation);
7939 }
7940
7941 vmaDestroyBuffer(g_hAllocator, buf.Buffer, buf.Allocation);
7942 }
7943 // Scenario:
7944 // - Create many buffers
7945 // - Map + unmap one of them many times. Hysteresis should launch.
7946 // - Hysteresis should unmap during freeing the buffers.
7947 else if(scenarioIndex == 4)
7948 {
7949 VmaAllocationCreateInfo allocCreateInfo = {};
7950 allocCreateInfo.pool = pool;
7951
7952 for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex)
7953 {
7954 TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
7955 TEST(allocInfo.pMappedData == nullptr);
7956 bufs.push_back(buf);
7957 }
7958
7959 for(uint32_t i = 0; i < BUF_COUNT; ++i)
7960 {
7961 void* mappedData = nullptr;
7962 TEST(vmaMapMemory(g_hAllocator, buf.Allocation, &mappedData) == VK_SUCCESS);
7963 TEST(mappedData != nullptr);
7964 vmaUnmapMemory(g_hAllocator, buf.Allocation);
7965 }
7966 }
7967 else
7968 endOfScenarios = true;
7969
7970 for(size_t i = bufs.size(); i--; )
7971 vmaDestroyBuffer(g_hAllocator, bufs[i].Buffer, bufs[i].Allocation);
7972
7973 vmaDestroyPool(g_hAllocator, pool);
7974 }
7975 }
7976
Test()7977 void Test()
7978 {
7979 wprintf(L"TESTING:\n");
7980
7981 if(false)
7982 {
7983 ////////////////////////////////////////////////////////////////////////////////
7984 // Temporarily insert custom tests here:
7985 return;
7986 }
7987
7988 // # Simple tests
7989
7990 #if VMA_DEBUG_MARGIN
7991 TestDebugMargin();
7992 TestDebugMarginNotInVirtualAllocator();
7993 #else
7994 TestJson();
7995 TestBasics();
7996 TestVirtualBlocks();
7997 TestVirtualBlocksAlgorithms();
7998 TestVirtualBlocksAlgorithmsBenchmark();
7999 TestAllocationVersusResourceSize();
8000 //TestGpuData(); // Not calling this because it's just testing the testing environment.
8001 TestPool_SameSize();
8002 TestPool_MinBlockCount();
8003 TestPool_MinAllocationAlignment();
8004 TestPoolsAndAllocationParameters();
8005 TestHeapSizeLimit();
8006 #if VMA_DEBUG_INITIALIZE_ALLOCATIONS
8007 TestAllocationsInitialization();
8008 #endif
8009 TestMemoryUsage();
8010 TestDeviceCoherentMemory();
8011 TestBudget();
8012 TestAliasing();
8013 TestAllocationAliasing();
8014 TestMapping();
8015 TestMappingHysteresis();
8016 TestDeviceLocalMapped();
8017 TestMappingMultithreaded();
8018 TestLinearAllocator();
8019 ManuallyTestLinearAllocator();
8020 TestLinearAllocatorMultiBlock();
8021 TestAllocationAlgorithmsCorrectness();
8022
8023 BasicTestTLSF();
8024 BasicTestAllocatePages();
8025
8026 if (VK_KHR_buffer_device_address_enabled)
8027 TestBufferDeviceAddress();
8028 if (VK_EXT_memory_priority_enabled)
8029 TestMemoryPriority();
8030
8031 {
8032 FILE* file;
8033 fopen_s(&file, "Algorithms.csv", "w");
8034 assert(file != NULL);
8035 BenchmarkAlgorithms(file);
8036 fclose(file);
8037 }
8038
8039 TestDefragmentationSimple();
8040 TestDefragmentationVsMapping();
8041 if (ConfigType >= CONFIG_TYPE_AVERAGE)
8042 {
8043 TestDefragmentationAlgorithms();
8044 TestDefragmentationFull();
8045 TestDefragmentationGpu();
8046 TestDefragmentationIncrementalBasic();
8047 TestDefragmentationIncrementalComplex();
8048 }
8049
8050 // # Detailed tests
8051 FILE* file;
8052 fopen_s(&file, "Results.csv", "w");
8053 assert(file != NULL);
8054
8055 WriteMainTestResultHeader(file);
8056 PerformMainTests(file);
8057 PerformCustomMainTest(file);
8058
8059 WritePoolTestResultHeader(file);
8060 PerformPoolTests(file);
8061 PerformCustomPoolTest(file);
8062
8063 fclose(file);
8064 #endif // #if defined(VMA_DEBUG_MARGIN) && VMA_DEBUG_MARGIN > 0
8065
8066 wprintf(L"Done, all PASSED.\n");
8067 }
8068
8069 #endif // #ifdef _WIN32
8070