blob: 46b456e66ca0e92bc33f0453e2fabb1c83945c63 [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
1325static void TestPool_SameSize()
1326{
1327 const VkDeviceSize BUF_SIZE = 1024 * 1024;
1328 const size_t BUF_COUNT = 100;
1329 VkResult res;
1330
1331 RandomNumberGenerator rand{123};
1332
1333 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1334 bufferInfo.size = BUF_SIZE;
1335 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1336
1337 uint32_t memoryTypeBits = UINT32_MAX;
1338 {
1339 VkBuffer dummyBuffer;
1340 res = vkCreateBuffer(g_hDevice, &bufferInfo, nullptr, &dummyBuffer);
1341 assert(res == VK_SUCCESS);
1342
1343 VkMemoryRequirements memReq;
1344 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
1345 memoryTypeBits = memReq.memoryTypeBits;
1346
1347 vkDestroyBuffer(g_hDevice, dummyBuffer, nullptr);
1348 }
1349
1350 VmaAllocationCreateInfo poolAllocInfo = {};
1351 poolAllocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1352 uint32_t memTypeIndex;
1353 res = vmaFindMemoryTypeIndex(
1354 g_hAllocator,
1355 memoryTypeBits,
1356 &poolAllocInfo,
1357 &memTypeIndex);
1358
1359 VmaPoolCreateInfo poolCreateInfo = {};
1360 poolCreateInfo.memoryTypeIndex = memTypeIndex;
1361 poolCreateInfo.blockSize = BUF_SIZE * BUF_COUNT / 4;
1362 poolCreateInfo.minBlockCount = 1;
1363 poolCreateInfo.maxBlockCount = 4;
1364 poolCreateInfo.frameInUseCount = 0;
1365
1366 VmaPool pool;
1367 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
1368 assert(res == VK_SUCCESS);
1369
1370 vmaSetCurrentFrameIndex(g_hAllocator, 1);
1371
1372 VmaAllocationCreateInfo allocInfo = {};
1373 allocInfo.pool = pool;
1374 allocInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT |
1375 VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT;
1376
1377 struct BufItem
1378 {
1379 VkBuffer Buf;
1380 VmaAllocation Alloc;
1381 };
1382 std::vector<BufItem> items;
1383
1384 // Fill entire pool.
1385 for(size_t i = 0; i < BUF_COUNT; ++i)
1386 {
1387 BufItem item;
1388 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1389 assert(res == VK_SUCCESS);
1390 items.push_back(item);
1391 }
1392
1393 // Make sure that another allocation would fail.
1394 {
1395 BufItem item;
1396 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1397 assert(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
1398 }
1399
1400 // Validate that no buffer is lost. Also check that they are not mapped.
1401 for(size_t i = 0; i < items.size(); ++i)
1402 {
1403 VmaAllocationInfo allocInfo;
1404 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1405 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
1406 assert(allocInfo.pMappedData == nullptr);
1407 }
1408
1409 // Free some percent of random items.
1410 {
1411 const size_t PERCENT_TO_FREE = 10;
1412 size_t itemsToFree = items.size() * PERCENT_TO_FREE / 100;
1413 for(size_t i = 0; i < itemsToFree; ++i)
1414 {
1415 size_t index = (size_t)rand.Generate() % items.size();
1416 vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc);
1417 items.erase(items.begin() + index);
1418 }
1419 }
1420
1421 // Randomly allocate and free items.
1422 {
1423 const size_t OPERATION_COUNT = BUF_COUNT;
1424 for(size_t i = 0; i < OPERATION_COUNT; ++i)
1425 {
1426 bool allocate = rand.Generate() % 2 != 0;
1427 if(allocate)
1428 {
1429 if(items.size() < BUF_COUNT)
1430 {
1431 BufItem item;
1432 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1433 assert(res == VK_SUCCESS);
1434 items.push_back(item);
1435 }
1436 }
1437 else // Free
1438 {
1439 if(!items.empty())
1440 {
1441 size_t index = (size_t)rand.Generate() % items.size();
1442 vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc);
1443 items.erase(items.begin() + index);
1444 }
1445 }
1446 }
1447 }
1448
1449 // Allocate up to maximum.
1450 while(items.size() < BUF_COUNT)
1451 {
1452 BufItem item;
1453 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1454 assert(res == VK_SUCCESS);
1455 items.push_back(item);
1456 }
1457
1458 // Validate that no buffer is lost.
1459 for(size_t i = 0; i < items.size(); ++i)
1460 {
1461 VmaAllocationInfo allocInfo;
1462 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1463 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
1464 }
1465
1466 // Next frame.
1467 vmaSetCurrentFrameIndex(g_hAllocator, 2);
1468
1469 // Allocate another BUF_COUNT buffers.
1470 for(size_t i = 0; i < BUF_COUNT; ++i)
1471 {
1472 BufItem item;
1473 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1474 assert(res == VK_SUCCESS);
1475 items.push_back(item);
1476 }
1477
1478 // Make sure the first BUF_COUNT is lost. Delete them.
1479 for(size_t i = 0; i < BUF_COUNT; ++i)
1480 {
1481 VmaAllocationInfo allocInfo;
1482 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1483 assert(allocInfo.deviceMemory == VK_NULL_HANDLE);
1484 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1485 }
1486 items.erase(items.begin(), items.begin() + BUF_COUNT);
1487
1488 // Validate that no buffer is lost.
1489 for(size_t i = 0; i < items.size(); ++i)
1490 {
1491 VmaAllocationInfo allocInfo;
1492 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1493 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
1494 }
1495
1496 // Free one item.
1497 vmaDestroyBuffer(g_hAllocator, items.back().Buf, items.back().Alloc);
1498 items.pop_back();
1499
1500 // Validate statistics.
1501 {
1502 VmaPoolStats poolStats = {};
1503 vmaGetPoolStats(g_hAllocator, pool, &poolStats);
1504 assert(poolStats.allocationCount == items.size());
1505 assert(poolStats.size = BUF_COUNT * BUF_SIZE);
1506 assert(poolStats.unusedRangeCount == 1);
1507 assert(poolStats.unusedRangeSizeMax == BUF_SIZE);
1508 assert(poolStats.unusedSize == BUF_SIZE);
1509 }
1510
1511 // Free all remaining items.
1512 for(size_t i = items.size(); i--; )
1513 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1514 items.clear();
1515
1516 // Allocate maximum items again.
1517 for(size_t i = 0; i < BUF_COUNT; ++i)
1518 {
1519 BufItem item;
1520 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1521 assert(res == VK_SUCCESS);
1522 items.push_back(item);
1523 }
1524
1525 // Delete every other item.
1526 for(size_t i = 0; i < BUF_COUNT / 2; ++i)
1527 {
1528 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1529 items.erase(items.begin() + i);
1530 }
1531
1532 // Defragment!
1533 {
1534 std::vector<VmaAllocation> allocationsToDefragment(items.size());
1535 for(size_t i = 0; i < items.size(); ++i)
1536 allocationsToDefragment[i] = items[i].Alloc;
1537
1538 VmaDefragmentationStats defragmentationStats;
1539 res = vmaDefragment(g_hAllocator, allocationsToDefragment.data(), items.size(), nullptr, nullptr, &defragmentationStats);
1540 assert(res == VK_SUCCESS);
1541 assert(defragmentationStats.deviceMemoryBlocksFreed == 2);
1542 }
1543
1544 // Free all remaining items.
1545 for(size_t i = items.size(); i--; )
1546 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1547 items.clear();
1548
1549 ////////////////////////////////////////////////////////////////////////////////
1550 // Test for vmaMakePoolAllocationsLost
1551
1552 // Allocate 4 buffers on frame 10.
1553 vmaSetCurrentFrameIndex(g_hAllocator, 10);
1554 for(size_t i = 0; i < 4; ++i)
1555 {
1556 BufItem item;
1557 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1558 assert(res == VK_SUCCESS);
1559 items.push_back(item);
1560 }
1561
1562 // Touch first 2 of them on frame 11.
1563 vmaSetCurrentFrameIndex(g_hAllocator, 11);
1564 for(size_t i = 0; i < 2; ++i)
1565 {
1566 VmaAllocationInfo allocInfo;
1567 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1568 }
1569
1570 // vmaMakePoolAllocationsLost. Only remaining 2 should be lost.
1571 size_t lostCount = 0xDEADC0DE;
1572 vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount);
1573 assert(lostCount == 2);
1574
1575 // Make another call. Now 0 should be lost.
1576 vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount);
1577 assert(lostCount == 0);
1578
1579 // Make another call, with null count. Should not crash.
1580 vmaMakePoolAllocationsLost(g_hAllocator, pool, nullptr);
1581
1582 // END: Free all remaining items.
1583 for(size_t i = items.size(); i--; )
1584 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1585
1586 items.clear();
1587
1588 vmaDestroyPool(g_hAllocator, pool);
1589}
1590
1591static void TestPool_Benchmark(
1592 PoolTestResult& outResult,
1593 const PoolTestConfig& config)
1594{
1595 assert(config.ThreadCount > 0);
1596
1597 RandomNumberGenerator mainRand{config.RandSeed};
1598
1599 uint32_t allocationSizeProbabilitySum = std::accumulate(
1600 config.AllocationSizes.begin(),
1601 config.AllocationSizes.end(),
1602 0u,
1603 [](uint32_t sum, const AllocationSize& allocSize) {
1604 return sum + allocSize.Probability;
1605 });
1606
1607 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1608 bufferInfo.size = 256; // Whatever.
1609 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1610
1611 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
1612 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1613 imageInfo.extent.width = 256; // Whatever.
1614 imageInfo.extent.height = 256; // Whatever.
1615 imageInfo.extent.depth = 1;
1616 imageInfo.mipLevels = 1;
1617 imageInfo.arrayLayers = 1;
1618 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
1619 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // LINEAR if CPU memory.
1620 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
1621 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // TRANSFER_SRC if CPU memory.
1622 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1623
1624 uint32_t bufferMemoryTypeBits = UINT32_MAX;
1625 {
1626 VkBuffer dummyBuffer;
1627 VkResult res = vkCreateBuffer(g_hDevice, &bufferInfo, nullptr, &dummyBuffer);
1628 assert(res == VK_SUCCESS);
1629
1630 VkMemoryRequirements memReq;
1631 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
1632 bufferMemoryTypeBits = memReq.memoryTypeBits;
1633
1634 vkDestroyBuffer(g_hDevice, dummyBuffer, nullptr);
1635 }
1636
1637 uint32_t imageMemoryTypeBits = UINT32_MAX;
1638 {
1639 VkImage dummyImage;
1640 VkResult res = vkCreateImage(g_hDevice, &imageInfo, nullptr, &dummyImage);
1641 assert(res == VK_SUCCESS);
1642
1643 VkMemoryRequirements memReq;
1644 vkGetImageMemoryRequirements(g_hDevice, dummyImage, &memReq);
1645 imageMemoryTypeBits = memReq.memoryTypeBits;
1646
1647 vkDestroyImage(g_hDevice, dummyImage, nullptr);
1648 }
1649
1650 uint32_t memoryTypeBits = 0;
1651 if(config.UsesBuffers() && config.UsesImages())
1652 {
1653 memoryTypeBits = bufferMemoryTypeBits & imageMemoryTypeBits;
1654 if(memoryTypeBits == 0)
1655 {
1656 PrintWarning(L"Cannot test buffers + images in the same memory pool on this GPU.");
1657 return;
1658 }
1659 }
1660 else if(config.UsesBuffers())
1661 memoryTypeBits = bufferMemoryTypeBits;
1662 else if(config.UsesImages())
1663 memoryTypeBits = imageMemoryTypeBits;
1664 else
1665 assert(0);
1666
1667 VmaPoolCreateInfo poolCreateInfo = {};
1668 poolCreateInfo.memoryTypeIndex = 0;
1669 poolCreateInfo.minBlockCount = 1;
1670 poolCreateInfo.maxBlockCount = 1;
1671 poolCreateInfo.blockSize = config.PoolSize;
1672 poolCreateInfo.frameInUseCount = 1;
1673
1674 VmaAllocationCreateInfo dummyAllocCreateInfo = {};
1675 dummyAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1676 vmaFindMemoryTypeIndex(g_hAllocator, memoryTypeBits, &dummyAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
1677
1678 VmaPool pool;
1679 VkResult res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
1680 assert(res == VK_SUCCESS);
1681
1682 // Start time measurement - after creating pool and initializing data structures.
1683 time_point timeBeg = std::chrono::high_resolution_clock::now();
1684
1685 ////////////////////////////////////////////////////////////////////////////////
1686 // ThreadProc
1687 auto ThreadProc = [&](
1688 PoolTestThreadResult* outThreadResult,
1689 uint32_t randSeed,
1690 HANDLE frameStartEvent,
1691 HANDLE frameEndEvent) -> void
1692 {
1693 RandomNumberGenerator threadRand{randSeed};
1694
1695 outThreadResult->AllocationTimeMin = duration::max();
1696 outThreadResult->AllocationTimeSum = duration::zero();
1697 outThreadResult->AllocationTimeMax = duration::min();
1698 outThreadResult->DeallocationTimeMin = duration::max();
1699 outThreadResult->DeallocationTimeSum = duration::zero();
1700 outThreadResult->DeallocationTimeMax = duration::min();
1701 outThreadResult->AllocationCount = 0;
1702 outThreadResult->DeallocationCount = 0;
1703 outThreadResult->LostAllocationCount = 0;
1704 outThreadResult->LostAllocationTotalSize = 0;
1705 outThreadResult->FailedAllocationCount = 0;
1706 outThreadResult->FailedAllocationTotalSize = 0;
1707
1708 struct Item
1709 {
1710 VkDeviceSize BufferSize;
1711 VkExtent2D ImageSize;
1712 VkBuffer Buf;
1713 VkImage Image;
1714 VmaAllocation Alloc;
1715
1716 VkDeviceSize CalcSizeBytes() const
1717 {
1718 return BufferSize +
1719 ImageSize.width * ImageSize.height * 4;
1720 }
1721 };
1722 std::vector<Item> unusedItems, usedItems;
1723
1724 const size_t threadTotalItemCount = config.TotalItemCount / config.ThreadCount;
1725
1726 // Create all items - all unused, not yet allocated.
1727 for(size_t i = 0; i < threadTotalItemCount; ++i)
1728 {
1729 Item item = {};
1730
1731 uint32_t allocSizeIndex = 0;
1732 uint32_t r = threadRand.Generate() % allocationSizeProbabilitySum;
1733 while(r >= config.AllocationSizes[allocSizeIndex].Probability)
1734 r -= config.AllocationSizes[allocSizeIndex++].Probability;
1735
1736 const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex];
1737 if(allocSize.BufferSizeMax > 0)
1738 {
1739 assert(allocSize.BufferSizeMin > 0);
1740 assert(allocSize.ImageSizeMin == 0 && allocSize.ImageSizeMax == 0);
1741 if(allocSize.BufferSizeMax == allocSize.BufferSizeMin)
1742 item.BufferSize = allocSize.BufferSizeMin;
1743 else
1744 {
1745 item.BufferSize = allocSize.BufferSizeMin + threadRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin);
1746 item.BufferSize = item.BufferSize / 16 * 16;
1747 }
1748 }
1749 else
1750 {
1751 assert(allocSize.ImageSizeMin > 0 && allocSize.ImageSizeMax > 0);
1752 if(allocSize.ImageSizeMax == allocSize.ImageSizeMin)
1753 item.ImageSize.width = item.ImageSize.height = allocSize.ImageSizeMax;
1754 else
1755 {
1756 item.ImageSize.width = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
1757 item.ImageSize.height = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
1758 }
1759 }
1760
1761 unusedItems.push_back(item);
1762 }
1763
1764 auto Allocate = [&](Item& item) -> VkResult
1765 {
1766 VmaAllocationCreateInfo allocCreateInfo = {};
1767 allocCreateInfo.pool = pool;
1768 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT |
1769 VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT;
1770
1771 if(item.BufferSize)
1772 {
1773 bufferInfo.size = item.BufferSize;
1774 PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
1775 return vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocCreateInfo, &item.Buf, &item.Alloc, nullptr);
1776 }
1777 else
1778 {
1779 assert(item.ImageSize.width && item.ImageSize.height);
1780
1781 imageInfo.extent.width = item.ImageSize.width;
1782 imageInfo.extent.height = item.ImageSize.height;
1783 PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
1784 return vmaCreateImage(g_hAllocator, &imageInfo, &allocCreateInfo, &item.Image, &item.Alloc, nullptr);
1785 }
1786 };
1787
1788 ////////////////////////////////////////////////////////////////////////////////
1789 // Frames
1790 for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex)
1791 {
1792 WaitForSingleObject(frameStartEvent, INFINITE);
1793
1794 // Always make some percent of used bufs unused, to choose different used ones.
1795 const size_t bufsToMakeUnused = usedItems.size() * config.ItemsToMakeUnusedPercent / 100;
1796 for(size_t i = 0; i < bufsToMakeUnused; ++i)
1797 {
1798 size_t index = threadRand.Generate() % usedItems.size();
1799 unusedItems.push_back(usedItems[index]);
1800 usedItems.erase(usedItems.begin() + index);
1801 }
1802
1803 // Determine which bufs we want to use in this frame.
1804 const size_t usedBufCount = (threadRand.Generate() % (config.UsedItemCountMax - config.UsedItemCountMin) + config.UsedItemCountMin)
1805 / config.ThreadCount;
1806 assert(usedBufCount < usedItems.size() + unusedItems.size());
1807 // Move some used to unused.
1808 while(usedBufCount < usedItems.size())
1809 {
1810 size_t index = threadRand.Generate() % usedItems.size();
1811 unusedItems.push_back(usedItems[index]);
1812 usedItems.erase(usedItems.begin() + index);
1813 }
1814 // Move some unused to used.
1815 while(usedBufCount > usedItems.size())
1816 {
1817 size_t index = threadRand.Generate() % unusedItems.size();
1818 usedItems.push_back(unusedItems[index]);
1819 unusedItems.erase(unusedItems.begin() + index);
1820 }
1821
1822 uint32_t touchExistingCount = 0;
1823 uint32_t touchLostCount = 0;
1824 uint32_t createSucceededCount = 0;
1825 uint32_t createFailedCount = 0;
1826
1827 // Touch all used bufs. If not created or lost, allocate.
1828 for(size_t i = 0; i < usedItems.size(); ++i)
1829 {
1830 Item& item = usedItems[i];
1831 // Not yet created.
1832 if(item.Alloc == VK_NULL_HANDLE)
1833 {
1834 res = Allocate(item);
1835 ++outThreadResult->AllocationCount;
1836 if(res != VK_SUCCESS)
1837 {
1838 item.Alloc = VK_NULL_HANDLE;
1839 item.Buf = VK_NULL_HANDLE;
1840 ++outThreadResult->FailedAllocationCount;
1841 outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes();
1842 ++createFailedCount;
1843 }
1844 else
1845 ++createSucceededCount;
1846 }
1847 else
1848 {
1849 // Touch.
1850 VmaAllocationInfo allocInfo;
1851 vmaGetAllocationInfo(g_hAllocator, item.Alloc, &allocInfo);
1852 // Lost.
1853 if(allocInfo.deviceMemory == VK_NULL_HANDLE)
1854 {
1855 ++touchLostCount;
1856
1857 // Destroy.
1858 {
1859 PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult);
1860 if(item.Buf)
1861 vmaDestroyBuffer(g_hAllocator, item.Buf, item.Alloc);
1862 else
1863 vmaDestroyImage(g_hAllocator, item.Image, item.Alloc);
1864 ++outThreadResult->DeallocationCount;
1865 }
1866 item.Alloc = VK_NULL_HANDLE;
1867 item.Buf = VK_NULL_HANDLE;
1868
1869 ++outThreadResult->LostAllocationCount;
1870 outThreadResult->LostAllocationTotalSize += item.CalcSizeBytes();
1871
1872 // Recreate.
1873 res = Allocate(item);
1874 ++outThreadResult->AllocationCount;
1875 // Creation failed.
1876 if(res != VK_SUCCESS)
1877 {
1878 ++outThreadResult->FailedAllocationCount;
1879 outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes();
1880 ++createFailedCount;
1881 }
1882 else
1883 ++createSucceededCount;
1884 }
1885 else
1886 ++touchExistingCount;
1887 }
1888 }
1889
1890 /*
1891 printf("Thread %u frame %u: Touch existing %u lost %u, create succeeded %u failed %u\n",
1892 randSeed, frameIndex,
1893 touchExistingCount, touchLostCount,
1894 createSucceededCount, createFailedCount);
1895 */
1896
1897 SetEvent(frameEndEvent);
1898 }
1899
1900 // Free all remaining items.
1901 for(size_t i = usedItems.size(); i--; )
1902 {
1903 PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult);
1904 if(usedItems[i].Buf)
1905 vmaDestroyBuffer(g_hAllocator, usedItems[i].Buf, usedItems[i].Alloc);
1906 else
1907 vmaDestroyImage(g_hAllocator, usedItems[i].Image, usedItems[i].Alloc);
1908 ++outThreadResult->DeallocationCount;
1909 }
1910 for(size_t i = unusedItems.size(); i--; )
1911 {
1912 PoolDeallocationTimeRegisterObj timeRegisterOb(*outThreadResult);
1913 if(unusedItems[i].Buf)
1914 vmaDestroyBuffer(g_hAllocator, unusedItems[i].Buf, unusedItems[i].Alloc);
1915 else
1916 vmaDestroyImage(g_hAllocator, unusedItems[i].Image, unusedItems[i].Alloc);
1917 ++outThreadResult->DeallocationCount;
1918 }
1919 };
1920
1921 // Launch threads.
1922 uint32_t threadRandSeed = mainRand.Generate();
1923 std::vector<HANDLE> frameStartEvents{config.ThreadCount};
1924 std::vector<HANDLE> frameEndEvents{config.ThreadCount};
1925 std::vector<std::thread> bkgThreads;
1926 std::vector<PoolTestThreadResult> threadResults{config.ThreadCount};
1927 for(uint32_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
1928 {
1929 frameStartEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL);
1930 frameEndEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL);
1931 bkgThreads.emplace_back(std::bind(
1932 ThreadProc,
1933 &threadResults[threadIndex],
1934 threadRandSeed + threadIndex,
1935 frameStartEvents[threadIndex],
1936 frameEndEvents[threadIndex]));
1937 }
1938
1939 // Execute frames.
1940 assert(config.ThreadCount <= MAXIMUM_WAIT_OBJECTS);
1941 for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex)
1942 {
1943 vmaSetCurrentFrameIndex(g_hAllocator, frameIndex);
1944 for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
1945 SetEvent(frameStartEvents[threadIndex]);
1946 WaitForMultipleObjects(config.ThreadCount, &frameEndEvents[0], TRUE, INFINITE);
1947 }
1948
1949 // Wait for threads finished
1950 for(size_t i = 0; i < bkgThreads.size(); ++i)
1951 {
1952 bkgThreads[i].join();
1953 CloseHandle(frameEndEvents[i]);
1954 CloseHandle(frameStartEvents[i]);
1955 }
1956 bkgThreads.clear();
1957
1958 // Finish time measurement - before destroying pool.
1959 outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg;
1960
1961 vmaDestroyPool(g_hAllocator, pool);
1962
1963 outResult.AllocationTimeMin = duration::max();
1964 outResult.AllocationTimeAvg = duration::zero();
1965 outResult.AllocationTimeMax = duration::min();
1966 outResult.DeallocationTimeMin = duration::max();
1967 outResult.DeallocationTimeAvg = duration::zero();
1968 outResult.DeallocationTimeMax = duration::min();
1969 outResult.LostAllocationCount = 0;
1970 outResult.LostAllocationTotalSize = 0;
1971 outResult.FailedAllocationCount = 0;
1972 outResult.FailedAllocationTotalSize = 0;
1973 size_t allocationCount = 0;
1974 size_t deallocationCount = 0;
1975 for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
1976 {
1977 const PoolTestThreadResult& threadResult = threadResults[threadIndex];
1978 outResult.AllocationTimeMin = std::min(outResult.AllocationTimeMin, threadResult.AllocationTimeMin);
1979 outResult.AllocationTimeMax = std::max(outResult.AllocationTimeMax, threadResult.AllocationTimeMax);
1980 outResult.AllocationTimeAvg += threadResult.AllocationTimeSum;
1981 outResult.DeallocationTimeMin = std::min(outResult.DeallocationTimeMin, threadResult.DeallocationTimeMin);
1982 outResult.DeallocationTimeMax = std::max(outResult.DeallocationTimeMax, threadResult.DeallocationTimeMax);
1983 outResult.DeallocationTimeAvg += threadResult.DeallocationTimeSum;
1984 allocationCount += threadResult.AllocationCount;
1985 deallocationCount += threadResult.DeallocationCount;
1986 outResult.FailedAllocationCount += threadResult.FailedAllocationCount;
1987 outResult.FailedAllocationTotalSize += threadResult.FailedAllocationTotalSize;
1988 outResult.LostAllocationCount += threadResult.LostAllocationCount;
1989 outResult.LostAllocationTotalSize += threadResult.LostAllocationTotalSize;
1990 }
1991 if(allocationCount)
1992 outResult.AllocationTimeAvg /= allocationCount;
1993 if(deallocationCount)
1994 outResult.DeallocationTimeAvg /= deallocationCount;
1995}
1996
1997static inline bool MemoryRegionsOverlap(char* ptr1, size_t size1, char* ptr2, size_t size2)
1998{
1999 if(ptr1 < ptr2)
2000 return ptr1 + size1 > ptr2;
2001 else if(ptr2 < ptr1)
2002 return ptr2 + size2 > ptr1;
2003 else
2004 return true;
2005}
2006
2007static void TestMapping()
2008{
2009 wprintf(L"Testing mapping...\n");
2010
2011 VkResult res;
2012 uint32_t memTypeIndex = UINT32_MAX;
2013
2014 enum TEST
2015 {
2016 TEST_NORMAL,
2017 TEST_POOL,
2018 TEST_DEDICATED,
2019 TEST_COUNT
2020 };
2021 for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex)
2022 {
2023 VmaPool pool = nullptr;
2024 if(testIndex == TEST_POOL)
2025 {
2026 assert(memTypeIndex != UINT32_MAX);
2027 VmaPoolCreateInfo poolInfo = {};
2028 poolInfo.memoryTypeIndex = memTypeIndex;
2029 res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
2030 assert(res == VK_SUCCESS);
2031 }
2032
2033 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2034 bufInfo.size = 0x10000;
2035 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2036
2037 VmaAllocationCreateInfo allocCreateInfo = {};
2038 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
2039 allocCreateInfo.pool = pool;
2040 if(testIndex == TEST_DEDICATED)
2041 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
2042
2043 VmaAllocationInfo allocInfo;
2044
2045 // Mapped manually
2046
2047 // Create 2 buffers.
2048 BufferInfo bufferInfos[3];
2049 for(size_t i = 0; i < 2; ++i)
2050 {
2051 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
2052 &bufferInfos[i].Buffer, &bufferInfos[i].Allocation, &allocInfo);
2053 assert(res == VK_SUCCESS);
2054 assert(allocInfo.pMappedData == nullptr);
2055 memTypeIndex = allocInfo.memoryType;
2056 }
2057
2058 // Map buffer 0.
2059 char* data00 = nullptr;
2060 res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data00);
2061 assert(res == VK_SUCCESS && data00 != nullptr);
2062 data00[0xFFFF] = data00[0];
2063
2064 // Map buffer 0 second time.
2065 char* data01 = nullptr;
2066 res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data01);
2067 assert(res == VK_SUCCESS && data01 == data00);
2068
2069 // Map buffer 1.
2070 char* data1 = nullptr;
2071 res = vmaMapMemory(g_hAllocator, bufferInfos[1].Allocation, (void**)&data1);
2072 assert(res == VK_SUCCESS && data1 != nullptr);
2073 assert(!MemoryRegionsOverlap(data00, (size_t)bufInfo.size, data1, (size_t)bufInfo.size));
2074 data1[0xFFFF] = data1[0];
2075
2076 // Unmap buffer 0 two times.
2077 vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
2078 vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
2079 vmaGetAllocationInfo(g_hAllocator, bufferInfos[0].Allocation, &allocInfo);
2080 assert(allocInfo.pMappedData == nullptr);
2081
2082 // Unmap buffer 1.
2083 vmaUnmapMemory(g_hAllocator, bufferInfos[1].Allocation);
2084 vmaGetAllocationInfo(g_hAllocator, bufferInfos[1].Allocation, &allocInfo);
2085 assert(allocInfo.pMappedData == nullptr);
2086
2087 // Create 3rd buffer - persistently mapped.
2088 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
2089 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
2090 &bufferInfos[2].Buffer, &bufferInfos[2].Allocation, &allocInfo);
2091 assert(res == VK_SUCCESS && allocInfo.pMappedData != nullptr);
2092
2093 // Map buffer 2.
2094 char* data2 = nullptr;
2095 res = vmaMapMemory(g_hAllocator, bufferInfos[2].Allocation, (void**)&data2);
2096 assert(res == VK_SUCCESS && data2 == allocInfo.pMappedData);
2097 data2[0xFFFF] = data2[0];
2098
2099 // Unmap buffer 2.
2100 vmaUnmapMemory(g_hAllocator, bufferInfos[2].Allocation);
2101 vmaGetAllocationInfo(g_hAllocator, bufferInfos[2].Allocation, &allocInfo);
2102 assert(allocInfo.pMappedData == data2);
2103
2104 // Destroy all buffers.
2105 for(size_t i = 3; i--; )
2106 vmaDestroyBuffer(g_hAllocator, bufferInfos[i].Buffer, bufferInfos[i].Allocation);
2107
2108 vmaDestroyPool(g_hAllocator, pool);
2109 }
2110}
2111
2112static void TestMappingMultithreaded()
2113{
2114 wprintf(L"Testing mapping multithreaded...\n");
2115
2116 static const uint32_t threadCount = 16;
2117 static const uint32_t bufferCount = 1024;
2118 static const uint32_t threadBufferCount = bufferCount / threadCount;
2119
2120 VkResult res;
2121 volatile uint32_t memTypeIndex = UINT32_MAX;
2122
2123 enum TEST
2124 {
2125 TEST_NORMAL,
2126 TEST_POOL,
2127 TEST_DEDICATED,
2128 TEST_COUNT
2129 };
2130 for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex)
2131 {
2132 VmaPool pool = nullptr;
2133 if(testIndex == TEST_POOL)
2134 {
2135 assert(memTypeIndex != UINT32_MAX);
2136 VmaPoolCreateInfo poolInfo = {};
2137 poolInfo.memoryTypeIndex = memTypeIndex;
2138 res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
2139 assert(res == VK_SUCCESS);
2140 }
2141
2142 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2143 bufCreateInfo.size = 0x10000;
2144 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2145
2146 VmaAllocationCreateInfo allocCreateInfo = {};
2147 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
2148 allocCreateInfo.pool = pool;
2149 if(testIndex == TEST_DEDICATED)
2150 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
2151
2152 std::thread threads[threadCount];
2153 for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex)
2154 {
2155 threads[threadIndex] = std::thread([=, &memTypeIndex](){
2156 // ======== THREAD FUNCTION ========
2157
2158 RandomNumberGenerator rand{threadIndex};
2159
2160 enum class MODE
2161 {
2162 // Don't map this buffer at all.
2163 DONT_MAP,
2164 // Map and quickly unmap.
2165 MAP_FOR_MOMENT,
2166 // Map and unmap before destruction.
2167 MAP_FOR_LONGER,
2168 // Map two times. Quickly unmap, second unmap before destruction.
2169 MAP_TWO_TIMES,
2170 // Create this buffer as persistently mapped.
2171 PERSISTENTLY_MAPPED,
2172 COUNT
2173 };
2174 std::vector<BufferInfo> bufInfos{threadBufferCount};
2175 std::vector<MODE> bufModes{threadBufferCount};
2176
2177 for(uint32_t bufferIndex = 0; bufferIndex < threadBufferCount; ++bufferIndex)
2178 {
2179 BufferInfo& bufInfo = bufInfos[bufferIndex];
2180 const MODE mode = (MODE)(rand.Generate() % (uint32_t)MODE::COUNT);
2181 bufModes[bufferIndex] = mode;
2182
2183 VmaAllocationCreateInfo localAllocCreateInfo = allocCreateInfo;
2184 if(mode == MODE::PERSISTENTLY_MAPPED)
2185 localAllocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
2186
2187 VmaAllocationInfo allocInfo;
2188 VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &localAllocCreateInfo,
2189 &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo);
2190 assert(res == VK_SUCCESS);
2191
2192 if(memTypeIndex == UINT32_MAX)
2193 memTypeIndex = allocInfo.memoryType;
2194
2195 char* data = nullptr;
2196
2197 if(mode == MODE::PERSISTENTLY_MAPPED)
2198 {
2199 data = (char*)allocInfo.pMappedData;
2200 assert(data != nullptr);
2201 }
2202 else if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_FOR_LONGER ||
2203 mode == MODE::MAP_TWO_TIMES)
2204 {
2205 assert(data == nullptr);
2206 res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data);
2207 assert(res == VK_SUCCESS && data != nullptr);
2208
2209 if(mode == MODE::MAP_TWO_TIMES)
2210 {
2211 char* data2 = nullptr;
2212 res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data2);
2213 assert(res == VK_SUCCESS && data2 == data);
2214 }
2215 }
2216 else if(mode == MODE::DONT_MAP)
2217 {
2218 assert(allocInfo.pMappedData == nullptr);
2219 }
2220 else
2221 assert(0);
2222
2223 // Test if reading and writing from the beginning and end of mapped memory doesn't crash.
2224 if(data)
2225 data[0xFFFF] = data[0];
2226
2227 if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_TWO_TIMES)
2228 {
2229 vmaUnmapMemory(g_hAllocator, bufInfo.Allocation);
2230
2231 VmaAllocationInfo allocInfo;
2232 vmaGetAllocationInfo(g_hAllocator, bufInfo.Allocation, &allocInfo);
2233 if(mode == MODE::MAP_FOR_MOMENT)
2234 assert(allocInfo.pMappedData == nullptr);
2235 else
2236 assert(allocInfo.pMappedData == data);
2237 }
2238
2239 switch(rand.Generate() % 3)
2240 {
2241 case 0: Sleep(0); break; // Yield.
2242 case 1: Sleep(10); break; // 10 ms
2243 // default: No sleep.
2244 }
2245
2246 // Test if reading and writing from the beginning and end of mapped memory doesn't crash.
2247 if(data)
2248 data[0xFFFF] = data[0];
2249 }
2250
2251 for(size_t bufferIndex = threadBufferCount; bufferIndex--; )
2252 {
2253 if(bufModes[bufferIndex] == MODE::MAP_FOR_LONGER ||
2254 bufModes[bufferIndex] == MODE::MAP_TWO_TIMES)
2255 {
2256 vmaUnmapMemory(g_hAllocator, bufInfos[bufferIndex].Allocation);
2257
2258 VmaAllocationInfo allocInfo;
2259 vmaGetAllocationInfo(g_hAllocator, bufInfos[bufferIndex].Allocation, &allocInfo);
2260 assert(allocInfo.pMappedData == nullptr);
2261 }
2262
2263 vmaDestroyBuffer(g_hAllocator, bufInfos[bufferIndex].Buffer, bufInfos[bufferIndex].Allocation);
2264 }
2265 });
2266 }
2267
2268 for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex)
2269 threads[threadIndex].join();
2270
2271 vmaDestroyPool(g_hAllocator, pool);
2272 }
2273}
2274
2275static void WriteMainTestResultHeader(FILE* file)
2276{
2277 fprintf(file,
2278 "Code,Test,Time,"
2279 "Config,"
2280 "Total Time (us),"
2281 "Allocation Time Min (us),"
2282 "Allocation Time Avg (us),"
2283 "Allocation Time Max (us),"
2284 "Deallocation Time Min (us),"
2285 "Deallocation Time Avg (us),"
2286 "Deallocation Time Max (us),"
2287 "Total Memory Allocated (B),"
2288 "Free Range Size Avg (B),"
2289 "Free Range Size Max (B)\n");
2290}
2291
2292static void WriteMainTestResult(
2293 FILE* file,
2294 const char* codeDescription,
2295 const char* testDescription,
2296 const Config& config, const Result& result)
2297{
2298 float totalTimeSeconds = ToFloatSeconds(result.TotalTime);
2299 float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin);
2300 float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg);
2301 float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax);
2302 float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin);
2303 float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg);
2304 float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax);
2305
2306 time_t rawTime; time(&rawTime);
2307 struct tm timeInfo; localtime_s(&timeInfo, &rawTime);
2308 char timeStr[128];
2309 strftime(timeStr, _countof(timeStr), "%c", &timeInfo);
2310
2311 fprintf(file,
2312 "%s,%s,%s,"
2313 "BeginBytesToAllocate=%I64u MaxBytesToAllocate=%I64u AdditionalOperationCount=%u ThreadCount=%u FreeOrder=%d,"
2314 "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n",
2315 codeDescription,
2316 testDescription,
2317 timeStr,
2318 config.BeginBytesToAllocate, config.MaxBytesToAllocate, config.AdditionalOperationCount, config.ThreadCount, (uint32_t)config.FreeOrder,
2319 totalTimeSeconds * 1e6f,
2320 allocationTimeMinSeconds * 1e6f,
2321 allocationTimeAvgSeconds * 1e6f,
2322 allocationTimeMaxSeconds * 1e6f,
2323 deallocationTimeMinSeconds * 1e6f,
2324 deallocationTimeAvgSeconds * 1e6f,
2325 deallocationTimeMaxSeconds * 1e6f,
2326 result.TotalMemoryAllocated,
2327 result.FreeRangeSizeAvg,
2328 result.FreeRangeSizeMax);
2329}
2330
2331static void WritePoolTestResultHeader(FILE* file)
2332{
2333 fprintf(file,
2334 "Code,Test,Time,"
2335 "Config,"
2336 "Total Time (us),"
2337 "Allocation Time Min (us),"
2338 "Allocation Time Avg (us),"
2339 "Allocation Time Max (us),"
2340 "Deallocation Time Min (us),"
2341 "Deallocation Time Avg (us),"
2342 "Deallocation Time Max (us),"
2343 "Lost Allocation Count,"
2344 "Lost Allocation Total Size (B),"
2345 "Failed Allocation Count,"
2346 "Failed Allocation Total Size (B)\n");
2347}
2348
2349static void WritePoolTestResult(
2350 FILE* file,
2351 const char* codeDescription,
2352 const char* testDescription,
2353 const PoolTestConfig& config,
2354 const PoolTestResult& result)
2355{
2356 float totalTimeSeconds = ToFloatSeconds(result.TotalTime);
2357 float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin);
2358 float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg);
2359 float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax);
2360 float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin);
2361 float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg);
2362 float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax);
2363
2364 time_t rawTime; time(&rawTime);
2365 struct tm timeInfo; localtime_s(&timeInfo, &rawTime);
2366 char timeStr[128];
2367 strftime(timeStr, _countof(timeStr), "%c", &timeInfo);
2368
2369 fprintf(file,
2370 "%s,%s,%s,"
2371 "ThreadCount=%u PoolSize=%llu FrameCount=%u TotalItemCount=%u UsedItemCount=%u...%u ItemsToMakeUnusedPercent=%u,"
2372 "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u,%I64u\n",
2373 // General
2374 codeDescription,
2375 testDescription,
2376 timeStr,
2377 // Config
2378 config.ThreadCount,
2379 (unsigned long long)config.PoolSize,
2380 config.FrameCount,
2381 config.TotalItemCount,
2382 config.UsedItemCountMin,
2383 config.UsedItemCountMax,
2384 config.ItemsToMakeUnusedPercent,
2385 // Results
2386 totalTimeSeconds * 1e6f,
2387 allocationTimeMinSeconds * 1e6f,
2388 allocationTimeAvgSeconds * 1e6f,
2389 allocationTimeMaxSeconds * 1e6f,
2390 deallocationTimeMinSeconds * 1e6f,
2391 deallocationTimeAvgSeconds * 1e6f,
2392 deallocationTimeMaxSeconds * 1e6f,
2393 result.LostAllocationCount,
2394 result.LostAllocationTotalSize,
2395 result.FailedAllocationCount,
2396 result.FailedAllocationTotalSize);
2397}
2398
2399static void PerformCustomMainTest(FILE* file)
2400{
2401 Config config{};
2402 config.RandSeed = 65735476;
2403 //config.MaxBytesToAllocate = 4ull * 1024 * 1024; // 4 MB
2404 config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB
2405 config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY
2406 config.FreeOrder = FREE_ORDER::FORWARD;
2407 config.ThreadCount = 16;
2408 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
2409
2410 // Buffers
2411 //config.AllocationSizes.push_back({4, 16, 1024});
2412 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
2413
2414 // Images
2415 //config.AllocationSizes.push_back({4, 0, 0, 4, 32});
2416 //config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
2417
2418 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100;
2419 config.AdditionalOperationCount = 1024;
2420
2421 Result result{};
2422 VkResult res = MainTest(result, config);
2423 assert(res == VK_SUCCESS);
2424 WriteMainTestResult(file, "Foo", "CustomTest", config, result);
2425}
2426
2427static void PerformCustomPoolTest(FILE* file)
2428{
2429 PoolTestConfig config;
2430 config.PoolSize = 100 * 1024 * 1024;
2431 config.RandSeed = 2345764;
2432 config.ThreadCount = 1;
2433 config.FrameCount = 200;
2434 config.ItemsToMakeUnusedPercent = 2;
2435
2436 AllocationSize allocSize = {};
2437 allocSize.BufferSizeMin = 1024;
2438 allocSize.BufferSizeMax = 1024 * 1024;
2439 allocSize.Probability = 1;
2440 config.AllocationSizes.push_back(allocSize);
2441
2442 allocSize.BufferSizeMin = 0;
2443 allocSize.BufferSizeMax = 0;
2444 allocSize.ImageSizeMin = 128;
2445 allocSize.ImageSizeMax = 1024;
2446 allocSize.Probability = 1;
2447 config.AllocationSizes.push_back(allocSize);
2448
2449 config.PoolSize = config.CalcAvgResourceSize() * 200;
2450 config.UsedItemCountMax = 160;
2451 config.TotalItemCount = config.UsedItemCountMax * 10;
2452 config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100;
2453
2454 g_MemoryAliasingWarningEnabled = false;
2455 PoolTestResult result = {};
2456 TestPool_Benchmark(result, config);
2457 g_MemoryAliasingWarningEnabled = true;
2458
2459 WritePoolTestResult(file, "Code desc", "Test desc", config, result);
2460}
2461
2462enum CONFIG_TYPE {
2463 CONFIG_TYPE_MINIMUM,
2464 CONFIG_TYPE_SMALL,
2465 CONFIG_TYPE_AVERAGE,
2466 CONFIG_TYPE_LARGE,
2467 CONFIG_TYPE_MAXIMUM,
2468 CONFIG_TYPE_COUNT
2469};
2470
2471static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_SMALL;
2472//static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_LARGE;
2473static const char* CODE_DESCRIPTION = "Foo";
2474
2475static void PerformMainTests(FILE* file)
2476{
2477 uint32_t repeatCount = 1;
2478 if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3;
2479
2480 Config config{};
2481 config.RandSeed = 65735476;
2482 config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY
2483 config.FreeOrder = FREE_ORDER::FORWARD;
2484
2485 size_t threadCountCount = 1;
2486 switch(ConfigType)
2487 {
2488 case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break;
2489 case CONFIG_TYPE_SMALL: threadCountCount = 2; break;
2490 case CONFIG_TYPE_AVERAGE: threadCountCount = 3; break;
2491 case CONFIG_TYPE_LARGE: threadCountCount = 5; break;
2492 case CONFIG_TYPE_MAXIMUM: threadCountCount = 7; break;
2493 default: assert(0);
2494 }
2495 for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex)
2496 {
2497 std::string desc1;
2498
2499 switch(threadCountIndex)
2500 {
2501 case 0:
2502 desc1 += "1_thread";
2503 config.ThreadCount = 1;
2504 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
2505 break;
2506 case 1:
2507 desc1 += "16_threads+0%_common";
2508 config.ThreadCount = 16;
2509 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
2510 break;
2511 case 2:
2512 desc1 += "16_threads+50%_common";
2513 config.ThreadCount = 16;
2514 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
2515 break;
2516 case 3:
2517 desc1 += "16_threads+100%_common";
2518 config.ThreadCount = 16;
2519 config.ThreadsUsingCommonAllocationsProbabilityPercent = 100;
2520 break;
2521 case 4:
2522 desc1 += "2_threads+0%_common";
2523 config.ThreadCount = 2;
2524 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
2525 break;
2526 case 5:
2527 desc1 += "2_threads+50%_common";
2528 config.ThreadCount = 2;
2529 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
2530 break;
2531 case 6:
2532 desc1 += "2_threads+100%_common";
2533 config.ThreadCount = 2;
2534 config.ThreadsUsingCommonAllocationsProbabilityPercent = 100;
2535 break;
2536 default:
2537 assert(0);
2538 }
2539
2540 // 0 = buffers, 1 = images, 2 = buffers and images
2541 size_t buffersVsImagesCount = 2;
2542 if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount;
2543 for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex)
2544 {
2545 std::string desc2 = desc1;
2546 switch(buffersVsImagesIndex)
2547 {
2548 case 0: desc2 += " Buffers"; break;
2549 case 1: desc2 += " Images"; break;
2550 case 2: desc2 += " Buffers+Images"; break;
2551 default: assert(0);
2552 }
2553
2554 // 0 = small, 1 = large, 2 = small and large
2555 size_t smallVsLargeCount = 2;
2556 if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount;
2557 for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex)
2558 {
2559 std::string desc3 = desc2;
2560 switch(smallVsLargeIndex)
2561 {
2562 case 0: desc3 += " Small"; break;
2563 case 1: desc3 += " Large"; break;
2564 case 2: desc3 += " Small+Large"; break;
2565 default: assert(0);
2566 }
2567
2568 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2569 config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB
2570 else
2571 config.MaxBytesToAllocate = 4ull * 1024 * 1024;
2572
2573 // 0 = varying sizes min...max, 1 = set of constant sizes
2574 size_t constantSizesCount = 1;
2575 if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount;
2576 for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex)
2577 {
2578 std::string desc4 = desc3;
2579 switch(constantSizesIndex)
2580 {
2581 case 0: desc4 += " Varying_sizes"; break;
2582 case 1: desc4 += " Constant_sizes"; break;
2583 default: assert(0);
2584 }
2585
2586 config.AllocationSizes.clear();
2587 // Buffers present
2588 if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
2589 {
2590 // Small
2591 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
2592 {
2593 // Varying size
2594 if(constantSizesIndex == 0)
2595 config.AllocationSizes.push_back({4, 16, 1024});
2596 // Constant sizes
2597 else
2598 {
2599 config.AllocationSizes.push_back({1, 16, 16});
2600 config.AllocationSizes.push_back({1, 64, 64});
2601 config.AllocationSizes.push_back({1, 256, 256});
2602 config.AllocationSizes.push_back({1, 1024, 1024});
2603 }
2604 }
2605 // Large
2606 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2607 {
2608 // Varying size
2609 if(constantSizesIndex == 0)
2610 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
2611 // Constant sizes
2612 else
2613 {
2614 config.AllocationSizes.push_back({1, 0x10000, 0x10000});
2615 config.AllocationSizes.push_back({1, 0x80000, 0x80000});
2616 config.AllocationSizes.push_back({1, 0x200000, 0x200000});
2617 config.AllocationSizes.push_back({1, 0xA00000, 0xA00000});
2618 }
2619 }
2620 }
2621 // Images present
2622 if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
2623 {
2624 // Small
2625 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
2626 {
2627 // Varying size
2628 if(constantSizesIndex == 0)
2629 config.AllocationSizes.push_back({4, 0, 0, 4, 32});
2630 // Constant sizes
2631 else
2632 {
2633 config.AllocationSizes.push_back({1, 0, 0, 4, 4});
2634 config.AllocationSizes.push_back({1, 0, 0, 8, 8});
2635 config.AllocationSizes.push_back({1, 0, 0, 16, 16});
2636 config.AllocationSizes.push_back({1, 0, 0, 32, 32});
2637 }
2638 }
2639 // Large
2640 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2641 {
2642 // Varying size
2643 if(constantSizesIndex == 0)
2644 config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
2645 // Constant sizes
2646 else
2647 {
2648 config.AllocationSizes.push_back({1, 0, 0, 256, 256});
2649 config.AllocationSizes.push_back({1, 0, 0, 512, 512});
2650 config.AllocationSizes.push_back({1, 0, 0, 1024, 1024});
2651 config.AllocationSizes.push_back({1, 0, 0, 2048, 2048});
2652 }
2653 }
2654 }
2655
2656 // 0 = 100%, additional_operations = 0, 1 = 50%, 2 = 5%, 3 = 95% additional_operations = a lot
2657 size_t beginBytesToAllocateCount = 1;
2658 if(ConfigType >= CONFIG_TYPE_SMALL) ++beginBytesToAllocateCount;
2659 if(ConfigType >= CONFIG_TYPE_AVERAGE) ++beginBytesToAllocateCount;
2660 if(ConfigType >= CONFIG_TYPE_LARGE) ++beginBytesToAllocateCount;
2661 for(size_t beginBytesToAllocateIndex = 0; beginBytesToAllocateIndex < beginBytesToAllocateCount; ++beginBytesToAllocateIndex)
2662 {
2663 std::string desc5 = desc4;
2664
2665 switch(beginBytesToAllocateIndex)
2666 {
2667 case 0:
2668 desc5 += " Allocate_100%";
2669 config.BeginBytesToAllocate = config.MaxBytesToAllocate;
2670 config.AdditionalOperationCount = 0;
2671 break;
2672 case 1:
2673 desc5 += " Allocate_50%+Operations";
2674 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 50 / 100;
2675 config.AdditionalOperationCount = 1024;
2676 break;
2677 case 2:
2678 desc5 += " Allocate_5%+Operations";
2679 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100;
2680 config.AdditionalOperationCount = 1024;
2681 break;
2682 case 3:
2683 desc5 += " Allocate_95%+Operations";
2684 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 95 / 100;
2685 config.AdditionalOperationCount = 1024;
2686 break;
2687 default:
2688 assert(0);
2689 }
2690
2691 const char* testDescription = desc5.c_str();
2692
2693 for(size_t repeat = 0; repeat < repeatCount; ++repeat)
2694 {
2695 printf("%s Repeat %u\n", testDescription, (uint32_t)repeat);
2696
2697 Result result{};
2698 VkResult res = MainTest(result, config);
2699 assert(res == VK_SUCCESS);
2700 WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
2701 }
2702 }
2703 }
2704 }
2705 }
2706 }
2707}
2708
2709static void PerformPoolTests(FILE* file)
2710{
2711 const size_t AVG_RESOURCES_PER_POOL = 300;
2712
2713 uint32_t repeatCount = 1;
2714 if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3;
2715
2716 PoolTestConfig config{};
2717 config.RandSeed = 2346343;
2718 config.FrameCount = 200;
2719 config.ItemsToMakeUnusedPercent = 2;
2720
2721 size_t threadCountCount = 1;
2722 switch(ConfigType)
2723 {
2724 case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break;
2725 case CONFIG_TYPE_SMALL: threadCountCount = 2; break;
2726 case CONFIG_TYPE_AVERAGE: threadCountCount = 2; break;
2727 case CONFIG_TYPE_LARGE: threadCountCount = 3; break;
2728 case CONFIG_TYPE_MAXIMUM: threadCountCount = 3; break;
2729 default: assert(0);
2730 }
2731 for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex)
2732 {
2733 std::string desc1;
2734
2735 switch(threadCountIndex)
2736 {
2737 case 0:
2738 desc1 += "1_thread";
2739 config.ThreadCount = 1;
2740 break;
2741 case 1:
2742 desc1 += "16_threads";
2743 config.ThreadCount = 16;
2744 break;
2745 case 2:
2746 desc1 += "2_threads";
2747 config.ThreadCount = 2;
2748 break;
2749 default:
2750 assert(0);
2751 }
2752
2753 // 0 = buffers, 1 = images, 2 = buffers and images
2754 size_t buffersVsImagesCount = 2;
2755 if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount;
2756 for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex)
2757 {
2758 std::string desc2 = desc1;
2759 switch(buffersVsImagesIndex)
2760 {
2761 case 0: desc2 += " Buffers"; break;
2762 case 1: desc2 += " Images"; break;
2763 case 2: desc2 += " Buffers+Images"; break;
2764 default: assert(0);
2765 }
2766
2767 // 0 = small, 1 = large, 2 = small and large
2768 size_t smallVsLargeCount = 2;
2769 if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount;
2770 for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex)
2771 {
2772 std::string desc3 = desc2;
2773 switch(smallVsLargeIndex)
2774 {
2775 case 0: desc3 += " Small"; break;
2776 case 1: desc3 += " Large"; break;
2777 case 2: desc3 += " Small+Large"; break;
2778 default: assert(0);
2779 }
2780
2781 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2782 config.PoolSize = 6ull * 1024 * 1024 * 1024; // 6 GB
2783 else
2784 config.PoolSize = 4ull * 1024 * 1024;
2785
2786 // 0 = varying sizes min...max, 1 = set of constant sizes
2787 size_t constantSizesCount = 1;
2788 if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount;
2789 for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex)
2790 {
2791 std::string desc4 = desc3;
2792 switch(constantSizesIndex)
2793 {
2794 case 0: desc4 += " Varying_sizes"; break;
2795 case 1: desc4 += " Constant_sizes"; break;
2796 default: assert(0);
2797 }
2798
2799 config.AllocationSizes.clear();
2800 // Buffers present
2801 if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
2802 {
2803 // Small
2804 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
2805 {
2806 // Varying size
2807 if(constantSizesIndex == 0)
2808 config.AllocationSizes.push_back({4, 16, 1024});
2809 // Constant sizes
2810 else
2811 {
2812 config.AllocationSizes.push_back({1, 16, 16});
2813 config.AllocationSizes.push_back({1, 64, 64});
2814 config.AllocationSizes.push_back({1, 256, 256});
2815 config.AllocationSizes.push_back({1, 1024, 1024});
2816 }
2817 }
2818 // Large
2819 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2820 {
2821 // Varying size
2822 if(constantSizesIndex == 0)
2823 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
2824 // Constant sizes
2825 else
2826 {
2827 config.AllocationSizes.push_back({1, 0x10000, 0x10000});
2828 config.AllocationSizes.push_back({1, 0x80000, 0x80000});
2829 config.AllocationSizes.push_back({1, 0x200000, 0x200000});
2830 config.AllocationSizes.push_back({1, 0xA00000, 0xA00000});
2831 }
2832 }
2833 }
2834 // Images present
2835 if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
2836 {
2837 // Small
2838 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
2839 {
2840 // Varying size
2841 if(constantSizesIndex == 0)
2842 config.AllocationSizes.push_back({4, 0, 0, 4, 32});
2843 // Constant sizes
2844 else
2845 {
2846 config.AllocationSizes.push_back({1, 0, 0, 4, 4});
2847 config.AllocationSizes.push_back({1, 0, 0, 8, 8});
2848 config.AllocationSizes.push_back({1, 0, 0, 16, 16});
2849 config.AllocationSizes.push_back({1, 0, 0, 32, 32});
2850 }
2851 }
2852 // Large
2853 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2854 {
2855 // Varying size
2856 if(constantSizesIndex == 0)
2857 config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
2858 // Constant sizes
2859 else
2860 {
2861 config.AllocationSizes.push_back({1, 0, 0, 256, 256});
2862 config.AllocationSizes.push_back({1, 0, 0, 512, 512});
2863 config.AllocationSizes.push_back({1, 0, 0, 1024, 1024});
2864 config.AllocationSizes.push_back({1, 0, 0, 2048, 2048});
2865 }
2866 }
2867 }
2868
2869 const VkDeviceSize avgResourceSize = config.CalcAvgResourceSize();
2870 config.PoolSize = avgResourceSize * AVG_RESOURCES_PER_POOL;
2871
2872 // 0 = 66%, 1 = 133%, 2 = 100%, 3 = 33%, 4 = 166%
2873 size_t subscriptionModeCount;
2874 switch(ConfigType)
2875 {
2876 case CONFIG_TYPE_MINIMUM: subscriptionModeCount = 2; break;
2877 case CONFIG_TYPE_SMALL: subscriptionModeCount = 2; break;
2878 case CONFIG_TYPE_AVERAGE: subscriptionModeCount = 3; break;
2879 case CONFIG_TYPE_LARGE: subscriptionModeCount = 5; break;
2880 case CONFIG_TYPE_MAXIMUM: subscriptionModeCount = 5; break;
2881 default: assert(0);
2882 }
2883 for(size_t subscriptionModeIndex = 0; subscriptionModeIndex < subscriptionModeCount; ++subscriptionModeIndex)
2884 {
2885 std::string desc5 = desc4;
2886
2887 switch(subscriptionModeIndex)
2888 {
2889 case 0:
2890 desc5 += " Subscription_66%";
2891 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 66 / 100;
2892 break;
2893 case 1:
2894 desc5 += " Subscription_133%";
2895 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 133 / 100;
2896 break;
2897 case 2:
2898 desc5 += " Subscription_100%";
2899 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL;
2900 break;
2901 case 3:
2902 desc5 += " Subscription_33%";
2903 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 33 / 100;
2904 break;
2905 case 4:
2906 desc5 += " Subscription_166%";
2907 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 166 / 100;
2908 break;
2909 default:
2910 assert(0);
2911 }
2912
2913 config.TotalItemCount = config.UsedItemCountMax * 5;
2914 config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100;
2915
2916 const char* testDescription = desc5.c_str();
2917
2918 for(size_t repeat = 0; repeat < repeatCount; ++repeat)
2919 {
2920 printf("%s Repeat %u\n", testDescription, (uint32_t)repeat);
2921
2922 PoolTestResult result{};
2923 g_MemoryAliasingWarningEnabled = false;
2924 TestPool_Benchmark(result, config);
2925 g_MemoryAliasingWarningEnabled = true;
2926 WritePoolTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
2927 }
2928 }
2929 }
2930 }
2931 }
2932 }
2933}
2934
2935void Test()
2936{
2937 wprintf(L"TESTING:\n");
2938
2939 // # Simple tests
2940
2941 TestBasics();
2942 TestPool_SameSize();
2943 TestHeapSizeLimit();
2944 TestMapping();
2945 TestMappingMultithreaded();
2946 TestDefragmentationSimple();
2947 TestDefragmentationFull();
2948
2949 // # Detailed tests
2950 FILE* file;
2951 fopen_s(&file, "Results.csv", "w");
2952 assert(file != NULL);
2953
2954 WriteMainTestResultHeader(file);
2955 PerformMainTests(file);
2956 //PerformCustomMainTest(file);
2957
2958 WritePoolTestResultHeader(file);
2959 PerformPoolTests(file);
2960 //PerformCustomPoolTest(file);
2961
2962 fclose(file);
2963
2964 wprintf(L"Done.\n");
2965}
2966
Adam Sawickif1a793c2018-03-13 15:42:22 +01002967#endif // #ifdef _WIN32