xref: /aosp_15_r20/external/angle/third_party/vulkan_memory_allocator/src/Tests.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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, &copy);
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, &copy);
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, &copy);
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, &region);
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