blob: 344acbfcdf1e41947a36cc740ef8bfdd9583ded6 [file] [log] [blame]
Adam Sawickif1a793c2018-03-13 15:42:22 +01001#include "Tests.h"
2#include "VmaUsage.h"
3#include "Common.h"
Adam Sawickib8333fb2018-03-13 16:15:53 +01004#include <atomic>
5#include <thread>
6#include <mutex>
Adam Sawickif1a793c2018-03-13 15:42:22 +01007
8#ifdef _WIN32
9
Adam Sawickib8333fb2018-03-13 16:15:53 +010010enum class FREE_ORDER { FORWARD, BACKWARD, RANDOM, COUNT };
11
12struct AllocationSize
13{
14 uint32_t Probability;
15 VkDeviceSize BufferSizeMin, BufferSizeMax;
16 uint32_t ImageSizeMin, ImageSizeMax;
17};
18
19struct Config
20{
21 uint32_t RandSeed;
22 VkDeviceSize BeginBytesToAllocate;
23 uint32_t AdditionalOperationCount;
24 VkDeviceSize MaxBytesToAllocate;
25 uint32_t MemUsageProbability[4]; // For VMA_MEMORY_USAGE_*
26 std::vector<AllocationSize> AllocationSizes;
27 uint32_t ThreadCount;
28 uint32_t ThreadsUsingCommonAllocationsProbabilityPercent;
29 FREE_ORDER FreeOrder;
30};
31
32struct Result
33{
34 duration TotalTime;
35 duration AllocationTimeMin, AllocationTimeAvg, AllocationTimeMax;
36 duration DeallocationTimeMin, DeallocationTimeAvg, DeallocationTimeMax;
37 VkDeviceSize TotalMemoryAllocated;
38 VkDeviceSize FreeRangeSizeAvg, FreeRangeSizeMax;
39};
40
41void TestDefragmentationSimple();
42void TestDefragmentationFull();
43
44struct PoolTestConfig
45{
46 uint32_t RandSeed;
47 uint32_t ThreadCount;
48 VkDeviceSize PoolSize;
49 uint32_t FrameCount;
50 uint32_t TotalItemCount;
51 // Range for number of items used in each frame.
52 uint32_t UsedItemCountMin, UsedItemCountMax;
53 // Percent of items to make unused, and possibly make some others used in each frame.
54 uint32_t ItemsToMakeUnusedPercent;
55 std::vector<AllocationSize> AllocationSizes;
56
57 VkDeviceSize CalcAvgResourceSize() const
58 {
59 uint32_t probabilitySum = 0;
60 VkDeviceSize sizeSum = 0;
61 for(size_t i = 0; i < AllocationSizes.size(); ++i)
62 {
63 const AllocationSize& allocSize = AllocationSizes[i];
64 if(allocSize.BufferSizeMax > 0)
65 sizeSum += (allocSize.BufferSizeMin + allocSize.BufferSizeMax) / 2 * allocSize.Probability;
66 else
67 {
68 const VkDeviceSize avgDimension = (allocSize.ImageSizeMin + allocSize.ImageSizeMax) / 2;
69 sizeSum += avgDimension * avgDimension * 4 * allocSize.Probability;
70 }
71 probabilitySum += allocSize.Probability;
72 }
73 return sizeSum / probabilitySum;
74 }
75
76 bool UsesBuffers() const
77 {
78 for(size_t i = 0; i < AllocationSizes.size(); ++i)
79 if(AllocationSizes[i].BufferSizeMax > 0)
80 return true;
81 return false;
82 }
83
84 bool UsesImages() const
85 {
86 for(size_t i = 0; i < AllocationSizes.size(); ++i)
87 if(AllocationSizes[i].ImageSizeMax > 0)
88 return true;
89 return false;
90 }
91};
92
93struct PoolTestResult
94{
95 duration TotalTime;
96 duration AllocationTimeMin, AllocationTimeAvg, AllocationTimeMax;
97 duration DeallocationTimeMin, DeallocationTimeAvg, DeallocationTimeMax;
98 size_t LostAllocationCount, LostAllocationTotalSize;
99 size_t FailedAllocationCount, FailedAllocationTotalSize;
100};
101
102static const uint32_t IMAGE_BYTES_PER_PIXEL = 1;
103
104struct BufferInfo
105{
106 VkBuffer Buffer = VK_NULL_HANDLE;
107 VmaAllocation Allocation = VK_NULL_HANDLE;
108};
109
110static void InitResult(Result& outResult)
111{
112 outResult.TotalTime = duration::zero();
113 outResult.AllocationTimeMin = duration::max();
114 outResult.AllocationTimeAvg = duration::zero();
115 outResult.AllocationTimeMax = duration::min();
116 outResult.DeallocationTimeMin = duration::max();
117 outResult.DeallocationTimeAvg = duration::zero();
118 outResult.DeallocationTimeMax = duration::min();
119 outResult.TotalMemoryAllocated = 0;
120 outResult.FreeRangeSizeAvg = 0;
121 outResult.FreeRangeSizeMax = 0;
122}
123
124class TimeRegisterObj
125{
126public:
127 TimeRegisterObj(duration& min, duration& sum, duration& max) :
128 m_Min(min),
129 m_Sum(sum),
130 m_Max(max),
131 m_TimeBeg(std::chrono::high_resolution_clock::now())
132 {
133 }
134
135 ~TimeRegisterObj()
136 {
137 duration d = std::chrono::high_resolution_clock::now() - m_TimeBeg;
138 m_Sum += d;
139 if(d < m_Min) m_Min = d;
140 if(d > m_Max) m_Max = d;
141 }
142
143private:
144 duration& m_Min;
145 duration& m_Sum;
146 duration& m_Max;
147 time_point m_TimeBeg;
148};
149
150struct PoolTestThreadResult
151{
152 duration AllocationTimeMin, AllocationTimeSum, AllocationTimeMax;
153 duration DeallocationTimeMin, DeallocationTimeSum, DeallocationTimeMax;
154 size_t AllocationCount, DeallocationCount;
155 size_t LostAllocationCount, LostAllocationTotalSize;
156 size_t FailedAllocationCount, FailedAllocationTotalSize;
157};
158
159class AllocationTimeRegisterObj : public TimeRegisterObj
160{
161public:
162 AllocationTimeRegisterObj(Result& result) :
163 TimeRegisterObj(result.AllocationTimeMin, result.AllocationTimeAvg, result.AllocationTimeMax)
164 {
165 }
166};
167
168class DeallocationTimeRegisterObj : public TimeRegisterObj
169{
170public:
171 DeallocationTimeRegisterObj(Result& result) :
172 TimeRegisterObj(result.DeallocationTimeMin, result.DeallocationTimeAvg, result.DeallocationTimeMax)
173 {
174 }
175};
176
177class PoolAllocationTimeRegisterObj : public TimeRegisterObj
178{
179public:
180 PoolAllocationTimeRegisterObj(PoolTestThreadResult& result) :
181 TimeRegisterObj(result.AllocationTimeMin, result.AllocationTimeSum, result.AllocationTimeMax)
182 {
183 }
184};
185
186class PoolDeallocationTimeRegisterObj : public TimeRegisterObj
187{
188public:
189 PoolDeallocationTimeRegisterObj(PoolTestThreadResult& result) :
190 TimeRegisterObj(result.DeallocationTimeMin, result.DeallocationTimeSum, result.DeallocationTimeMax)
191 {
192 }
193};
194
195VkResult MainTest(Result& outResult, const Config& config)
196{
197 assert(config.ThreadCount > 0);
198
199 InitResult(outResult);
200
201 RandomNumberGenerator mainRand{config.RandSeed};
202
203 time_point timeBeg = std::chrono::high_resolution_clock::now();
204
205 std::atomic<size_t> allocationCount = 0;
206 VkResult res = VK_SUCCESS;
207
208 uint32_t memUsageProbabilitySum =
209 config.MemUsageProbability[0] + config.MemUsageProbability[1] +
210 config.MemUsageProbability[2] + config.MemUsageProbability[3];
211 assert(memUsageProbabilitySum > 0);
212
213 uint32_t allocationSizeProbabilitySum = std::accumulate(
214 config.AllocationSizes.begin(),
215 config.AllocationSizes.end(),
216 0u,
217 [](uint32_t sum, const AllocationSize& allocSize) {
218 return sum + allocSize.Probability;
219 });
220
221 struct Allocation
222 {
223 VkBuffer Buffer;
224 VkImage Image;
225 VmaAllocation Alloc;
226 };
227
228 std::vector<Allocation> commonAllocations;
229 std::mutex commonAllocationsMutex;
230
231 auto Allocate = [&](
232 VkDeviceSize bufferSize,
233 const VkExtent2D imageExtent,
234 RandomNumberGenerator& localRand,
235 VkDeviceSize& totalAllocatedBytes,
236 std::vector<Allocation>& allocations) -> VkResult
237 {
238 assert((bufferSize == 0) != (imageExtent.width == 0 && imageExtent.height == 0));
239
240 uint32_t memUsageIndex = 0;
241 uint32_t memUsageRand = localRand.Generate() % memUsageProbabilitySum;
242 while(memUsageRand >= config.MemUsageProbability[memUsageIndex])
243 memUsageRand -= config.MemUsageProbability[memUsageIndex++];
244
245 VmaAllocationCreateInfo memReq = {};
246 memReq.usage = (VmaMemoryUsage)(VMA_MEMORY_USAGE_GPU_ONLY + memUsageIndex);
247
248 Allocation allocation = {};
249 VmaAllocationInfo allocationInfo;
250
251 // Buffer
252 if(bufferSize > 0)
253 {
254 assert(imageExtent.width == 0);
255 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
256 bufferInfo.size = bufferSize;
257 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
258
259 {
260 AllocationTimeRegisterObj timeRegisterObj{outResult};
261 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &memReq, &allocation.Buffer, &allocation.Alloc, &allocationInfo);
262 }
263 }
264 // Image
265 else
266 {
267 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
268 imageInfo.imageType = VK_IMAGE_TYPE_2D;
269 imageInfo.extent.width = imageExtent.width;
270 imageInfo.extent.height = imageExtent.height;
271 imageInfo.extent.depth = 1;
272 imageInfo.mipLevels = 1;
273 imageInfo.arrayLayers = 1;
274 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
275 imageInfo.tiling = memReq.usage == VMA_MEMORY_USAGE_GPU_ONLY ?
276 VK_IMAGE_TILING_OPTIMAL :
277 VK_IMAGE_TILING_LINEAR;
278 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
279 switch(memReq.usage)
280 {
281 case VMA_MEMORY_USAGE_GPU_ONLY:
282 switch(localRand.Generate() % 3)
283 {
284 case 0:
285 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
286 break;
287 case 1:
288 imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
289 break;
290 case 2:
291 imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
292 break;
293 }
294 break;
295 case VMA_MEMORY_USAGE_CPU_ONLY:
296 case VMA_MEMORY_USAGE_CPU_TO_GPU:
297 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
298 break;
299 case VMA_MEMORY_USAGE_GPU_TO_CPU:
300 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
301 break;
302 }
303 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
304 imageInfo.flags = 0;
305
306 {
307 AllocationTimeRegisterObj timeRegisterObj{outResult};
308 res = vmaCreateImage(g_hAllocator, &imageInfo, &memReq, &allocation.Image, &allocation.Alloc, &allocationInfo);
309 }
310 }
311
312 if(res == VK_SUCCESS)
313 {
314 ++allocationCount;
315 totalAllocatedBytes += allocationInfo.size;
316 bool useCommonAllocations = localRand.Generate() % 100 < config.ThreadsUsingCommonAllocationsProbabilityPercent;
317 if(useCommonAllocations)
318 {
319 std::unique_lock<std::mutex> lock(commonAllocationsMutex);
320 commonAllocations.push_back(allocation);
321 }
322 else
323 allocations.push_back(allocation);
324 }
325 else
326 {
327 assert(0);
328 }
329 return res;
330 };
331
332 auto GetNextAllocationSize = [&](
333 VkDeviceSize& outBufSize,
334 VkExtent2D& outImageSize,
335 RandomNumberGenerator& localRand)
336 {
337 outBufSize = 0;
338 outImageSize = {0, 0};
339
340 uint32_t allocSizeIndex = 0;
341 uint32_t r = localRand.Generate() % allocationSizeProbabilitySum;
342 while(r >= config.AllocationSizes[allocSizeIndex].Probability)
343 r -= config.AllocationSizes[allocSizeIndex++].Probability;
344
345 const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex];
346 if(allocSize.BufferSizeMax > 0)
347 {
348 assert(allocSize.ImageSizeMax == 0);
349 if(allocSize.BufferSizeMax == allocSize.BufferSizeMin)
350 outBufSize = allocSize.BufferSizeMin;
351 else
352 {
353 outBufSize = allocSize.BufferSizeMin + localRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin);
354 outBufSize = outBufSize / 16 * 16;
355 }
356 }
357 else
358 {
359 if(allocSize.ImageSizeMax == allocSize.ImageSizeMin)
360 outImageSize.width = outImageSize.height = allocSize.ImageSizeMax;
361 else
362 {
363 outImageSize.width = allocSize.ImageSizeMin + localRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
364 outImageSize.height = allocSize.ImageSizeMin + localRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
365 }
366 }
367 };
368
369 std::atomic<uint32_t> numThreadsReachedMaxAllocations = 0;
370 HANDLE threadsFinishEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
371
372 auto ThreadProc = [&](uint32_t randSeed) -> void
373 {
374 RandomNumberGenerator threadRand(randSeed);
375 VkDeviceSize threadTotalAllocatedBytes = 0;
376 std::vector<Allocation> threadAllocations;
377 VkDeviceSize threadBeginBytesToAllocate = config.BeginBytesToAllocate / config.ThreadCount;
378 VkDeviceSize threadMaxBytesToAllocate = config.MaxBytesToAllocate / config.ThreadCount;
379 uint32_t threadAdditionalOperationCount = config.AdditionalOperationCount / config.ThreadCount;
380
381 // BEGIN ALLOCATIONS
382 for(;;)
383 {
384 VkDeviceSize bufferSize = 0;
385 VkExtent2D imageExtent = {};
386 GetNextAllocationSize(bufferSize, imageExtent, threadRand);
387 if(threadTotalAllocatedBytes + bufferSize + imageExtent.width * imageExtent.height * IMAGE_BYTES_PER_PIXEL <
388 threadBeginBytesToAllocate)
389 {
390 if(Allocate(bufferSize, imageExtent, threadRand, threadTotalAllocatedBytes, threadAllocations) != VK_SUCCESS)
391 break;
392 }
393 else
394 break;
395 }
396
397 // ADDITIONAL ALLOCATIONS AND FREES
398 for(size_t i = 0; i < threadAdditionalOperationCount; ++i)
399 {
400 VkDeviceSize bufferSize = 0;
401 VkExtent2D imageExtent = {};
402 GetNextAllocationSize(bufferSize, imageExtent, threadRand);
403
404 // true = allocate, false = free
405 bool allocate = threadRand.Generate() % 2 != 0;
406
407 if(allocate)
408 {
409 if(threadTotalAllocatedBytes +
410 bufferSize +
411 imageExtent.width * imageExtent.height * IMAGE_BYTES_PER_PIXEL <
412 threadMaxBytesToAllocate)
413 {
414 if(Allocate(bufferSize, imageExtent, threadRand, threadTotalAllocatedBytes, threadAllocations) != VK_SUCCESS)
415 break;
416 }
417 }
418 else
419 {
420 bool useCommonAllocations = threadRand.Generate() % 100 < config.ThreadsUsingCommonAllocationsProbabilityPercent;
421 if(useCommonAllocations)
422 {
423 std::unique_lock<std::mutex> lock(commonAllocationsMutex);
424 if(!commonAllocations.empty())
425 {
426 size_t indexToFree = threadRand.Generate() % commonAllocations.size();
427 VmaAllocationInfo allocationInfo;
428 vmaGetAllocationInfo(g_hAllocator, commonAllocations[indexToFree].Alloc, &allocationInfo);
429 if(threadTotalAllocatedBytes >= allocationInfo.size)
430 {
431 DeallocationTimeRegisterObj timeRegisterObj{outResult};
432 if(commonAllocations[indexToFree].Buffer != VK_NULL_HANDLE)
433 vmaDestroyBuffer(g_hAllocator, commonAllocations[indexToFree].Buffer, commonAllocations[indexToFree].Alloc);
434 else
435 vmaDestroyImage(g_hAllocator, commonAllocations[indexToFree].Image, commonAllocations[indexToFree].Alloc);
436 threadTotalAllocatedBytes -= allocationInfo.size;
437 commonAllocations.erase(commonAllocations.begin() + indexToFree);
438 }
439 }
440 }
441 else
442 {
443 if(!threadAllocations.empty())
444 {
445 size_t indexToFree = threadRand.Generate() % threadAllocations.size();
446 VmaAllocationInfo allocationInfo;
447 vmaGetAllocationInfo(g_hAllocator, threadAllocations[indexToFree].Alloc, &allocationInfo);
448 if(threadTotalAllocatedBytes >= allocationInfo.size)
449 {
450 DeallocationTimeRegisterObj timeRegisterObj{outResult};
451 if(threadAllocations[indexToFree].Buffer != VK_NULL_HANDLE)
452 vmaDestroyBuffer(g_hAllocator, threadAllocations[indexToFree].Buffer, threadAllocations[indexToFree].Alloc);
453 else
454 vmaDestroyImage(g_hAllocator, threadAllocations[indexToFree].Image, threadAllocations[indexToFree].Alloc);
455 threadTotalAllocatedBytes -= allocationInfo.size;
456 threadAllocations.erase(threadAllocations.begin() + indexToFree);
457 }
458 }
459 }
460 }
461 }
462
463 ++numThreadsReachedMaxAllocations;
464
465 WaitForSingleObject(threadsFinishEvent, INFINITE);
466
467 // DEALLOCATION
468 while(!threadAllocations.empty())
469 {
470 size_t indexToFree = 0;
471 switch(config.FreeOrder)
472 {
473 case FREE_ORDER::FORWARD:
474 indexToFree = 0;
475 break;
476 case FREE_ORDER::BACKWARD:
477 indexToFree = threadAllocations.size() - 1;
478 break;
479 case FREE_ORDER::RANDOM:
480 indexToFree = mainRand.Generate() % threadAllocations.size();
481 break;
482 }
483
484 {
485 DeallocationTimeRegisterObj timeRegisterObj{outResult};
486 if(threadAllocations[indexToFree].Buffer != VK_NULL_HANDLE)
487 vmaDestroyBuffer(g_hAllocator, threadAllocations[indexToFree].Buffer, threadAllocations[indexToFree].Alloc);
488 else
489 vmaDestroyImage(g_hAllocator, threadAllocations[indexToFree].Image, threadAllocations[indexToFree].Alloc);
490 }
491 threadAllocations.erase(threadAllocations.begin() + indexToFree);
492 }
493 };
494
495 uint32_t threadRandSeed = mainRand.Generate();
496 std::vector<std::thread> bkgThreads;
497 for(size_t i = 0; i < config.ThreadCount; ++i)
498 {
499 bkgThreads.emplace_back(std::bind(ThreadProc, threadRandSeed + (uint32_t)i));
500 }
501
502 // Wait for threads reached max allocations
503 while(numThreadsReachedMaxAllocations < config.ThreadCount)
504 Sleep(0);
505
506 // CALCULATE MEMORY STATISTICS ON FINAL USAGE
507 VmaStats vmaStats = {};
508 vmaCalculateStats(g_hAllocator, &vmaStats);
509 outResult.TotalMemoryAllocated = vmaStats.total.usedBytes + vmaStats.total.unusedBytes;
510 outResult.FreeRangeSizeMax = vmaStats.total.unusedRangeSizeMax;
511 outResult.FreeRangeSizeAvg = vmaStats.total.unusedRangeSizeAvg;
512
513 // Signal threads to deallocate
514 SetEvent(threadsFinishEvent);
515
516 // Wait for threads finished
517 for(size_t i = 0; i < bkgThreads.size(); ++i)
518 bkgThreads[i].join();
519 bkgThreads.clear();
520
521 CloseHandle(threadsFinishEvent);
522
523 // Deallocate remaining common resources
524 while(!commonAllocations.empty())
525 {
526 size_t indexToFree = 0;
527 switch(config.FreeOrder)
528 {
529 case FREE_ORDER::FORWARD:
530 indexToFree = 0;
531 break;
532 case FREE_ORDER::BACKWARD:
533 indexToFree = commonAllocations.size() - 1;
534 break;
535 case FREE_ORDER::RANDOM:
536 indexToFree = mainRand.Generate() % commonAllocations.size();
537 break;
538 }
539
540 {
541 DeallocationTimeRegisterObj timeRegisterObj{outResult};
542 if(commonAllocations[indexToFree].Buffer != VK_NULL_HANDLE)
543 vmaDestroyBuffer(g_hAllocator, commonAllocations[indexToFree].Buffer, commonAllocations[indexToFree].Alloc);
544 else
545 vmaDestroyImage(g_hAllocator, commonAllocations[indexToFree].Image, commonAllocations[indexToFree].Alloc);
546 }
547 commonAllocations.erase(commonAllocations.begin() + indexToFree);
548 }
549
550 if(allocationCount)
551 {
552 outResult.AllocationTimeAvg /= allocationCount;
553 outResult.DeallocationTimeAvg /= allocationCount;
554 }
555
556 outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg;
557
558 return res;
559}
560
561static void SaveAllocatorStatsToFile(VmaAllocator alloc, const wchar_t* filePath)
562{
563 char* stats;
564 vmaBuildStatsString(alloc, &stats, VK_TRUE);
565 SaveFile(filePath, stats, strlen(stats));
566 vmaFreeStatsString(alloc, stats);
567}
568
569struct AllocInfo
570{
571 VmaAllocation m_Allocation;
572 VkBuffer m_Buffer;
573 VkImage m_Image;
574 uint32_t m_StartValue;
575 union
576 {
577 VkBufferCreateInfo m_BufferInfo;
578 VkImageCreateInfo m_ImageInfo;
579 };
580};
581
582static void GetMemReq(VmaAllocationCreateInfo& outMemReq)
583{
584 outMemReq = {};
585 outMemReq.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
586 //outMemReq.flags = VMA_ALLOCATION_CREATE_PERSISTENT_MAP_BIT;
587}
588
589static void CreateBuffer(
590 VmaPool pool,
591 const VkBufferCreateInfo& bufCreateInfo,
592 bool persistentlyMapped,
593 AllocInfo& outAllocInfo)
594{
595 outAllocInfo = {};
596 outAllocInfo.m_BufferInfo = bufCreateInfo;
597
598 VmaAllocationCreateInfo allocCreateInfo = {};
599 allocCreateInfo.pool = pool;
600 if(persistentlyMapped)
601 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
602
603 VmaAllocationInfo vmaAllocInfo = {};
604 ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &outAllocInfo.m_Buffer, &outAllocInfo.m_Allocation, &vmaAllocInfo) );
605
606 // Setup StartValue and fill.
607 {
608 outAllocInfo.m_StartValue = (uint32_t)rand();
609 uint32_t* data = (uint32_t*)vmaAllocInfo.pMappedData;
610 assert((data != nullptr) == persistentlyMapped);
611 if(!persistentlyMapped)
612 {
613 ERR_GUARD_VULKAN( vmaMapMemory(g_hAllocator, outAllocInfo.m_Allocation, (void**)&data) );
614 }
615
616 uint32_t value = outAllocInfo.m_StartValue;
617 assert(bufCreateInfo.size % 4 == 0);
618 for(size_t i = 0; i < bufCreateInfo.size / sizeof(uint32_t); ++i)
619 data[i] = value++;
620
621 if(!persistentlyMapped)
622 vmaUnmapMemory(g_hAllocator, outAllocInfo.m_Allocation);
623 }
624}
625
626static void CreateAllocation(AllocInfo& outAllocation, VmaAllocator allocator)
627{
628 outAllocation.m_Allocation = nullptr;
629 outAllocation.m_Buffer = nullptr;
630 outAllocation.m_Image = nullptr;
631 outAllocation.m_StartValue = (uint32_t)rand();
632
633 VmaAllocationCreateInfo vmaMemReq;
634 GetMemReq(vmaMemReq);
635
636 VmaAllocationInfo allocInfo;
637
638 const bool isBuffer = true;//(rand() & 0x1) != 0;
639 const bool isLarge = (rand() % 16) == 0;
640 if(isBuffer)
641 {
642 const uint32_t bufferSize = isLarge ?
643 (rand() % 10 + 1) * (1024 * 1024) : // 1 MB ... 10 MB
644 (rand() % 1024 + 1) * 1024; // 1 KB ... 1 MB
645
646 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
647 bufferInfo.size = bufferSize;
648 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
649
650 VkResult res = vmaCreateBuffer(allocator, &bufferInfo, &vmaMemReq, &outAllocation.m_Buffer, &outAllocation.m_Allocation, &allocInfo);
651 outAllocation.m_BufferInfo = bufferInfo;
652 assert(res == VK_SUCCESS);
653 }
654 else
655 {
656 const uint32_t imageSizeX = isLarge ?
657 1024 + rand() % (4096 - 1024) : // 1024 ... 4096
658 rand() % 1024 + 1; // 1 ... 1024
659 const uint32_t imageSizeY = isLarge ?
660 1024 + rand() % (4096 - 1024) : // 1024 ... 4096
661 rand() % 1024 + 1; // 1 ... 1024
662
663 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
664 imageInfo.imageType = VK_IMAGE_TYPE_2D;
665 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
666 imageInfo.extent.width = imageSizeX;
667 imageInfo.extent.height = imageSizeY;
668 imageInfo.extent.depth = 1;
669 imageInfo.mipLevels = 1;
670 imageInfo.arrayLayers = 1;
671 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
672 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
673 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
674 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
675
676 VkResult res = vmaCreateImage(allocator, &imageInfo, &vmaMemReq, &outAllocation.m_Image, &outAllocation.m_Allocation, &allocInfo);
677 outAllocation.m_ImageInfo = imageInfo;
678 assert(res == VK_SUCCESS);
679 }
680
681 uint32_t* data = (uint32_t*)allocInfo.pMappedData;
682 if(allocInfo.pMappedData == nullptr)
683 {
684 VkResult res = vmaMapMemory(allocator, outAllocation.m_Allocation, (void**)&data);
685 assert(res == VK_SUCCESS);
686 }
687
688 uint32_t value = outAllocation.m_StartValue;
689 assert(allocInfo.size % 4 == 0);
690 for(size_t i = 0; i < allocInfo.size / sizeof(uint32_t); ++i)
691 data[i] = value++;
692
693 if(allocInfo.pMappedData == nullptr)
694 vmaUnmapMemory(allocator, outAllocation.m_Allocation);
695}
696
697static void DestroyAllocation(const AllocInfo& allocation)
698{
699 if(allocation.m_Buffer)
700 vmaDestroyBuffer(g_hAllocator, allocation.m_Buffer, allocation.m_Allocation);
701 else
702 vmaDestroyImage(g_hAllocator, allocation.m_Image, allocation.m_Allocation);
703}
704
705static void DestroyAllAllocations(std::vector<AllocInfo>& allocations)
706{
707 for(size_t i = allocations.size(); i--; )
708 DestroyAllocation(allocations[i]);
709 allocations.clear();
710}
711
712static void ValidateAllocationData(const AllocInfo& allocation)
713{
714 VmaAllocationInfo allocInfo;
715 vmaGetAllocationInfo(g_hAllocator, allocation.m_Allocation, &allocInfo);
716
717 uint32_t* data = (uint32_t*)allocInfo.pMappedData;
718 if(allocInfo.pMappedData == nullptr)
719 {
720 VkResult res = vmaMapMemory(g_hAllocator, allocation.m_Allocation, (void**)&data);
721 assert(res == VK_SUCCESS);
722 }
723
724 uint32_t value = allocation.m_StartValue;
725 bool ok = true;
726 size_t i;
727 assert(allocInfo.size % 4 == 0);
728 for(i = 0; i < allocInfo.size / sizeof(uint32_t); ++i)
729 {
730 if(data[i] != value++)
731 {
732 ok = false;
733 break;
734 }
735 }
736 assert(ok);
737
738 if(allocInfo.pMappedData == nullptr)
739 vmaUnmapMemory(g_hAllocator, allocation.m_Allocation);
740}
741
742static void RecreateAllocationResource(AllocInfo& allocation)
743{
744 VmaAllocationInfo allocInfo;
745 vmaGetAllocationInfo(g_hAllocator, allocation.m_Allocation, &allocInfo);
746
747 if(allocation.m_Buffer)
748 {
749 vkDestroyBuffer(g_hDevice, allocation.m_Buffer, nullptr);
750
751 VkResult res = vkCreateBuffer(g_hDevice, &allocation.m_BufferInfo, nullptr, &allocation.m_Buffer);
752 assert(res == VK_SUCCESS);
753
754 // Just to silence validation layer warnings.
755 VkMemoryRequirements vkMemReq;
756 vkGetBufferMemoryRequirements(g_hDevice, allocation.m_Buffer, &vkMemReq);
757 assert(vkMemReq.size == allocation.m_BufferInfo.size);
758
759 res = vkBindBufferMemory(g_hDevice, allocation.m_Buffer, allocInfo.deviceMemory, allocInfo.offset);
760 assert(res == VK_SUCCESS);
761 }
762 else
763 {
764 vkDestroyImage(g_hDevice, allocation.m_Image, nullptr);
765
766 VkResult res = vkCreateImage(g_hDevice, &allocation.m_ImageInfo, nullptr, &allocation.m_Image);
767 assert(res == VK_SUCCESS);
768
769 // Just to silence validation layer warnings.
770 VkMemoryRequirements vkMemReq;
771 vkGetImageMemoryRequirements(g_hDevice, allocation.m_Image, &vkMemReq);
772
773 res = vkBindImageMemory(g_hDevice, allocation.m_Image, allocInfo.deviceMemory, allocInfo.offset);
774 assert(res == VK_SUCCESS);
775 }
776}
777
778static void Defragment(AllocInfo* allocs, size_t allocCount,
779 const VmaDefragmentationInfo* defragmentationInfo = nullptr,
780 VmaDefragmentationStats* defragmentationStats = nullptr)
781{
782 std::vector<VmaAllocation> vmaAllocs(allocCount);
783 for(size_t i = 0; i < allocCount; ++i)
784 vmaAllocs[i] = allocs[i].m_Allocation;
785
786 std::vector<VkBool32> allocChanged(allocCount);
787
788 ERR_GUARD_VULKAN( vmaDefragment(g_hAllocator, vmaAllocs.data(), allocCount, allocChanged.data(),
789 defragmentationInfo, defragmentationStats) );
790
791 for(size_t i = 0; i < allocCount; ++i)
792 {
793 if(allocChanged[i])
794 {
795 RecreateAllocationResource(allocs[i]);
796 }
797 }
798}
799
800static void ValidateAllocationsData(const AllocInfo* allocs, size_t allocCount)
801{
802 std::for_each(allocs, allocs + allocCount, [](const AllocInfo& allocInfo) {
803 ValidateAllocationData(allocInfo);
804 });
805}
806
807void TestDefragmentationSimple()
808{
809 wprintf(L"Test defragmentation simple\n");
810
811 RandomNumberGenerator rand(667);
812
813 const VkDeviceSize BUF_SIZE = 0x10000;
814 const VkDeviceSize BLOCK_SIZE = BUF_SIZE * 8;
815
816 const VkDeviceSize MIN_BUF_SIZE = 32;
817 const VkDeviceSize MAX_BUF_SIZE = BUF_SIZE * 4;
818 auto RandomBufSize = [&]() -> VkDeviceSize {
819 return align_up<VkDeviceSize>(rand.Generate() % (MAX_BUF_SIZE - MIN_BUF_SIZE + 1) + MIN_BUF_SIZE, 32);
820 };
821
822 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
823 bufCreateInfo.size = BUF_SIZE;
824 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
825
826 VmaAllocationCreateInfo exampleAllocCreateInfo = {};
827 exampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
828
829 uint32_t memTypeIndex = UINT32_MAX;
830 vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &exampleAllocCreateInfo, &memTypeIndex);
831
832 VmaPoolCreateInfo poolCreateInfo = {};
833 poolCreateInfo.blockSize = BLOCK_SIZE;
834 poolCreateInfo.memoryTypeIndex = memTypeIndex;
835
836 VmaPool pool;
837 ERR_GUARD_VULKAN( vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) );
838
839 std::vector<AllocInfo> allocations;
840
841 // persistentlyMappedOption = 0 - not persistently mapped.
842 // persistentlyMappedOption = 1 - persistently mapped.
843 for(uint32_t persistentlyMappedOption = 0; persistentlyMappedOption < 2; ++persistentlyMappedOption)
844 {
845 wprintf(L" Persistently mapped option = %u\n", persistentlyMappedOption);
846 const bool persistentlyMapped = persistentlyMappedOption != 0;
847
848 // # Test 1
849 // Buffers of fixed size.
850 // Fill 2 blocks. Remove odd buffers. Defragment everything.
851 // Expected result: at least 1 block freed.
852 {
853 for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i)
854 {
855 AllocInfo allocInfo;
856 CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo);
857 allocations.push_back(allocInfo);
858 }
859
860 for(size_t i = 1; i < allocations.size(); ++i)
861 {
862 DestroyAllocation(allocations[i]);
863 allocations.erase(allocations.begin() + i);
864 }
865
866 VmaDefragmentationStats defragStats;
867 Defragment(allocations.data(), allocations.size(), nullptr, &defragStats);
868 assert(defragStats.allocationsMoved > 0 && defragStats.bytesMoved > 0);
869 assert(defragStats.deviceMemoryBlocksFreed >= 1);
870
871 ValidateAllocationsData(allocations.data(), allocations.size());
872
873 DestroyAllAllocations(allocations);
874 }
875
876 // # Test 2
877 // Buffers of fixed size.
878 // Fill 2 blocks. Remove odd buffers. Defragment one buffer at time.
879 // Expected result: Each of 4 interations makes some progress.
880 {
881 for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i)
882 {
883 AllocInfo allocInfo;
884 CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo);
885 allocations.push_back(allocInfo);
886 }
887
888 for(size_t i = 1; i < allocations.size(); ++i)
889 {
890 DestroyAllocation(allocations[i]);
891 allocations.erase(allocations.begin() + i);
892 }
893
894 VmaDefragmentationInfo defragInfo = {};
895 defragInfo.maxAllocationsToMove = 1;
896 defragInfo.maxBytesToMove = BUF_SIZE;
897
898 for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE / 2; ++i)
899 {
900 VmaDefragmentationStats defragStats;
901 Defragment(allocations.data(), allocations.size(), &defragInfo, &defragStats);
902 assert(defragStats.allocationsMoved > 0 && defragStats.bytesMoved > 0);
903 }
904
905 ValidateAllocationsData(allocations.data(), allocations.size());
906
907 DestroyAllAllocations(allocations);
908 }
909
910 // # Test 3
911 // Buffers of variable size.
912 // Create a number of buffers. Remove some percent of them.
913 // Defragment while having some percent of them unmovable.
914 // Expected result: Just simple validation.
915 {
916 for(size_t i = 0; i < 100; ++i)
917 {
918 VkBufferCreateInfo localBufCreateInfo = bufCreateInfo;
919 localBufCreateInfo.size = RandomBufSize();
920
921 AllocInfo allocInfo;
922 CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo);
923 allocations.push_back(allocInfo);
924 }
925
926 const uint32_t percentToDelete = 60;
927 const size_t numberToDelete = allocations.size() * percentToDelete / 100;
928 for(size_t i = 0; i < numberToDelete; ++i)
929 {
930 size_t indexToDelete = rand.Generate() % (uint32_t)allocations.size();
931 DestroyAllocation(allocations[indexToDelete]);
932 allocations.erase(allocations.begin() + indexToDelete);
933 }
934
935 // Non-movable allocations will be at the beginning of allocations array.
936 const uint32_t percentNonMovable = 20;
937 const size_t numberNonMovable = allocations.size() * percentNonMovable / 100;
938 for(size_t i = 0; i < numberNonMovable; ++i)
939 {
940 size_t indexNonMovable = i + rand.Generate() % (uint32_t)(allocations.size() - i);
941 if(indexNonMovable != i)
942 std::swap(allocations[i], allocations[indexNonMovable]);
943 }
944
945 VmaDefragmentationStats defragStats;
946 Defragment(
947 allocations.data() + numberNonMovable,
948 allocations.size() - numberNonMovable,
949 nullptr, &defragStats);
950
951 ValidateAllocationsData(allocations.data(), allocations.size());
952
953 DestroyAllAllocations(allocations);
954 }
955 }
956
957 vmaDestroyPool(g_hAllocator, pool);
958}
959
960void TestDefragmentationFull()
961{
962 std::vector<AllocInfo> allocations;
963
964 // Create initial allocations.
965 for(size_t i = 0; i < 400; ++i)
966 {
967 AllocInfo allocation;
968 CreateAllocation(allocation, g_hAllocator);
969 allocations.push_back(allocation);
970 }
971
972 // Delete random allocations
973 const size_t allocationsToDeletePercent = 80;
974 size_t allocationsToDelete = allocations.size() * allocationsToDeletePercent / 100;
975 for(size_t i = 0; i < allocationsToDelete; ++i)
976 {
977 size_t index = (size_t)rand() % allocations.size();
978 DestroyAllocation(allocations[index]);
979 allocations.erase(allocations.begin() + index);
980 }
981
982 for(size_t i = 0; i < allocations.size(); ++i)
983 ValidateAllocationData(allocations[i]);
984
985 SaveAllocatorStatsToFile(g_hAllocator, L"Before.csv");
986
987 {
988 std::vector<VmaAllocation> vmaAllocations(allocations.size());
989 for(size_t i = 0; i < allocations.size(); ++i)
990 vmaAllocations[i] = allocations[i].m_Allocation;
991
992 const size_t nonMovablePercent = 0;
993 size_t nonMovableCount = vmaAllocations.size() * nonMovablePercent / 100;
994 for(size_t i = 0; i < nonMovableCount; ++i)
995 {
996 size_t index = (size_t)rand() % vmaAllocations.size();
997 vmaAllocations.erase(vmaAllocations.begin() + index);
998 }
999
1000 const uint32_t defragCount = 1;
1001 for(uint32_t defragIndex = 0; defragIndex < defragCount; ++defragIndex)
1002 {
1003 std::vector<VkBool32> allocationsChanged(vmaAllocations.size());
1004
1005 VmaDefragmentationInfo defragmentationInfo;
1006 defragmentationInfo.maxAllocationsToMove = UINT_MAX;
1007 defragmentationInfo.maxBytesToMove = SIZE_MAX;
1008
1009 wprintf(L"Defragmentation #%u\n", defragIndex);
1010
1011 time_point begTime = std::chrono::high_resolution_clock::now();
1012
1013 VmaDefragmentationStats stats;
1014 VkResult res = vmaDefragment(g_hAllocator, vmaAllocations.data(), vmaAllocations.size(), allocationsChanged.data(), &defragmentationInfo, &stats);
1015 assert(res >= 0);
1016
1017 float defragmentDuration = ToFloatSeconds(std::chrono::high_resolution_clock::now() - begTime);
1018
1019 wprintf(L"Moved allocations %u, bytes %llu\n", stats.allocationsMoved, stats.bytesMoved);
1020 wprintf(L"Freed blocks %u, bytes %llu\n", stats.deviceMemoryBlocksFreed, stats.bytesFreed);
1021 wprintf(L"Time: %.2f s\n", defragmentDuration);
1022
1023 for(size_t i = 0; i < vmaAllocations.size(); ++i)
1024 {
1025 if(allocationsChanged[i])
1026 {
1027 RecreateAllocationResource(allocations[i]);
1028 }
1029 }
1030
1031 for(size_t i = 0; i < allocations.size(); ++i)
1032 ValidateAllocationData(allocations[i]);
1033
1034 wchar_t fileName[MAX_PATH];
1035 swprintf(fileName, MAX_PATH, L"After_%02u.csv", defragIndex);
1036 SaveAllocatorStatsToFile(g_hAllocator, fileName);
1037 }
1038 }
1039
1040 // Destroy all remaining allocations.
1041 DestroyAllAllocations(allocations);
1042}
1043
1044static void TestUserData()
1045{
1046 VkResult res;
1047
1048 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1049 bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
1050 bufCreateInfo.size = 0x10000;
1051
1052 for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
1053 {
1054 // Opaque pointer
1055 {
1056
1057 void* numberAsPointer = (void*)(size_t)0xC2501FF3u;
1058 void* pointerToSomething = &res;
1059
1060 VmaAllocationCreateInfo allocCreateInfo = {};
1061 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1062 allocCreateInfo.pUserData = numberAsPointer;
1063 if(testIndex == 1)
1064 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
1065
1066 VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo;
1067 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1068 assert(res == VK_SUCCESS);
1069 assert(allocInfo.pUserData = numberAsPointer);
1070
1071 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1072 assert(allocInfo.pUserData == numberAsPointer);
1073
1074 vmaSetAllocationUserData(g_hAllocator, alloc, pointerToSomething);
1075 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1076 assert(allocInfo.pUserData == pointerToSomething);
1077
1078 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1079 }
1080
1081 // String
1082 {
1083 const char* name1 = "Buffer name \\\"\'<>&% \nSecond line .,;=";
1084 const char* name2 = "2";
1085 const size_t name1Len = strlen(name1);
1086
1087 char* name1Buf = new char[name1Len + 1];
1088 strcpy_s(name1Buf, name1Len + 1, name1);
1089
1090 VmaAllocationCreateInfo allocCreateInfo = {};
1091 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1092 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
1093 allocCreateInfo.pUserData = name1Buf;
1094 if(testIndex == 1)
1095 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
1096
1097 VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo;
1098 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1099 assert(res == VK_SUCCESS);
1100 assert(allocInfo.pUserData != nullptr && allocInfo.pUserData != name1Buf);
1101 assert(strcmp(name1, (const char*)allocInfo.pUserData) == 0);
1102
1103 delete[] name1Buf;
1104
1105 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1106 assert(strcmp(name1, (const char*)allocInfo.pUserData) == 0);
1107
1108 vmaSetAllocationUserData(g_hAllocator, alloc, (void*)name2);
1109 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1110 assert(strcmp(name2, (const char*)allocInfo.pUserData) == 0);
1111
1112 vmaSetAllocationUserData(g_hAllocator, alloc, nullptr);
1113 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1114 assert(allocInfo.pUserData == nullptr);
1115
1116 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1117 }
1118 }
1119}
1120
1121static void TestMemoryRequirements()
1122{
1123 VkResult res;
1124 VkBuffer buf;
1125 VmaAllocation alloc;
1126 VmaAllocationInfo allocInfo;
1127
1128 const VkPhysicalDeviceMemoryProperties* memProps;
1129 vmaGetMemoryProperties(g_hAllocator, &memProps);
1130
1131 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1132 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
1133 bufInfo.size = 128;
1134
1135 VmaAllocationCreateInfo allocCreateInfo = {};
1136
1137 // No requirements.
1138 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1139 assert(res == VK_SUCCESS);
1140 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1141
1142 // Usage.
1143 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1144 allocCreateInfo.requiredFlags = 0;
1145 allocCreateInfo.preferredFlags = 0;
1146 allocCreateInfo.memoryTypeBits = UINT32_MAX;
1147
1148 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1149 assert(res == VK_SUCCESS);
1150 assert(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
1151 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1152
1153 // Required flags, preferred flags.
1154 allocCreateInfo.usage = VMA_MEMORY_USAGE_UNKNOWN;
1155 allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
1156 allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
1157 allocCreateInfo.memoryTypeBits = 0;
1158
1159 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1160 assert(res == VK_SUCCESS);
1161 assert(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
1162 assert(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
1163 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1164
1165 // memoryTypeBits.
1166 const uint32_t memType = allocInfo.memoryType;
1167 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1168 allocCreateInfo.requiredFlags = 0;
1169 allocCreateInfo.preferredFlags = 0;
1170 allocCreateInfo.memoryTypeBits = 1u << memType;
1171
1172 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1173 assert(res == VK_SUCCESS);
1174 assert(allocInfo.memoryType == memType);
1175 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1176
1177}
1178
1179static void TestBasics()
1180{
1181 VkResult res;
1182
1183 TestMemoryRequirements();
1184
1185 // Lost allocation
1186 {
1187 VmaAllocation alloc = VK_NULL_HANDLE;
1188 vmaCreateLostAllocation(g_hAllocator, &alloc);
1189 assert(alloc != VK_NULL_HANDLE);
1190
1191 VmaAllocationInfo allocInfo;
1192 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1193 assert(allocInfo.deviceMemory == VK_NULL_HANDLE);
1194 assert(allocInfo.size == 0);
1195
1196 vmaFreeMemory(g_hAllocator, alloc);
1197 }
1198
1199 // Allocation that is MAPPED and not necessarily HOST_VISIBLE.
1200 {
1201 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1202 bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
1203 bufCreateInfo.size = 128;
1204
1205 VmaAllocationCreateInfo allocCreateInfo = {};
1206 allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1207 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
1208
1209 VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo;
1210 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1211 assert(res == VK_SUCCESS);
1212
1213 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1214
1215 // Same with OWN_MEMORY.
1216 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
1217
1218 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1219 assert(res == VK_SUCCESS);
1220
1221 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1222 }
1223
1224 TestUserData();
1225}
1226
1227void TestHeapSizeLimit()
1228{
1229 const VkDeviceSize HEAP_SIZE_LIMIT = 1ull * 1024 * 1024 * 1024; // 1 GB
1230 const VkDeviceSize BLOCK_SIZE = 128ull * 1024 * 1024; // 128 MB
1231
1232 VkDeviceSize heapSizeLimit[VK_MAX_MEMORY_HEAPS];
1233 for(uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i)
1234 {
1235 heapSizeLimit[i] = HEAP_SIZE_LIMIT;
1236 }
1237
1238 VmaAllocatorCreateInfo allocatorCreateInfo = {};
1239 allocatorCreateInfo.physicalDevice = g_hPhysicalDevice;
1240 allocatorCreateInfo.device = g_hDevice;
1241 allocatorCreateInfo.pHeapSizeLimit = heapSizeLimit;
1242
1243 VmaAllocator hAllocator;
1244 VkResult res = vmaCreateAllocator(&allocatorCreateInfo, &hAllocator);
1245 assert(res == VK_SUCCESS);
1246
1247 struct Item
1248 {
1249 VkBuffer hBuf;
1250 VmaAllocation hAlloc;
1251 };
1252 std::vector<Item> items;
1253
1254 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1255 bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
1256
1257 // 1. Allocate two blocks of Own Memory, half the size of BLOCK_SIZE.
1258 VmaAllocationInfo ownAllocInfo;
1259 {
1260 VmaAllocationCreateInfo allocCreateInfo = {};
1261 allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1262 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
1263
1264 bufCreateInfo.size = BLOCK_SIZE / 2;
1265
1266 for(size_t i = 0; i < 2; ++i)
1267 {
1268 Item item;
1269 res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, &ownAllocInfo);
1270 assert(res == VK_SUCCESS);
1271 items.push_back(item);
1272 }
1273 }
1274
1275 // Create pool to make sure allocations must be out of this memory type.
1276 VmaPoolCreateInfo poolCreateInfo = {};
1277 poolCreateInfo.memoryTypeIndex = ownAllocInfo.memoryType;
1278 poolCreateInfo.blockSize = BLOCK_SIZE;
1279
1280 VmaPool hPool;
1281 res = vmaCreatePool(hAllocator, &poolCreateInfo, &hPool);
1282 assert(res == VK_SUCCESS);
1283
1284 // 2. Allocate normal buffers from all the remaining memory.
1285 {
1286 VmaAllocationCreateInfo allocCreateInfo = {};
1287 allocCreateInfo.pool = hPool;
1288
1289 bufCreateInfo.size = BLOCK_SIZE / 2;
1290
1291 const size_t bufCount = ((HEAP_SIZE_LIMIT / BLOCK_SIZE) - 1) * 2;
1292 for(size_t i = 0; i < bufCount; ++i)
1293 {
1294 Item item;
1295 res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, nullptr);
1296 assert(res == VK_SUCCESS);
1297 items.push_back(item);
1298 }
1299 }
1300
1301 // 3. Allocation of one more (even small) buffer should fail.
1302 {
1303 VmaAllocationCreateInfo allocCreateInfo = {};
1304 allocCreateInfo.pool = hPool;
1305
1306 bufCreateInfo.size = 128;
1307
1308 VkBuffer hBuf;
1309 VmaAllocation hAlloc;
1310 res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &hBuf, &hAlloc, nullptr);
1311 assert(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
1312 }
1313
1314 // Destroy everything.
1315 for(size_t i = items.size(); i--; )
1316 {
1317 vmaDestroyBuffer(hAllocator, items[i].hBuf, items[i].hAlloc);
1318 }
1319
1320 vmaDestroyPool(hAllocator, hPool);
1321
1322 vmaDestroyAllocator(hAllocator);
1323}
1324
Adam Sawicki73b16652018-06-11 16:39:25 +02001325static void TestDebugMargin()
1326{
1327 if(VMA_DEBUG_MARGIN == 0)
1328 {
1329 return;
1330 }
1331
1332 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1333 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1334
1335 VmaAllocationCreateInfo allocCreateInfo = {};
1336 allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1337
1338 // Create few buffers of different size.
1339 const size_t BUF_COUNT = 10;
1340 BufferInfo buffers[BUF_COUNT];
1341 VmaAllocationInfo allocInfo[BUF_COUNT];
1342 for(size_t i = 0; i < 10; ++i)
1343 {
1344 bufInfo.size = (VkDeviceSize)(i + 1) * 64;
1345
1346 VkResult res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buffers[i].Buffer, &buffers[i].Allocation, &allocInfo[i]);
1347 assert(res == VK_SUCCESS);
1348 // Margin is preserved also at the beginning of a block.
1349 assert(allocInfo[i].offset >= VMA_DEBUG_MARGIN);
1350 }
1351
1352 // Check if their offsets preserve margin between them.
1353 std::sort(allocInfo, allocInfo + BUF_COUNT, [](const VmaAllocationInfo& lhs, const VmaAllocationInfo& rhs) -> bool
1354 {
1355 if(lhs.deviceMemory != rhs.deviceMemory)
1356 {
1357 return lhs.deviceMemory < rhs.deviceMemory;
1358 }
1359 return lhs.offset < rhs.offset;
1360 });
1361 for(size_t i = 1; i < BUF_COUNT; ++i)
1362 {
1363 if(allocInfo[i].deviceMemory == allocInfo[i - 1].deviceMemory)
1364 {
1365 assert(allocInfo[i].offset >= allocInfo[i - 1].offset + VMA_DEBUG_MARGIN);
1366 }
1367 }
1368
1369 // Destroy all buffers.
1370 for(size_t i = BUF_COUNT; i--; )
1371 {
1372 vmaDestroyBuffer(g_hAllocator, buffers[i].Buffer, buffers[i].Allocation);
1373 }
1374}
1375
Adam Sawickib8333fb2018-03-13 16:15:53 +01001376static void TestPool_SameSize()
1377{
1378 const VkDeviceSize BUF_SIZE = 1024 * 1024;
1379 const size_t BUF_COUNT = 100;
1380 VkResult res;
1381
1382 RandomNumberGenerator rand{123};
1383
1384 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1385 bufferInfo.size = BUF_SIZE;
1386 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1387
1388 uint32_t memoryTypeBits = UINT32_MAX;
1389 {
1390 VkBuffer dummyBuffer;
1391 res = vkCreateBuffer(g_hDevice, &bufferInfo, nullptr, &dummyBuffer);
1392 assert(res == VK_SUCCESS);
1393
1394 VkMemoryRequirements memReq;
1395 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
1396 memoryTypeBits = memReq.memoryTypeBits;
1397
1398 vkDestroyBuffer(g_hDevice, dummyBuffer, nullptr);
1399 }
1400
1401 VmaAllocationCreateInfo poolAllocInfo = {};
1402 poolAllocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1403 uint32_t memTypeIndex;
1404 res = vmaFindMemoryTypeIndex(
1405 g_hAllocator,
1406 memoryTypeBits,
1407 &poolAllocInfo,
1408 &memTypeIndex);
1409
1410 VmaPoolCreateInfo poolCreateInfo = {};
1411 poolCreateInfo.memoryTypeIndex = memTypeIndex;
1412 poolCreateInfo.blockSize = BUF_SIZE * BUF_COUNT / 4;
1413 poolCreateInfo.minBlockCount = 1;
1414 poolCreateInfo.maxBlockCount = 4;
1415 poolCreateInfo.frameInUseCount = 0;
1416
1417 VmaPool pool;
1418 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
1419 assert(res == VK_SUCCESS);
1420
1421 vmaSetCurrentFrameIndex(g_hAllocator, 1);
1422
1423 VmaAllocationCreateInfo allocInfo = {};
1424 allocInfo.pool = pool;
1425 allocInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT |
1426 VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT;
1427
1428 struct BufItem
1429 {
1430 VkBuffer Buf;
1431 VmaAllocation Alloc;
1432 };
1433 std::vector<BufItem> items;
1434
1435 // Fill entire pool.
1436 for(size_t i = 0; i < BUF_COUNT; ++i)
1437 {
1438 BufItem item;
1439 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1440 assert(res == VK_SUCCESS);
1441 items.push_back(item);
1442 }
1443
1444 // Make sure that another allocation would fail.
1445 {
1446 BufItem item;
1447 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1448 assert(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
1449 }
1450
1451 // Validate that no buffer is lost. Also check that they are not mapped.
1452 for(size_t i = 0; i < items.size(); ++i)
1453 {
1454 VmaAllocationInfo allocInfo;
1455 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1456 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
1457 assert(allocInfo.pMappedData == nullptr);
1458 }
1459
1460 // Free some percent of random items.
1461 {
1462 const size_t PERCENT_TO_FREE = 10;
1463 size_t itemsToFree = items.size() * PERCENT_TO_FREE / 100;
1464 for(size_t i = 0; i < itemsToFree; ++i)
1465 {
1466 size_t index = (size_t)rand.Generate() % items.size();
1467 vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc);
1468 items.erase(items.begin() + index);
1469 }
1470 }
1471
1472 // Randomly allocate and free items.
1473 {
1474 const size_t OPERATION_COUNT = BUF_COUNT;
1475 for(size_t i = 0; i < OPERATION_COUNT; ++i)
1476 {
1477 bool allocate = rand.Generate() % 2 != 0;
1478 if(allocate)
1479 {
1480 if(items.size() < BUF_COUNT)
1481 {
1482 BufItem item;
1483 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1484 assert(res == VK_SUCCESS);
1485 items.push_back(item);
1486 }
1487 }
1488 else // Free
1489 {
1490 if(!items.empty())
1491 {
1492 size_t index = (size_t)rand.Generate() % items.size();
1493 vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc);
1494 items.erase(items.begin() + index);
1495 }
1496 }
1497 }
1498 }
1499
1500 // Allocate up to maximum.
1501 while(items.size() < BUF_COUNT)
1502 {
1503 BufItem item;
1504 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1505 assert(res == VK_SUCCESS);
1506 items.push_back(item);
1507 }
1508
1509 // Validate that no buffer is lost.
1510 for(size_t i = 0; i < items.size(); ++i)
1511 {
1512 VmaAllocationInfo allocInfo;
1513 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1514 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
1515 }
1516
1517 // Next frame.
1518 vmaSetCurrentFrameIndex(g_hAllocator, 2);
1519
1520 // Allocate another BUF_COUNT buffers.
1521 for(size_t i = 0; i < BUF_COUNT; ++i)
1522 {
1523 BufItem item;
1524 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1525 assert(res == VK_SUCCESS);
1526 items.push_back(item);
1527 }
1528
1529 // Make sure the first BUF_COUNT is lost. Delete them.
1530 for(size_t i = 0; i < BUF_COUNT; ++i)
1531 {
1532 VmaAllocationInfo allocInfo;
1533 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1534 assert(allocInfo.deviceMemory == VK_NULL_HANDLE);
1535 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1536 }
1537 items.erase(items.begin(), items.begin() + BUF_COUNT);
1538
1539 // Validate that no buffer is lost.
1540 for(size_t i = 0; i < items.size(); ++i)
1541 {
1542 VmaAllocationInfo allocInfo;
1543 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1544 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
1545 }
1546
1547 // Free one item.
1548 vmaDestroyBuffer(g_hAllocator, items.back().Buf, items.back().Alloc);
1549 items.pop_back();
1550
1551 // Validate statistics.
1552 {
1553 VmaPoolStats poolStats = {};
1554 vmaGetPoolStats(g_hAllocator, pool, &poolStats);
1555 assert(poolStats.allocationCount == items.size());
1556 assert(poolStats.size = BUF_COUNT * BUF_SIZE);
1557 assert(poolStats.unusedRangeCount == 1);
1558 assert(poolStats.unusedRangeSizeMax == BUF_SIZE);
1559 assert(poolStats.unusedSize == BUF_SIZE);
1560 }
1561
1562 // Free all remaining items.
1563 for(size_t i = items.size(); i--; )
1564 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1565 items.clear();
1566
1567 // Allocate maximum items again.
1568 for(size_t i = 0; i < BUF_COUNT; ++i)
1569 {
1570 BufItem item;
1571 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1572 assert(res == VK_SUCCESS);
1573 items.push_back(item);
1574 }
1575
1576 // Delete every other item.
1577 for(size_t i = 0; i < BUF_COUNT / 2; ++i)
1578 {
1579 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1580 items.erase(items.begin() + i);
1581 }
1582
1583 // Defragment!
1584 {
1585 std::vector<VmaAllocation> allocationsToDefragment(items.size());
1586 for(size_t i = 0; i < items.size(); ++i)
1587 allocationsToDefragment[i] = items[i].Alloc;
1588
1589 VmaDefragmentationStats defragmentationStats;
1590 res = vmaDefragment(g_hAllocator, allocationsToDefragment.data(), items.size(), nullptr, nullptr, &defragmentationStats);
1591 assert(res == VK_SUCCESS);
1592 assert(defragmentationStats.deviceMemoryBlocksFreed == 2);
1593 }
1594
1595 // Free all remaining items.
1596 for(size_t i = items.size(); i--; )
1597 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1598 items.clear();
1599
1600 ////////////////////////////////////////////////////////////////////////////////
1601 // Test for vmaMakePoolAllocationsLost
1602
1603 // Allocate 4 buffers on frame 10.
1604 vmaSetCurrentFrameIndex(g_hAllocator, 10);
1605 for(size_t i = 0; i < 4; ++i)
1606 {
1607 BufItem item;
1608 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1609 assert(res == VK_SUCCESS);
1610 items.push_back(item);
1611 }
1612
1613 // Touch first 2 of them on frame 11.
1614 vmaSetCurrentFrameIndex(g_hAllocator, 11);
1615 for(size_t i = 0; i < 2; ++i)
1616 {
1617 VmaAllocationInfo allocInfo;
1618 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1619 }
1620
1621 // vmaMakePoolAllocationsLost. Only remaining 2 should be lost.
1622 size_t lostCount = 0xDEADC0DE;
1623 vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount);
1624 assert(lostCount == 2);
1625
1626 // Make another call. Now 0 should be lost.
1627 vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount);
1628 assert(lostCount == 0);
1629
1630 // Make another call, with null count. Should not crash.
1631 vmaMakePoolAllocationsLost(g_hAllocator, pool, nullptr);
1632
1633 // END: Free all remaining items.
1634 for(size_t i = items.size(); i--; )
1635 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1636
1637 items.clear();
1638
Adam Sawickid2924172018-06-11 12:48:46 +02001639 ////////////////////////////////////////////////////////////////////////////////
1640 // Test for allocation too large for pool
1641
1642 {
1643 VmaAllocationCreateInfo allocCreateInfo = {};
1644 allocCreateInfo.pool = pool;
1645
1646 VkMemoryRequirements memReq;
1647 memReq.memoryTypeBits = UINT32_MAX;
1648 memReq.alignment = 1;
1649 memReq.size = poolCreateInfo.blockSize + 4;
1650
1651 VmaAllocation alloc = nullptr;
1652 res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr);
1653 assert(res == VK_ERROR_OUT_OF_DEVICE_MEMORY && alloc == nullptr);
1654 }
1655
Adam Sawickib8333fb2018-03-13 16:15:53 +01001656 vmaDestroyPool(g_hAllocator, pool);
1657}
1658
1659static void TestPool_Benchmark(
1660 PoolTestResult& outResult,
1661 const PoolTestConfig& config)
1662{
1663 assert(config.ThreadCount > 0);
1664
1665 RandomNumberGenerator mainRand{config.RandSeed};
1666
1667 uint32_t allocationSizeProbabilitySum = std::accumulate(
1668 config.AllocationSizes.begin(),
1669 config.AllocationSizes.end(),
1670 0u,
1671 [](uint32_t sum, const AllocationSize& allocSize) {
1672 return sum + allocSize.Probability;
1673 });
1674
1675 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1676 bufferInfo.size = 256; // Whatever.
1677 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1678
1679 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
1680 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1681 imageInfo.extent.width = 256; // Whatever.
1682 imageInfo.extent.height = 256; // Whatever.
1683 imageInfo.extent.depth = 1;
1684 imageInfo.mipLevels = 1;
1685 imageInfo.arrayLayers = 1;
1686 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
1687 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // LINEAR if CPU memory.
1688 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
1689 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // TRANSFER_SRC if CPU memory.
1690 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1691
1692 uint32_t bufferMemoryTypeBits = UINT32_MAX;
1693 {
1694 VkBuffer dummyBuffer;
1695 VkResult res = vkCreateBuffer(g_hDevice, &bufferInfo, nullptr, &dummyBuffer);
1696 assert(res == VK_SUCCESS);
1697
1698 VkMemoryRequirements memReq;
1699 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
1700 bufferMemoryTypeBits = memReq.memoryTypeBits;
1701
1702 vkDestroyBuffer(g_hDevice, dummyBuffer, nullptr);
1703 }
1704
1705 uint32_t imageMemoryTypeBits = UINT32_MAX;
1706 {
1707 VkImage dummyImage;
1708 VkResult res = vkCreateImage(g_hDevice, &imageInfo, nullptr, &dummyImage);
1709 assert(res == VK_SUCCESS);
1710
1711 VkMemoryRequirements memReq;
1712 vkGetImageMemoryRequirements(g_hDevice, dummyImage, &memReq);
1713 imageMemoryTypeBits = memReq.memoryTypeBits;
1714
1715 vkDestroyImage(g_hDevice, dummyImage, nullptr);
1716 }
1717
1718 uint32_t memoryTypeBits = 0;
1719 if(config.UsesBuffers() && config.UsesImages())
1720 {
1721 memoryTypeBits = bufferMemoryTypeBits & imageMemoryTypeBits;
1722 if(memoryTypeBits == 0)
1723 {
1724 PrintWarning(L"Cannot test buffers + images in the same memory pool on this GPU.");
1725 return;
1726 }
1727 }
1728 else if(config.UsesBuffers())
1729 memoryTypeBits = bufferMemoryTypeBits;
1730 else if(config.UsesImages())
1731 memoryTypeBits = imageMemoryTypeBits;
1732 else
1733 assert(0);
1734
1735 VmaPoolCreateInfo poolCreateInfo = {};
1736 poolCreateInfo.memoryTypeIndex = 0;
1737 poolCreateInfo.minBlockCount = 1;
1738 poolCreateInfo.maxBlockCount = 1;
1739 poolCreateInfo.blockSize = config.PoolSize;
1740 poolCreateInfo.frameInUseCount = 1;
1741
1742 VmaAllocationCreateInfo dummyAllocCreateInfo = {};
1743 dummyAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1744 vmaFindMemoryTypeIndex(g_hAllocator, memoryTypeBits, &dummyAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
1745
1746 VmaPool pool;
1747 VkResult res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
1748 assert(res == VK_SUCCESS);
1749
1750 // Start time measurement - after creating pool and initializing data structures.
1751 time_point timeBeg = std::chrono::high_resolution_clock::now();
1752
1753 ////////////////////////////////////////////////////////////////////////////////
1754 // ThreadProc
1755 auto ThreadProc = [&](
1756 PoolTestThreadResult* outThreadResult,
1757 uint32_t randSeed,
1758 HANDLE frameStartEvent,
1759 HANDLE frameEndEvent) -> void
1760 {
1761 RandomNumberGenerator threadRand{randSeed};
1762
1763 outThreadResult->AllocationTimeMin = duration::max();
1764 outThreadResult->AllocationTimeSum = duration::zero();
1765 outThreadResult->AllocationTimeMax = duration::min();
1766 outThreadResult->DeallocationTimeMin = duration::max();
1767 outThreadResult->DeallocationTimeSum = duration::zero();
1768 outThreadResult->DeallocationTimeMax = duration::min();
1769 outThreadResult->AllocationCount = 0;
1770 outThreadResult->DeallocationCount = 0;
1771 outThreadResult->LostAllocationCount = 0;
1772 outThreadResult->LostAllocationTotalSize = 0;
1773 outThreadResult->FailedAllocationCount = 0;
1774 outThreadResult->FailedAllocationTotalSize = 0;
1775
1776 struct Item
1777 {
1778 VkDeviceSize BufferSize;
1779 VkExtent2D ImageSize;
1780 VkBuffer Buf;
1781 VkImage Image;
1782 VmaAllocation Alloc;
1783
1784 VkDeviceSize CalcSizeBytes() const
1785 {
1786 return BufferSize +
1787 ImageSize.width * ImageSize.height * 4;
1788 }
1789 };
1790 std::vector<Item> unusedItems, usedItems;
1791
1792 const size_t threadTotalItemCount = config.TotalItemCount / config.ThreadCount;
1793
1794 // Create all items - all unused, not yet allocated.
1795 for(size_t i = 0; i < threadTotalItemCount; ++i)
1796 {
1797 Item item = {};
1798
1799 uint32_t allocSizeIndex = 0;
1800 uint32_t r = threadRand.Generate() % allocationSizeProbabilitySum;
1801 while(r >= config.AllocationSizes[allocSizeIndex].Probability)
1802 r -= config.AllocationSizes[allocSizeIndex++].Probability;
1803
1804 const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex];
1805 if(allocSize.BufferSizeMax > 0)
1806 {
1807 assert(allocSize.BufferSizeMin > 0);
1808 assert(allocSize.ImageSizeMin == 0 && allocSize.ImageSizeMax == 0);
1809 if(allocSize.BufferSizeMax == allocSize.BufferSizeMin)
1810 item.BufferSize = allocSize.BufferSizeMin;
1811 else
1812 {
1813 item.BufferSize = allocSize.BufferSizeMin + threadRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin);
1814 item.BufferSize = item.BufferSize / 16 * 16;
1815 }
1816 }
1817 else
1818 {
1819 assert(allocSize.ImageSizeMin > 0 && allocSize.ImageSizeMax > 0);
1820 if(allocSize.ImageSizeMax == allocSize.ImageSizeMin)
1821 item.ImageSize.width = item.ImageSize.height = allocSize.ImageSizeMax;
1822 else
1823 {
1824 item.ImageSize.width = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
1825 item.ImageSize.height = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
1826 }
1827 }
1828
1829 unusedItems.push_back(item);
1830 }
1831
1832 auto Allocate = [&](Item& item) -> VkResult
1833 {
1834 VmaAllocationCreateInfo allocCreateInfo = {};
1835 allocCreateInfo.pool = pool;
1836 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT |
1837 VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT;
1838
1839 if(item.BufferSize)
1840 {
1841 bufferInfo.size = item.BufferSize;
1842 PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
1843 return vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocCreateInfo, &item.Buf, &item.Alloc, nullptr);
1844 }
1845 else
1846 {
1847 assert(item.ImageSize.width && item.ImageSize.height);
1848
1849 imageInfo.extent.width = item.ImageSize.width;
1850 imageInfo.extent.height = item.ImageSize.height;
1851 PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
1852 return vmaCreateImage(g_hAllocator, &imageInfo, &allocCreateInfo, &item.Image, &item.Alloc, nullptr);
1853 }
1854 };
1855
1856 ////////////////////////////////////////////////////////////////////////////////
1857 // Frames
1858 for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex)
1859 {
1860 WaitForSingleObject(frameStartEvent, INFINITE);
1861
1862 // Always make some percent of used bufs unused, to choose different used ones.
1863 const size_t bufsToMakeUnused = usedItems.size() * config.ItemsToMakeUnusedPercent / 100;
1864 for(size_t i = 0; i < bufsToMakeUnused; ++i)
1865 {
1866 size_t index = threadRand.Generate() % usedItems.size();
1867 unusedItems.push_back(usedItems[index]);
1868 usedItems.erase(usedItems.begin() + index);
1869 }
1870
1871 // Determine which bufs we want to use in this frame.
1872 const size_t usedBufCount = (threadRand.Generate() % (config.UsedItemCountMax - config.UsedItemCountMin) + config.UsedItemCountMin)
1873 / config.ThreadCount;
1874 assert(usedBufCount < usedItems.size() + unusedItems.size());
1875 // Move some used to unused.
1876 while(usedBufCount < usedItems.size())
1877 {
1878 size_t index = threadRand.Generate() % usedItems.size();
1879 unusedItems.push_back(usedItems[index]);
1880 usedItems.erase(usedItems.begin() + index);
1881 }
1882 // Move some unused to used.
1883 while(usedBufCount > usedItems.size())
1884 {
1885 size_t index = threadRand.Generate() % unusedItems.size();
1886 usedItems.push_back(unusedItems[index]);
1887 unusedItems.erase(unusedItems.begin() + index);
1888 }
1889
1890 uint32_t touchExistingCount = 0;
1891 uint32_t touchLostCount = 0;
1892 uint32_t createSucceededCount = 0;
1893 uint32_t createFailedCount = 0;
1894
1895 // Touch all used bufs. If not created or lost, allocate.
1896 for(size_t i = 0; i < usedItems.size(); ++i)
1897 {
1898 Item& item = usedItems[i];
1899 // Not yet created.
1900 if(item.Alloc == VK_NULL_HANDLE)
1901 {
1902 res = Allocate(item);
1903 ++outThreadResult->AllocationCount;
1904 if(res != VK_SUCCESS)
1905 {
1906 item.Alloc = VK_NULL_HANDLE;
1907 item.Buf = VK_NULL_HANDLE;
1908 ++outThreadResult->FailedAllocationCount;
1909 outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes();
1910 ++createFailedCount;
1911 }
1912 else
1913 ++createSucceededCount;
1914 }
1915 else
1916 {
1917 // Touch.
1918 VmaAllocationInfo allocInfo;
1919 vmaGetAllocationInfo(g_hAllocator, item.Alloc, &allocInfo);
1920 // Lost.
1921 if(allocInfo.deviceMemory == VK_NULL_HANDLE)
1922 {
1923 ++touchLostCount;
1924
1925 // Destroy.
1926 {
1927 PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult);
1928 if(item.Buf)
1929 vmaDestroyBuffer(g_hAllocator, item.Buf, item.Alloc);
1930 else
1931 vmaDestroyImage(g_hAllocator, item.Image, item.Alloc);
1932 ++outThreadResult->DeallocationCount;
1933 }
1934 item.Alloc = VK_NULL_HANDLE;
1935 item.Buf = VK_NULL_HANDLE;
1936
1937 ++outThreadResult->LostAllocationCount;
1938 outThreadResult->LostAllocationTotalSize += item.CalcSizeBytes();
1939
1940 // Recreate.
1941 res = Allocate(item);
1942 ++outThreadResult->AllocationCount;
1943 // Creation failed.
1944 if(res != VK_SUCCESS)
1945 {
1946 ++outThreadResult->FailedAllocationCount;
1947 outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes();
1948 ++createFailedCount;
1949 }
1950 else
1951 ++createSucceededCount;
1952 }
1953 else
1954 ++touchExistingCount;
1955 }
1956 }
1957
1958 /*
1959 printf("Thread %u frame %u: Touch existing %u lost %u, create succeeded %u failed %u\n",
1960 randSeed, frameIndex,
1961 touchExistingCount, touchLostCount,
1962 createSucceededCount, createFailedCount);
1963 */
1964
1965 SetEvent(frameEndEvent);
1966 }
1967
1968 // Free all remaining items.
1969 for(size_t i = usedItems.size(); i--; )
1970 {
1971 PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult);
1972 if(usedItems[i].Buf)
1973 vmaDestroyBuffer(g_hAllocator, usedItems[i].Buf, usedItems[i].Alloc);
1974 else
1975 vmaDestroyImage(g_hAllocator, usedItems[i].Image, usedItems[i].Alloc);
1976 ++outThreadResult->DeallocationCount;
1977 }
1978 for(size_t i = unusedItems.size(); i--; )
1979 {
1980 PoolDeallocationTimeRegisterObj timeRegisterOb(*outThreadResult);
1981 if(unusedItems[i].Buf)
1982 vmaDestroyBuffer(g_hAllocator, unusedItems[i].Buf, unusedItems[i].Alloc);
1983 else
1984 vmaDestroyImage(g_hAllocator, unusedItems[i].Image, unusedItems[i].Alloc);
1985 ++outThreadResult->DeallocationCount;
1986 }
1987 };
1988
1989 // Launch threads.
1990 uint32_t threadRandSeed = mainRand.Generate();
1991 std::vector<HANDLE> frameStartEvents{config.ThreadCount};
1992 std::vector<HANDLE> frameEndEvents{config.ThreadCount};
1993 std::vector<std::thread> bkgThreads;
1994 std::vector<PoolTestThreadResult> threadResults{config.ThreadCount};
1995 for(uint32_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
1996 {
1997 frameStartEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL);
1998 frameEndEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL);
1999 bkgThreads.emplace_back(std::bind(
2000 ThreadProc,
2001 &threadResults[threadIndex],
2002 threadRandSeed + threadIndex,
2003 frameStartEvents[threadIndex],
2004 frameEndEvents[threadIndex]));
2005 }
2006
2007 // Execute frames.
2008 assert(config.ThreadCount <= MAXIMUM_WAIT_OBJECTS);
2009 for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex)
2010 {
2011 vmaSetCurrentFrameIndex(g_hAllocator, frameIndex);
2012 for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
2013 SetEvent(frameStartEvents[threadIndex]);
2014 WaitForMultipleObjects(config.ThreadCount, &frameEndEvents[0], TRUE, INFINITE);
2015 }
2016
2017 // Wait for threads finished
2018 for(size_t i = 0; i < bkgThreads.size(); ++i)
2019 {
2020 bkgThreads[i].join();
2021 CloseHandle(frameEndEvents[i]);
2022 CloseHandle(frameStartEvents[i]);
2023 }
2024 bkgThreads.clear();
2025
2026 // Finish time measurement - before destroying pool.
2027 outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg;
2028
2029 vmaDestroyPool(g_hAllocator, pool);
2030
2031 outResult.AllocationTimeMin = duration::max();
2032 outResult.AllocationTimeAvg = duration::zero();
2033 outResult.AllocationTimeMax = duration::min();
2034 outResult.DeallocationTimeMin = duration::max();
2035 outResult.DeallocationTimeAvg = duration::zero();
2036 outResult.DeallocationTimeMax = duration::min();
2037 outResult.LostAllocationCount = 0;
2038 outResult.LostAllocationTotalSize = 0;
2039 outResult.FailedAllocationCount = 0;
2040 outResult.FailedAllocationTotalSize = 0;
2041 size_t allocationCount = 0;
2042 size_t deallocationCount = 0;
2043 for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
2044 {
2045 const PoolTestThreadResult& threadResult = threadResults[threadIndex];
2046 outResult.AllocationTimeMin = std::min(outResult.AllocationTimeMin, threadResult.AllocationTimeMin);
2047 outResult.AllocationTimeMax = std::max(outResult.AllocationTimeMax, threadResult.AllocationTimeMax);
2048 outResult.AllocationTimeAvg += threadResult.AllocationTimeSum;
2049 outResult.DeallocationTimeMin = std::min(outResult.DeallocationTimeMin, threadResult.DeallocationTimeMin);
2050 outResult.DeallocationTimeMax = std::max(outResult.DeallocationTimeMax, threadResult.DeallocationTimeMax);
2051 outResult.DeallocationTimeAvg += threadResult.DeallocationTimeSum;
2052 allocationCount += threadResult.AllocationCount;
2053 deallocationCount += threadResult.DeallocationCount;
2054 outResult.FailedAllocationCount += threadResult.FailedAllocationCount;
2055 outResult.FailedAllocationTotalSize += threadResult.FailedAllocationTotalSize;
2056 outResult.LostAllocationCount += threadResult.LostAllocationCount;
2057 outResult.LostAllocationTotalSize += threadResult.LostAllocationTotalSize;
2058 }
2059 if(allocationCount)
2060 outResult.AllocationTimeAvg /= allocationCount;
2061 if(deallocationCount)
2062 outResult.DeallocationTimeAvg /= deallocationCount;
2063}
2064
2065static inline bool MemoryRegionsOverlap(char* ptr1, size_t size1, char* ptr2, size_t size2)
2066{
2067 if(ptr1 < ptr2)
2068 return ptr1 + size1 > ptr2;
2069 else if(ptr2 < ptr1)
2070 return ptr2 + size2 > ptr1;
2071 else
2072 return true;
2073}
2074
2075static void TestMapping()
2076{
2077 wprintf(L"Testing mapping...\n");
2078
2079 VkResult res;
2080 uint32_t memTypeIndex = UINT32_MAX;
2081
2082 enum TEST
2083 {
2084 TEST_NORMAL,
2085 TEST_POOL,
2086 TEST_DEDICATED,
2087 TEST_COUNT
2088 };
2089 for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex)
2090 {
2091 VmaPool pool = nullptr;
2092 if(testIndex == TEST_POOL)
2093 {
2094 assert(memTypeIndex != UINT32_MAX);
2095 VmaPoolCreateInfo poolInfo = {};
2096 poolInfo.memoryTypeIndex = memTypeIndex;
2097 res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
2098 assert(res == VK_SUCCESS);
2099 }
2100
2101 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2102 bufInfo.size = 0x10000;
2103 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2104
2105 VmaAllocationCreateInfo allocCreateInfo = {};
2106 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
2107 allocCreateInfo.pool = pool;
2108 if(testIndex == TEST_DEDICATED)
2109 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
2110
2111 VmaAllocationInfo allocInfo;
2112
2113 // Mapped manually
2114
2115 // Create 2 buffers.
2116 BufferInfo bufferInfos[3];
2117 for(size_t i = 0; i < 2; ++i)
2118 {
2119 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
2120 &bufferInfos[i].Buffer, &bufferInfos[i].Allocation, &allocInfo);
2121 assert(res == VK_SUCCESS);
2122 assert(allocInfo.pMappedData == nullptr);
2123 memTypeIndex = allocInfo.memoryType;
2124 }
2125
2126 // Map buffer 0.
2127 char* data00 = nullptr;
2128 res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data00);
2129 assert(res == VK_SUCCESS && data00 != nullptr);
2130 data00[0xFFFF] = data00[0];
2131
2132 // Map buffer 0 second time.
2133 char* data01 = nullptr;
2134 res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data01);
2135 assert(res == VK_SUCCESS && data01 == data00);
2136
2137 // Map buffer 1.
2138 char* data1 = nullptr;
2139 res = vmaMapMemory(g_hAllocator, bufferInfos[1].Allocation, (void**)&data1);
2140 assert(res == VK_SUCCESS && data1 != nullptr);
2141 assert(!MemoryRegionsOverlap(data00, (size_t)bufInfo.size, data1, (size_t)bufInfo.size));
2142 data1[0xFFFF] = data1[0];
2143
2144 // Unmap buffer 0 two times.
2145 vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
2146 vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
2147 vmaGetAllocationInfo(g_hAllocator, bufferInfos[0].Allocation, &allocInfo);
2148 assert(allocInfo.pMappedData == nullptr);
2149
2150 // Unmap buffer 1.
2151 vmaUnmapMemory(g_hAllocator, bufferInfos[1].Allocation);
2152 vmaGetAllocationInfo(g_hAllocator, bufferInfos[1].Allocation, &allocInfo);
2153 assert(allocInfo.pMappedData == nullptr);
2154
2155 // Create 3rd buffer - persistently mapped.
2156 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
2157 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
2158 &bufferInfos[2].Buffer, &bufferInfos[2].Allocation, &allocInfo);
2159 assert(res == VK_SUCCESS && allocInfo.pMappedData != nullptr);
2160
2161 // Map buffer 2.
2162 char* data2 = nullptr;
2163 res = vmaMapMemory(g_hAllocator, bufferInfos[2].Allocation, (void**)&data2);
2164 assert(res == VK_SUCCESS && data2 == allocInfo.pMappedData);
2165 data2[0xFFFF] = data2[0];
2166
2167 // Unmap buffer 2.
2168 vmaUnmapMemory(g_hAllocator, bufferInfos[2].Allocation);
2169 vmaGetAllocationInfo(g_hAllocator, bufferInfos[2].Allocation, &allocInfo);
2170 assert(allocInfo.pMappedData == data2);
2171
2172 // Destroy all buffers.
2173 for(size_t i = 3; i--; )
2174 vmaDestroyBuffer(g_hAllocator, bufferInfos[i].Buffer, bufferInfos[i].Allocation);
2175
2176 vmaDestroyPool(g_hAllocator, pool);
2177 }
2178}
2179
2180static void TestMappingMultithreaded()
2181{
2182 wprintf(L"Testing mapping multithreaded...\n");
2183
2184 static const uint32_t threadCount = 16;
2185 static const uint32_t bufferCount = 1024;
2186 static const uint32_t threadBufferCount = bufferCount / threadCount;
2187
2188 VkResult res;
2189 volatile uint32_t memTypeIndex = UINT32_MAX;
2190
2191 enum TEST
2192 {
2193 TEST_NORMAL,
2194 TEST_POOL,
2195 TEST_DEDICATED,
2196 TEST_COUNT
2197 };
2198 for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex)
2199 {
2200 VmaPool pool = nullptr;
2201 if(testIndex == TEST_POOL)
2202 {
2203 assert(memTypeIndex != UINT32_MAX);
2204 VmaPoolCreateInfo poolInfo = {};
2205 poolInfo.memoryTypeIndex = memTypeIndex;
2206 res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
2207 assert(res == VK_SUCCESS);
2208 }
2209
2210 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2211 bufCreateInfo.size = 0x10000;
2212 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2213
2214 VmaAllocationCreateInfo allocCreateInfo = {};
2215 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
2216 allocCreateInfo.pool = pool;
2217 if(testIndex == TEST_DEDICATED)
2218 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
2219
2220 std::thread threads[threadCount];
2221 for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex)
2222 {
2223 threads[threadIndex] = std::thread([=, &memTypeIndex](){
2224 // ======== THREAD FUNCTION ========
2225
2226 RandomNumberGenerator rand{threadIndex};
2227
2228 enum class MODE
2229 {
2230 // Don't map this buffer at all.
2231 DONT_MAP,
2232 // Map and quickly unmap.
2233 MAP_FOR_MOMENT,
2234 // Map and unmap before destruction.
2235 MAP_FOR_LONGER,
2236 // Map two times. Quickly unmap, second unmap before destruction.
2237 MAP_TWO_TIMES,
2238 // Create this buffer as persistently mapped.
2239 PERSISTENTLY_MAPPED,
2240 COUNT
2241 };
2242 std::vector<BufferInfo> bufInfos{threadBufferCount};
2243 std::vector<MODE> bufModes{threadBufferCount};
2244
2245 for(uint32_t bufferIndex = 0; bufferIndex < threadBufferCount; ++bufferIndex)
2246 {
2247 BufferInfo& bufInfo = bufInfos[bufferIndex];
2248 const MODE mode = (MODE)(rand.Generate() % (uint32_t)MODE::COUNT);
2249 bufModes[bufferIndex] = mode;
2250
2251 VmaAllocationCreateInfo localAllocCreateInfo = allocCreateInfo;
2252 if(mode == MODE::PERSISTENTLY_MAPPED)
2253 localAllocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
2254
2255 VmaAllocationInfo allocInfo;
2256 VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &localAllocCreateInfo,
2257 &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo);
2258 assert(res == VK_SUCCESS);
2259
2260 if(memTypeIndex == UINT32_MAX)
2261 memTypeIndex = allocInfo.memoryType;
2262
2263 char* data = nullptr;
2264
2265 if(mode == MODE::PERSISTENTLY_MAPPED)
2266 {
2267 data = (char*)allocInfo.pMappedData;
2268 assert(data != nullptr);
2269 }
2270 else if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_FOR_LONGER ||
2271 mode == MODE::MAP_TWO_TIMES)
2272 {
2273 assert(data == nullptr);
2274 res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data);
2275 assert(res == VK_SUCCESS && data != nullptr);
2276
2277 if(mode == MODE::MAP_TWO_TIMES)
2278 {
2279 char* data2 = nullptr;
2280 res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data2);
2281 assert(res == VK_SUCCESS && data2 == data);
2282 }
2283 }
2284 else if(mode == MODE::DONT_MAP)
2285 {
2286 assert(allocInfo.pMappedData == nullptr);
2287 }
2288 else
2289 assert(0);
2290
2291 // Test if reading and writing from the beginning and end of mapped memory doesn't crash.
2292 if(data)
2293 data[0xFFFF] = data[0];
2294
2295 if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_TWO_TIMES)
2296 {
2297 vmaUnmapMemory(g_hAllocator, bufInfo.Allocation);
2298
2299 VmaAllocationInfo allocInfo;
2300 vmaGetAllocationInfo(g_hAllocator, bufInfo.Allocation, &allocInfo);
2301 if(mode == MODE::MAP_FOR_MOMENT)
2302 assert(allocInfo.pMappedData == nullptr);
2303 else
2304 assert(allocInfo.pMappedData == data);
2305 }
2306
2307 switch(rand.Generate() % 3)
2308 {
2309 case 0: Sleep(0); break; // Yield.
2310 case 1: Sleep(10); break; // 10 ms
2311 // default: No sleep.
2312 }
2313
2314 // Test if reading and writing from the beginning and end of mapped memory doesn't crash.
2315 if(data)
2316 data[0xFFFF] = data[0];
2317 }
2318
2319 for(size_t bufferIndex = threadBufferCount; bufferIndex--; )
2320 {
2321 if(bufModes[bufferIndex] == MODE::MAP_FOR_LONGER ||
2322 bufModes[bufferIndex] == MODE::MAP_TWO_TIMES)
2323 {
2324 vmaUnmapMemory(g_hAllocator, bufInfos[bufferIndex].Allocation);
2325
2326 VmaAllocationInfo allocInfo;
2327 vmaGetAllocationInfo(g_hAllocator, bufInfos[bufferIndex].Allocation, &allocInfo);
2328 assert(allocInfo.pMappedData == nullptr);
2329 }
2330
2331 vmaDestroyBuffer(g_hAllocator, bufInfos[bufferIndex].Buffer, bufInfos[bufferIndex].Allocation);
2332 }
2333 });
2334 }
2335
2336 for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex)
2337 threads[threadIndex].join();
2338
2339 vmaDestroyPool(g_hAllocator, pool);
2340 }
2341}
2342
2343static void WriteMainTestResultHeader(FILE* file)
2344{
2345 fprintf(file,
2346 "Code,Test,Time,"
2347 "Config,"
2348 "Total Time (us),"
2349 "Allocation Time Min (us),"
2350 "Allocation Time Avg (us),"
2351 "Allocation Time Max (us),"
2352 "Deallocation Time Min (us),"
2353 "Deallocation Time Avg (us),"
2354 "Deallocation Time Max (us),"
2355 "Total Memory Allocated (B),"
2356 "Free Range Size Avg (B),"
2357 "Free Range Size Max (B)\n");
2358}
2359
2360static void WriteMainTestResult(
2361 FILE* file,
2362 const char* codeDescription,
2363 const char* testDescription,
2364 const Config& config, const Result& result)
2365{
2366 float totalTimeSeconds = ToFloatSeconds(result.TotalTime);
2367 float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin);
2368 float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg);
2369 float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax);
2370 float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin);
2371 float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg);
2372 float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax);
2373
2374 time_t rawTime; time(&rawTime);
2375 struct tm timeInfo; localtime_s(&timeInfo, &rawTime);
2376 char timeStr[128];
2377 strftime(timeStr, _countof(timeStr), "%c", &timeInfo);
2378
2379 fprintf(file,
2380 "%s,%s,%s,"
2381 "BeginBytesToAllocate=%I64u MaxBytesToAllocate=%I64u AdditionalOperationCount=%u ThreadCount=%u FreeOrder=%d,"
2382 "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n",
2383 codeDescription,
2384 testDescription,
2385 timeStr,
2386 config.BeginBytesToAllocate, config.MaxBytesToAllocate, config.AdditionalOperationCount, config.ThreadCount, (uint32_t)config.FreeOrder,
2387 totalTimeSeconds * 1e6f,
2388 allocationTimeMinSeconds * 1e6f,
2389 allocationTimeAvgSeconds * 1e6f,
2390 allocationTimeMaxSeconds * 1e6f,
2391 deallocationTimeMinSeconds * 1e6f,
2392 deallocationTimeAvgSeconds * 1e6f,
2393 deallocationTimeMaxSeconds * 1e6f,
2394 result.TotalMemoryAllocated,
2395 result.FreeRangeSizeAvg,
2396 result.FreeRangeSizeMax);
2397}
2398
2399static void WritePoolTestResultHeader(FILE* file)
2400{
2401 fprintf(file,
2402 "Code,Test,Time,"
2403 "Config,"
2404 "Total Time (us),"
2405 "Allocation Time Min (us),"
2406 "Allocation Time Avg (us),"
2407 "Allocation Time Max (us),"
2408 "Deallocation Time Min (us),"
2409 "Deallocation Time Avg (us),"
2410 "Deallocation Time Max (us),"
2411 "Lost Allocation Count,"
2412 "Lost Allocation Total Size (B),"
2413 "Failed Allocation Count,"
2414 "Failed Allocation Total Size (B)\n");
2415}
2416
2417static void WritePoolTestResult(
2418 FILE* file,
2419 const char* codeDescription,
2420 const char* testDescription,
2421 const PoolTestConfig& config,
2422 const PoolTestResult& result)
2423{
2424 float totalTimeSeconds = ToFloatSeconds(result.TotalTime);
2425 float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin);
2426 float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg);
2427 float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax);
2428 float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin);
2429 float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg);
2430 float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax);
2431
2432 time_t rawTime; time(&rawTime);
2433 struct tm timeInfo; localtime_s(&timeInfo, &rawTime);
2434 char timeStr[128];
2435 strftime(timeStr, _countof(timeStr), "%c", &timeInfo);
2436
2437 fprintf(file,
2438 "%s,%s,%s,"
2439 "ThreadCount=%u PoolSize=%llu FrameCount=%u TotalItemCount=%u UsedItemCount=%u...%u ItemsToMakeUnusedPercent=%u,"
2440 "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u,%I64u\n",
2441 // General
2442 codeDescription,
2443 testDescription,
2444 timeStr,
2445 // Config
2446 config.ThreadCount,
2447 (unsigned long long)config.PoolSize,
2448 config.FrameCount,
2449 config.TotalItemCount,
2450 config.UsedItemCountMin,
2451 config.UsedItemCountMax,
2452 config.ItemsToMakeUnusedPercent,
2453 // Results
2454 totalTimeSeconds * 1e6f,
2455 allocationTimeMinSeconds * 1e6f,
2456 allocationTimeAvgSeconds * 1e6f,
2457 allocationTimeMaxSeconds * 1e6f,
2458 deallocationTimeMinSeconds * 1e6f,
2459 deallocationTimeAvgSeconds * 1e6f,
2460 deallocationTimeMaxSeconds * 1e6f,
2461 result.LostAllocationCount,
2462 result.LostAllocationTotalSize,
2463 result.FailedAllocationCount,
2464 result.FailedAllocationTotalSize);
2465}
2466
2467static void PerformCustomMainTest(FILE* file)
2468{
2469 Config config{};
2470 config.RandSeed = 65735476;
2471 //config.MaxBytesToAllocate = 4ull * 1024 * 1024; // 4 MB
2472 config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB
2473 config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY
2474 config.FreeOrder = FREE_ORDER::FORWARD;
2475 config.ThreadCount = 16;
2476 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
2477
2478 // Buffers
2479 //config.AllocationSizes.push_back({4, 16, 1024});
2480 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
2481
2482 // Images
2483 //config.AllocationSizes.push_back({4, 0, 0, 4, 32});
2484 //config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
2485
2486 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100;
2487 config.AdditionalOperationCount = 1024;
2488
2489 Result result{};
2490 VkResult res = MainTest(result, config);
2491 assert(res == VK_SUCCESS);
2492 WriteMainTestResult(file, "Foo", "CustomTest", config, result);
2493}
2494
2495static void PerformCustomPoolTest(FILE* file)
2496{
2497 PoolTestConfig config;
2498 config.PoolSize = 100 * 1024 * 1024;
2499 config.RandSeed = 2345764;
2500 config.ThreadCount = 1;
2501 config.FrameCount = 200;
2502 config.ItemsToMakeUnusedPercent = 2;
2503
2504 AllocationSize allocSize = {};
2505 allocSize.BufferSizeMin = 1024;
2506 allocSize.BufferSizeMax = 1024 * 1024;
2507 allocSize.Probability = 1;
2508 config.AllocationSizes.push_back(allocSize);
2509
2510 allocSize.BufferSizeMin = 0;
2511 allocSize.BufferSizeMax = 0;
2512 allocSize.ImageSizeMin = 128;
2513 allocSize.ImageSizeMax = 1024;
2514 allocSize.Probability = 1;
2515 config.AllocationSizes.push_back(allocSize);
2516
2517 config.PoolSize = config.CalcAvgResourceSize() * 200;
2518 config.UsedItemCountMax = 160;
2519 config.TotalItemCount = config.UsedItemCountMax * 10;
2520 config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100;
2521
2522 g_MemoryAliasingWarningEnabled = false;
2523 PoolTestResult result = {};
2524 TestPool_Benchmark(result, config);
2525 g_MemoryAliasingWarningEnabled = true;
2526
2527 WritePoolTestResult(file, "Code desc", "Test desc", config, result);
2528}
2529
2530enum CONFIG_TYPE {
2531 CONFIG_TYPE_MINIMUM,
2532 CONFIG_TYPE_SMALL,
2533 CONFIG_TYPE_AVERAGE,
2534 CONFIG_TYPE_LARGE,
2535 CONFIG_TYPE_MAXIMUM,
2536 CONFIG_TYPE_COUNT
2537};
2538
2539static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_SMALL;
2540//static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_LARGE;
2541static const char* CODE_DESCRIPTION = "Foo";
2542
2543static void PerformMainTests(FILE* file)
2544{
2545 uint32_t repeatCount = 1;
2546 if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3;
2547
2548 Config config{};
2549 config.RandSeed = 65735476;
2550 config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY
2551 config.FreeOrder = FREE_ORDER::FORWARD;
2552
2553 size_t threadCountCount = 1;
2554 switch(ConfigType)
2555 {
2556 case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break;
2557 case CONFIG_TYPE_SMALL: threadCountCount = 2; break;
2558 case CONFIG_TYPE_AVERAGE: threadCountCount = 3; break;
2559 case CONFIG_TYPE_LARGE: threadCountCount = 5; break;
2560 case CONFIG_TYPE_MAXIMUM: threadCountCount = 7; break;
2561 default: assert(0);
2562 }
2563 for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex)
2564 {
2565 std::string desc1;
2566
2567 switch(threadCountIndex)
2568 {
2569 case 0:
2570 desc1 += "1_thread";
2571 config.ThreadCount = 1;
2572 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
2573 break;
2574 case 1:
2575 desc1 += "16_threads+0%_common";
2576 config.ThreadCount = 16;
2577 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
2578 break;
2579 case 2:
2580 desc1 += "16_threads+50%_common";
2581 config.ThreadCount = 16;
2582 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
2583 break;
2584 case 3:
2585 desc1 += "16_threads+100%_common";
2586 config.ThreadCount = 16;
2587 config.ThreadsUsingCommonAllocationsProbabilityPercent = 100;
2588 break;
2589 case 4:
2590 desc1 += "2_threads+0%_common";
2591 config.ThreadCount = 2;
2592 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
2593 break;
2594 case 5:
2595 desc1 += "2_threads+50%_common";
2596 config.ThreadCount = 2;
2597 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
2598 break;
2599 case 6:
2600 desc1 += "2_threads+100%_common";
2601 config.ThreadCount = 2;
2602 config.ThreadsUsingCommonAllocationsProbabilityPercent = 100;
2603 break;
2604 default:
2605 assert(0);
2606 }
2607
2608 // 0 = buffers, 1 = images, 2 = buffers and images
2609 size_t buffersVsImagesCount = 2;
2610 if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount;
2611 for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex)
2612 {
2613 std::string desc2 = desc1;
2614 switch(buffersVsImagesIndex)
2615 {
2616 case 0: desc2 += " Buffers"; break;
2617 case 1: desc2 += " Images"; break;
2618 case 2: desc2 += " Buffers+Images"; break;
2619 default: assert(0);
2620 }
2621
2622 // 0 = small, 1 = large, 2 = small and large
2623 size_t smallVsLargeCount = 2;
2624 if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount;
2625 for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex)
2626 {
2627 std::string desc3 = desc2;
2628 switch(smallVsLargeIndex)
2629 {
2630 case 0: desc3 += " Small"; break;
2631 case 1: desc3 += " Large"; break;
2632 case 2: desc3 += " Small+Large"; break;
2633 default: assert(0);
2634 }
2635
2636 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2637 config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB
2638 else
2639 config.MaxBytesToAllocate = 4ull * 1024 * 1024;
2640
2641 // 0 = varying sizes min...max, 1 = set of constant sizes
2642 size_t constantSizesCount = 1;
2643 if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount;
2644 for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex)
2645 {
2646 std::string desc4 = desc3;
2647 switch(constantSizesIndex)
2648 {
2649 case 0: desc4 += " Varying_sizes"; break;
2650 case 1: desc4 += " Constant_sizes"; break;
2651 default: assert(0);
2652 }
2653
2654 config.AllocationSizes.clear();
2655 // Buffers present
2656 if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
2657 {
2658 // Small
2659 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
2660 {
2661 // Varying size
2662 if(constantSizesIndex == 0)
2663 config.AllocationSizes.push_back({4, 16, 1024});
2664 // Constant sizes
2665 else
2666 {
2667 config.AllocationSizes.push_back({1, 16, 16});
2668 config.AllocationSizes.push_back({1, 64, 64});
2669 config.AllocationSizes.push_back({1, 256, 256});
2670 config.AllocationSizes.push_back({1, 1024, 1024});
2671 }
2672 }
2673 // Large
2674 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2675 {
2676 // Varying size
2677 if(constantSizesIndex == 0)
2678 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
2679 // Constant sizes
2680 else
2681 {
2682 config.AllocationSizes.push_back({1, 0x10000, 0x10000});
2683 config.AllocationSizes.push_back({1, 0x80000, 0x80000});
2684 config.AllocationSizes.push_back({1, 0x200000, 0x200000});
2685 config.AllocationSizes.push_back({1, 0xA00000, 0xA00000});
2686 }
2687 }
2688 }
2689 // Images present
2690 if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
2691 {
2692 // Small
2693 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
2694 {
2695 // Varying size
2696 if(constantSizesIndex == 0)
2697 config.AllocationSizes.push_back({4, 0, 0, 4, 32});
2698 // Constant sizes
2699 else
2700 {
2701 config.AllocationSizes.push_back({1, 0, 0, 4, 4});
2702 config.AllocationSizes.push_back({1, 0, 0, 8, 8});
2703 config.AllocationSizes.push_back({1, 0, 0, 16, 16});
2704 config.AllocationSizes.push_back({1, 0, 0, 32, 32});
2705 }
2706 }
2707 // Large
2708 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2709 {
2710 // Varying size
2711 if(constantSizesIndex == 0)
2712 config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
2713 // Constant sizes
2714 else
2715 {
2716 config.AllocationSizes.push_back({1, 0, 0, 256, 256});
2717 config.AllocationSizes.push_back({1, 0, 0, 512, 512});
2718 config.AllocationSizes.push_back({1, 0, 0, 1024, 1024});
2719 config.AllocationSizes.push_back({1, 0, 0, 2048, 2048});
2720 }
2721 }
2722 }
2723
2724 // 0 = 100%, additional_operations = 0, 1 = 50%, 2 = 5%, 3 = 95% additional_operations = a lot
2725 size_t beginBytesToAllocateCount = 1;
2726 if(ConfigType >= CONFIG_TYPE_SMALL) ++beginBytesToAllocateCount;
2727 if(ConfigType >= CONFIG_TYPE_AVERAGE) ++beginBytesToAllocateCount;
2728 if(ConfigType >= CONFIG_TYPE_LARGE) ++beginBytesToAllocateCount;
2729 for(size_t beginBytesToAllocateIndex = 0; beginBytesToAllocateIndex < beginBytesToAllocateCount; ++beginBytesToAllocateIndex)
2730 {
2731 std::string desc5 = desc4;
2732
2733 switch(beginBytesToAllocateIndex)
2734 {
2735 case 0:
2736 desc5 += " Allocate_100%";
2737 config.BeginBytesToAllocate = config.MaxBytesToAllocate;
2738 config.AdditionalOperationCount = 0;
2739 break;
2740 case 1:
2741 desc5 += " Allocate_50%+Operations";
2742 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 50 / 100;
2743 config.AdditionalOperationCount = 1024;
2744 break;
2745 case 2:
2746 desc5 += " Allocate_5%+Operations";
2747 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100;
2748 config.AdditionalOperationCount = 1024;
2749 break;
2750 case 3:
2751 desc5 += " Allocate_95%+Operations";
2752 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 95 / 100;
2753 config.AdditionalOperationCount = 1024;
2754 break;
2755 default:
2756 assert(0);
2757 }
2758
2759 const char* testDescription = desc5.c_str();
2760
2761 for(size_t repeat = 0; repeat < repeatCount; ++repeat)
2762 {
2763 printf("%s Repeat %u\n", testDescription, (uint32_t)repeat);
2764
2765 Result result{};
2766 VkResult res = MainTest(result, config);
2767 assert(res == VK_SUCCESS);
2768 WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
2769 }
2770 }
2771 }
2772 }
2773 }
2774 }
2775}
2776
2777static void PerformPoolTests(FILE* file)
2778{
2779 const size_t AVG_RESOURCES_PER_POOL = 300;
2780
2781 uint32_t repeatCount = 1;
2782 if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3;
2783
2784 PoolTestConfig config{};
2785 config.RandSeed = 2346343;
2786 config.FrameCount = 200;
2787 config.ItemsToMakeUnusedPercent = 2;
2788
2789 size_t threadCountCount = 1;
2790 switch(ConfigType)
2791 {
2792 case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break;
2793 case CONFIG_TYPE_SMALL: threadCountCount = 2; break;
2794 case CONFIG_TYPE_AVERAGE: threadCountCount = 2; break;
2795 case CONFIG_TYPE_LARGE: threadCountCount = 3; break;
2796 case CONFIG_TYPE_MAXIMUM: threadCountCount = 3; break;
2797 default: assert(0);
2798 }
2799 for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex)
2800 {
2801 std::string desc1;
2802
2803 switch(threadCountIndex)
2804 {
2805 case 0:
2806 desc1 += "1_thread";
2807 config.ThreadCount = 1;
2808 break;
2809 case 1:
2810 desc1 += "16_threads";
2811 config.ThreadCount = 16;
2812 break;
2813 case 2:
2814 desc1 += "2_threads";
2815 config.ThreadCount = 2;
2816 break;
2817 default:
2818 assert(0);
2819 }
2820
2821 // 0 = buffers, 1 = images, 2 = buffers and images
2822 size_t buffersVsImagesCount = 2;
2823 if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount;
2824 for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex)
2825 {
2826 std::string desc2 = desc1;
2827 switch(buffersVsImagesIndex)
2828 {
2829 case 0: desc2 += " Buffers"; break;
2830 case 1: desc2 += " Images"; break;
2831 case 2: desc2 += " Buffers+Images"; break;
2832 default: assert(0);
2833 }
2834
2835 // 0 = small, 1 = large, 2 = small and large
2836 size_t smallVsLargeCount = 2;
2837 if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount;
2838 for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex)
2839 {
2840 std::string desc3 = desc2;
2841 switch(smallVsLargeIndex)
2842 {
2843 case 0: desc3 += " Small"; break;
2844 case 1: desc3 += " Large"; break;
2845 case 2: desc3 += " Small+Large"; break;
2846 default: assert(0);
2847 }
2848
2849 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2850 config.PoolSize = 6ull * 1024 * 1024 * 1024; // 6 GB
2851 else
2852 config.PoolSize = 4ull * 1024 * 1024;
2853
2854 // 0 = varying sizes min...max, 1 = set of constant sizes
2855 size_t constantSizesCount = 1;
2856 if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount;
2857 for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex)
2858 {
2859 std::string desc4 = desc3;
2860 switch(constantSizesIndex)
2861 {
2862 case 0: desc4 += " Varying_sizes"; break;
2863 case 1: desc4 += " Constant_sizes"; break;
2864 default: assert(0);
2865 }
2866
2867 config.AllocationSizes.clear();
2868 // Buffers present
2869 if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
2870 {
2871 // Small
2872 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
2873 {
2874 // Varying size
2875 if(constantSizesIndex == 0)
2876 config.AllocationSizes.push_back({4, 16, 1024});
2877 // Constant sizes
2878 else
2879 {
2880 config.AllocationSizes.push_back({1, 16, 16});
2881 config.AllocationSizes.push_back({1, 64, 64});
2882 config.AllocationSizes.push_back({1, 256, 256});
2883 config.AllocationSizes.push_back({1, 1024, 1024});
2884 }
2885 }
2886 // Large
2887 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2888 {
2889 // Varying size
2890 if(constantSizesIndex == 0)
2891 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
2892 // Constant sizes
2893 else
2894 {
2895 config.AllocationSizes.push_back({1, 0x10000, 0x10000});
2896 config.AllocationSizes.push_back({1, 0x80000, 0x80000});
2897 config.AllocationSizes.push_back({1, 0x200000, 0x200000});
2898 config.AllocationSizes.push_back({1, 0xA00000, 0xA00000});
2899 }
2900 }
2901 }
2902 // Images present
2903 if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
2904 {
2905 // Small
2906 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
2907 {
2908 // Varying size
2909 if(constantSizesIndex == 0)
2910 config.AllocationSizes.push_back({4, 0, 0, 4, 32});
2911 // Constant sizes
2912 else
2913 {
2914 config.AllocationSizes.push_back({1, 0, 0, 4, 4});
2915 config.AllocationSizes.push_back({1, 0, 0, 8, 8});
2916 config.AllocationSizes.push_back({1, 0, 0, 16, 16});
2917 config.AllocationSizes.push_back({1, 0, 0, 32, 32});
2918 }
2919 }
2920 // Large
2921 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2922 {
2923 // Varying size
2924 if(constantSizesIndex == 0)
2925 config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
2926 // Constant sizes
2927 else
2928 {
2929 config.AllocationSizes.push_back({1, 0, 0, 256, 256});
2930 config.AllocationSizes.push_back({1, 0, 0, 512, 512});
2931 config.AllocationSizes.push_back({1, 0, 0, 1024, 1024});
2932 config.AllocationSizes.push_back({1, 0, 0, 2048, 2048});
2933 }
2934 }
2935 }
2936
2937 const VkDeviceSize avgResourceSize = config.CalcAvgResourceSize();
2938 config.PoolSize = avgResourceSize * AVG_RESOURCES_PER_POOL;
2939
2940 // 0 = 66%, 1 = 133%, 2 = 100%, 3 = 33%, 4 = 166%
2941 size_t subscriptionModeCount;
2942 switch(ConfigType)
2943 {
2944 case CONFIG_TYPE_MINIMUM: subscriptionModeCount = 2; break;
2945 case CONFIG_TYPE_SMALL: subscriptionModeCount = 2; break;
2946 case CONFIG_TYPE_AVERAGE: subscriptionModeCount = 3; break;
2947 case CONFIG_TYPE_LARGE: subscriptionModeCount = 5; break;
2948 case CONFIG_TYPE_MAXIMUM: subscriptionModeCount = 5; break;
2949 default: assert(0);
2950 }
2951 for(size_t subscriptionModeIndex = 0; subscriptionModeIndex < subscriptionModeCount; ++subscriptionModeIndex)
2952 {
2953 std::string desc5 = desc4;
2954
2955 switch(subscriptionModeIndex)
2956 {
2957 case 0:
2958 desc5 += " Subscription_66%";
2959 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 66 / 100;
2960 break;
2961 case 1:
2962 desc5 += " Subscription_133%";
2963 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 133 / 100;
2964 break;
2965 case 2:
2966 desc5 += " Subscription_100%";
2967 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL;
2968 break;
2969 case 3:
2970 desc5 += " Subscription_33%";
2971 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 33 / 100;
2972 break;
2973 case 4:
2974 desc5 += " Subscription_166%";
2975 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 166 / 100;
2976 break;
2977 default:
2978 assert(0);
2979 }
2980
2981 config.TotalItemCount = config.UsedItemCountMax * 5;
2982 config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100;
2983
2984 const char* testDescription = desc5.c_str();
2985
2986 for(size_t repeat = 0; repeat < repeatCount; ++repeat)
2987 {
2988 printf("%s Repeat %u\n", testDescription, (uint32_t)repeat);
2989
2990 PoolTestResult result{};
2991 g_MemoryAliasingWarningEnabled = false;
2992 TestPool_Benchmark(result, config);
2993 g_MemoryAliasingWarningEnabled = true;
2994 WritePoolTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
2995 }
2996 }
2997 }
2998 }
2999 }
3000 }
3001}
3002
3003void Test()
3004{
3005 wprintf(L"TESTING:\n");
3006
3007 // # Simple tests
3008
3009 TestBasics();
Adam Sawicki73b16652018-06-11 16:39:25 +02003010 if(VMA_DEBUG_MARGIN)
3011 {
3012 TestDebugMargin();
3013 }
3014 else
3015 {
3016 TestPool_SameSize();
3017 TestHeapSizeLimit();
3018 }
Adam Sawickib8333fb2018-03-13 16:15:53 +01003019 TestMapping();
3020 TestMappingMultithreaded();
3021 TestDefragmentationSimple();
3022 TestDefragmentationFull();
3023
3024 // # Detailed tests
3025 FILE* file;
3026 fopen_s(&file, "Results.csv", "w");
3027 assert(file != NULL);
3028
3029 WriteMainTestResultHeader(file);
3030 PerformMainTests(file);
3031 //PerformCustomMainTest(file);
3032
3033 WritePoolTestResultHeader(file);
3034 PerformPoolTests(file);
3035 //PerformCustomPoolTest(file);
3036
3037 fclose(file);
3038
3039 wprintf(L"Done.\n");
3040}
3041
Adam Sawickif1a793c2018-03-13 15:42:22 +01003042#endif // #ifdef _WIN32