blob: c408b0f8d611230bc484e369a29c9adc90e6279d [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
Adam Sawickie44c6262018-06-15 14:30:39 +0200561static void SaveAllocatorStatsToFile(const wchar_t* filePath)
Adam Sawickib8333fb2018-03-13 16:15:53 +0100562{
563 char* stats;
Adam Sawickie44c6262018-06-15 14:30:39 +0200564 vmaBuildStatsString(g_hAllocator, &stats, VK_TRUE);
Adam Sawickib8333fb2018-03-13 16:15:53 +0100565 SaveFile(filePath, stats, strlen(stats));
Adam Sawickie44c6262018-06-15 14:30:39 +0200566 vmaFreeStatsString(g_hAllocator, stats);
Adam Sawickib8333fb2018-03-13 16:15:53 +0100567}
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
Adam Sawickie44c6262018-06-15 14:30:39 +0200985 SaveAllocatorStatsToFile(L"Before.csv");
Adam Sawickib8333fb2018-03-13 16:15:53 +0100986
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);
Adam Sawickie44c6262018-06-15 14:30:39 +02001036 SaveAllocatorStatsToFile(fileName);
Adam Sawickib8333fb2018-03-13 16:15:53 +01001037 }
1038 }
1039
1040 // Destroy all remaining allocations.
1041 DestroyAllAllocations(allocations);
1042}
1043
1044static void TestUserData()
1045{
1046 VkResult res;
1047
1048 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1049 bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
1050 bufCreateInfo.size = 0x10000;
1051
1052 for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
1053 {
1054 // Opaque pointer
1055 {
1056
1057 void* numberAsPointer = (void*)(size_t)0xC2501FF3u;
1058 void* pointerToSomething = &res;
1059
1060 VmaAllocationCreateInfo allocCreateInfo = {};
1061 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1062 allocCreateInfo.pUserData = numberAsPointer;
1063 if(testIndex == 1)
1064 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
1065
1066 VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo;
1067 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1068 assert(res == VK_SUCCESS);
1069 assert(allocInfo.pUserData = numberAsPointer);
1070
1071 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1072 assert(allocInfo.pUserData == numberAsPointer);
1073
1074 vmaSetAllocationUserData(g_hAllocator, alloc, pointerToSomething);
1075 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1076 assert(allocInfo.pUserData == pointerToSomething);
1077
1078 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1079 }
1080
1081 // String
1082 {
1083 const char* name1 = "Buffer name \\\"\'<>&% \nSecond line .,;=";
1084 const char* name2 = "2";
1085 const size_t name1Len = strlen(name1);
1086
1087 char* name1Buf = new char[name1Len + 1];
1088 strcpy_s(name1Buf, name1Len + 1, name1);
1089
1090 VmaAllocationCreateInfo allocCreateInfo = {};
1091 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1092 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
1093 allocCreateInfo.pUserData = name1Buf;
1094 if(testIndex == 1)
1095 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
1096
1097 VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo;
1098 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1099 assert(res == VK_SUCCESS);
1100 assert(allocInfo.pUserData != nullptr && allocInfo.pUserData != name1Buf);
1101 assert(strcmp(name1, (const char*)allocInfo.pUserData) == 0);
1102
1103 delete[] name1Buf;
1104
1105 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1106 assert(strcmp(name1, (const char*)allocInfo.pUserData) == 0);
1107
1108 vmaSetAllocationUserData(g_hAllocator, alloc, (void*)name2);
1109 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1110 assert(strcmp(name2, (const char*)allocInfo.pUserData) == 0);
1111
1112 vmaSetAllocationUserData(g_hAllocator, alloc, nullptr);
1113 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1114 assert(allocInfo.pUserData == nullptr);
1115
1116 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1117 }
1118 }
1119}
1120
1121static void TestMemoryRequirements()
1122{
1123 VkResult res;
1124 VkBuffer buf;
1125 VmaAllocation alloc;
1126 VmaAllocationInfo allocInfo;
1127
1128 const VkPhysicalDeviceMemoryProperties* memProps;
1129 vmaGetMemoryProperties(g_hAllocator, &memProps);
1130
1131 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1132 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
1133 bufInfo.size = 128;
1134
1135 VmaAllocationCreateInfo allocCreateInfo = {};
1136
1137 // No requirements.
1138 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1139 assert(res == VK_SUCCESS);
1140 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1141
1142 // Usage.
1143 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1144 allocCreateInfo.requiredFlags = 0;
1145 allocCreateInfo.preferredFlags = 0;
1146 allocCreateInfo.memoryTypeBits = UINT32_MAX;
1147
1148 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1149 assert(res == VK_SUCCESS);
1150 assert(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
1151 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1152
1153 // Required flags, preferred flags.
1154 allocCreateInfo.usage = VMA_MEMORY_USAGE_UNKNOWN;
1155 allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
1156 allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
1157 allocCreateInfo.memoryTypeBits = 0;
1158
1159 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1160 assert(res == VK_SUCCESS);
1161 assert(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
1162 assert(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
1163 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1164
1165 // memoryTypeBits.
1166 const uint32_t memType = allocInfo.memoryType;
1167 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1168 allocCreateInfo.requiredFlags = 0;
1169 allocCreateInfo.preferredFlags = 0;
1170 allocCreateInfo.memoryTypeBits = 1u << memType;
1171
1172 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1173 assert(res == VK_SUCCESS);
1174 assert(allocInfo.memoryType == memType);
1175 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1176
1177}
1178
1179static void TestBasics()
1180{
1181 VkResult res;
1182
1183 TestMemoryRequirements();
1184
1185 // Lost allocation
1186 {
1187 VmaAllocation alloc = VK_NULL_HANDLE;
1188 vmaCreateLostAllocation(g_hAllocator, &alloc);
1189 assert(alloc != VK_NULL_HANDLE);
1190
1191 VmaAllocationInfo allocInfo;
1192 vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
1193 assert(allocInfo.deviceMemory == VK_NULL_HANDLE);
1194 assert(allocInfo.size == 0);
1195
1196 vmaFreeMemory(g_hAllocator, alloc);
1197 }
1198
1199 // Allocation that is MAPPED and not necessarily HOST_VISIBLE.
1200 {
1201 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1202 bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
1203 bufCreateInfo.size = 128;
1204
1205 VmaAllocationCreateInfo allocCreateInfo = {};
1206 allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1207 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
1208
1209 VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo;
1210 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1211 assert(res == VK_SUCCESS);
1212
1213 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1214
1215 // Same with OWN_MEMORY.
1216 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
1217
1218 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
1219 assert(res == VK_SUCCESS);
1220
1221 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1222 }
1223
1224 TestUserData();
1225}
1226
1227void TestHeapSizeLimit()
1228{
1229 const VkDeviceSize HEAP_SIZE_LIMIT = 1ull * 1024 * 1024 * 1024; // 1 GB
1230 const VkDeviceSize BLOCK_SIZE = 128ull * 1024 * 1024; // 128 MB
1231
1232 VkDeviceSize heapSizeLimit[VK_MAX_MEMORY_HEAPS];
1233 for(uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i)
1234 {
1235 heapSizeLimit[i] = HEAP_SIZE_LIMIT;
1236 }
1237
1238 VmaAllocatorCreateInfo allocatorCreateInfo = {};
1239 allocatorCreateInfo.physicalDevice = g_hPhysicalDevice;
1240 allocatorCreateInfo.device = g_hDevice;
1241 allocatorCreateInfo.pHeapSizeLimit = heapSizeLimit;
1242
1243 VmaAllocator hAllocator;
1244 VkResult res = vmaCreateAllocator(&allocatorCreateInfo, &hAllocator);
1245 assert(res == VK_SUCCESS);
1246
1247 struct Item
1248 {
1249 VkBuffer hBuf;
1250 VmaAllocation hAlloc;
1251 };
1252 std::vector<Item> items;
1253
1254 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1255 bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
1256
1257 // 1. Allocate two blocks of Own Memory, half the size of BLOCK_SIZE.
1258 VmaAllocationInfo ownAllocInfo;
1259 {
1260 VmaAllocationCreateInfo allocCreateInfo = {};
1261 allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1262 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
1263
1264 bufCreateInfo.size = BLOCK_SIZE / 2;
1265
1266 for(size_t i = 0; i < 2; ++i)
1267 {
1268 Item item;
1269 res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, &ownAllocInfo);
1270 assert(res == VK_SUCCESS);
1271 items.push_back(item);
1272 }
1273 }
1274
1275 // Create pool to make sure allocations must be out of this memory type.
1276 VmaPoolCreateInfo poolCreateInfo = {};
1277 poolCreateInfo.memoryTypeIndex = ownAllocInfo.memoryType;
1278 poolCreateInfo.blockSize = BLOCK_SIZE;
1279
1280 VmaPool hPool;
1281 res = vmaCreatePool(hAllocator, &poolCreateInfo, &hPool);
1282 assert(res == VK_SUCCESS);
1283
1284 // 2. Allocate normal buffers from all the remaining memory.
1285 {
1286 VmaAllocationCreateInfo allocCreateInfo = {};
1287 allocCreateInfo.pool = hPool;
1288
1289 bufCreateInfo.size = BLOCK_SIZE / 2;
1290
1291 const size_t bufCount = ((HEAP_SIZE_LIMIT / BLOCK_SIZE) - 1) * 2;
1292 for(size_t i = 0; i < bufCount; ++i)
1293 {
1294 Item item;
1295 res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, nullptr);
1296 assert(res == VK_SUCCESS);
1297 items.push_back(item);
1298 }
1299 }
1300
1301 // 3. Allocation of one more (even small) buffer should fail.
1302 {
1303 VmaAllocationCreateInfo allocCreateInfo = {};
1304 allocCreateInfo.pool = hPool;
1305
1306 bufCreateInfo.size = 128;
1307
1308 VkBuffer hBuf;
1309 VmaAllocation hAlloc;
1310 res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &hBuf, &hAlloc, nullptr);
1311 assert(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
1312 }
1313
1314 // Destroy everything.
1315 for(size_t i = items.size(); i--; )
1316 {
1317 vmaDestroyBuffer(hAllocator, items[i].hBuf, items[i].hAlloc);
1318 }
1319
1320 vmaDestroyPool(hAllocator, hPool);
1321
1322 vmaDestroyAllocator(hAllocator);
1323}
1324
Adam Sawicki212a4a62018-06-14 15:44:45 +02001325#if VMA_DEBUG_MARGIN
Adam Sawicki73b16652018-06-11 16:39:25 +02001326static void TestDebugMargin()
1327{
1328 if(VMA_DEBUG_MARGIN == 0)
1329 {
1330 return;
1331 }
1332
1333 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
Adam Sawicki212a4a62018-06-14 15:44:45 +02001334 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
Adam Sawicki73b16652018-06-11 16:39:25 +02001335
1336 VmaAllocationCreateInfo allocCreateInfo = {};
Adam Sawicki212a4a62018-06-14 15:44:45 +02001337 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
Adam Sawicki73b16652018-06-11 16:39:25 +02001338
1339 // Create few buffers of different size.
1340 const size_t BUF_COUNT = 10;
1341 BufferInfo buffers[BUF_COUNT];
1342 VmaAllocationInfo allocInfo[BUF_COUNT];
1343 for(size_t i = 0; i < 10; ++i)
1344 {
1345 bufInfo.size = (VkDeviceSize)(i + 1) * 64;
Adam Sawicki212a4a62018-06-14 15:44:45 +02001346 // Last one will be mapped.
1347 allocCreateInfo.flags = (i == BUF_COUNT - 1) ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0;
Adam Sawicki73b16652018-06-11 16:39:25 +02001348
1349 VkResult res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buffers[i].Buffer, &buffers[i].Allocation, &allocInfo[i]);
1350 assert(res == VK_SUCCESS);
1351 // Margin is preserved also at the beginning of a block.
1352 assert(allocInfo[i].offset >= VMA_DEBUG_MARGIN);
Adam Sawicki212a4a62018-06-14 15:44:45 +02001353
1354 if(i == BUF_COUNT - 1)
1355 {
1356 // Fill with data.
1357 assert(allocInfo[i].pMappedData != nullptr);
1358 // Uncomment this "+ 1" to overwrite past end of allocation and check corruption detection.
1359 memset(allocInfo[i].pMappedData, 0xFF, bufInfo.size /* + 1 */);
1360 }
Adam Sawicki73b16652018-06-11 16:39:25 +02001361 }
1362
1363 // Check if their offsets preserve margin between them.
1364 std::sort(allocInfo, allocInfo + BUF_COUNT, [](const VmaAllocationInfo& lhs, const VmaAllocationInfo& rhs) -> bool
1365 {
1366 if(lhs.deviceMemory != rhs.deviceMemory)
1367 {
1368 return lhs.deviceMemory < rhs.deviceMemory;
1369 }
1370 return lhs.offset < rhs.offset;
1371 });
1372 for(size_t i = 1; i < BUF_COUNT; ++i)
1373 {
1374 if(allocInfo[i].deviceMemory == allocInfo[i - 1].deviceMemory)
1375 {
1376 assert(allocInfo[i].offset >= allocInfo[i - 1].offset + VMA_DEBUG_MARGIN);
1377 }
1378 }
1379
Adam Sawicki212a4a62018-06-14 15:44:45 +02001380 VkResult res = vmaCheckCorruption(g_hAllocator, UINT32_MAX);
1381 assert(res == VK_SUCCESS);
1382
Adam Sawicki73b16652018-06-11 16:39:25 +02001383 // Destroy all buffers.
1384 for(size_t i = BUF_COUNT; i--; )
1385 {
1386 vmaDestroyBuffer(g_hAllocator, buffers[i].Buffer, buffers[i].Allocation);
1387 }
1388}
Adam Sawicki212a4a62018-06-14 15:44:45 +02001389#endif
Adam Sawicki73b16652018-06-11 16:39:25 +02001390
Adam Sawickib8333fb2018-03-13 16:15:53 +01001391static void TestPool_SameSize()
1392{
1393 const VkDeviceSize BUF_SIZE = 1024 * 1024;
1394 const size_t BUF_COUNT = 100;
1395 VkResult res;
1396
1397 RandomNumberGenerator rand{123};
1398
1399 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1400 bufferInfo.size = BUF_SIZE;
1401 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1402
1403 uint32_t memoryTypeBits = UINT32_MAX;
1404 {
1405 VkBuffer dummyBuffer;
1406 res = vkCreateBuffer(g_hDevice, &bufferInfo, nullptr, &dummyBuffer);
1407 assert(res == VK_SUCCESS);
1408
1409 VkMemoryRequirements memReq;
1410 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
1411 memoryTypeBits = memReq.memoryTypeBits;
1412
1413 vkDestroyBuffer(g_hDevice, dummyBuffer, nullptr);
1414 }
1415
1416 VmaAllocationCreateInfo poolAllocInfo = {};
1417 poolAllocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1418 uint32_t memTypeIndex;
1419 res = vmaFindMemoryTypeIndex(
1420 g_hAllocator,
1421 memoryTypeBits,
1422 &poolAllocInfo,
1423 &memTypeIndex);
1424
1425 VmaPoolCreateInfo poolCreateInfo = {};
1426 poolCreateInfo.memoryTypeIndex = memTypeIndex;
1427 poolCreateInfo.blockSize = BUF_SIZE * BUF_COUNT / 4;
1428 poolCreateInfo.minBlockCount = 1;
1429 poolCreateInfo.maxBlockCount = 4;
1430 poolCreateInfo.frameInUseCount = 0;
1431
1432 VmaPool pool;
1433 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
1434 assert(res == VK_SUCCESS);
1435
1436 vmaSetCurrentFrameIndex(g_hAllocator, 1);
1437
1438 VmaAllocationCreateInfo allocInfo = {};
1439 allocInfo.pool = pool;
1440 allocInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT |
1441 VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT;
1442
1443 struct BufItem
1444 {
1445 VkBuffer Buf;
1446 VmaAllocation Alloc;
1447 };
1448 std::vector<BufItem> items;
1449
1450 // Fill entire pool.
1451 for(size_t i = 0; i < BUF_COUNT; ++i)
1452 {
1453 BufItem item;
1454 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1455 assert(res == VK_SUCCESS);
1456 items.push_back(item);
1457 }
1458
1459 // Make sure that another allocation would fail.
1460 {
1461 BufItem item;
1462 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1463 assert(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
1464 }
1465
1466 // Validate that no buffer is lost. Also check that they are not mapped.
1467 for(size_t i = 0; i < items.size(); ++i)
1468 {
1469 VmaAllocationInfo allocInfo;
1470 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1471 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
1472 assert(allocInfo.pMappedData == nullptr);
1473 }
1474
1475 // Free some percent of random items.
1476 {
1477 const size_t PERCENT_TO_FREE = 10;
1478 size_t itemsToFree = items.size() * PERCENT_TO_FREE / 100;
1479 for(size_t i = 0; i < itemsToFree; ++i)
1480 {
1481 size_t index = (size_t)rand.Generate() % items.size();
1482 vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc);
1483 items.erase(items.begin() + index);
1484 }
1485 }
1486
1487 // Randomly allocate and free items.
1488 {
1489 const size_t OPERATION_COUNT = BUF_COUNT;
1490 for(size_t i = 0; i < OPERATION_COUNT; ++i)
1491 {
1492 bool allocate = rand.Generate() % 2 != 0;
1493 if(allocate)
1494 {
1495 if(items.size() < BUF_COUNT)
1496 {
1497 BufItem item;
1498 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1499 assert(res == VK_SUCCESS);
1500 items.push_back(item);
1501 }
1502 }
1503 else // Free
1504 {
1505 if(!items.empty())
1506 {
1507 size_t index = (size_t)rand.Generate() % items.size();
1508 vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc);
1509 items.erase(items.begin() + index);
1510 }
1511 }
1512 }
1513 }
1514
1515 // Allocate up to maximum.
1516 while(items.size() < BUF_COUNT)
1517 {
1518 BufItem item;
1519 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1520 assert(res == VK_SUCCESS);
1521 items.push_back(item);
1522 }
1523
1524 // Validate that no buffer is lost.
1525 for(size_t i = 0; i < items.size(); ++i)
1526 {
1527 VmaAllocationInfo allocInfo;
1528 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1529 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
1530 }
1531
1532 // Next frame.
1533 vmaSetCurrentFrameIndex(g_hAllocator, 2);
1534
1535 // Allocate another BUF_COUNT buffers.
1536 for(size_t i = 0; i < BUF_COUNT; ++i)
1537 {
1538 BufItem item;
1539 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1540 assert(res == VK_SUCCESS);
1541 items.push_back(item);
1542 }
1543
1544 // Make sure the first BUF_COUNT is lost. Delete them.
1545 for(size_t i = 0; i < BUF_COUNT; ++i)
1546 {
1547 VmaAllocationInfo allocInfo;
1548 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1549 assert(allocInfo.deviceMemory == VK_NULL_HANDLE);
1550 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1551 }
1552 items.erase(items.begin(), items.begin() + BUF_COUNT);
1553
1554 // Validate that no buffer is lost.
1555 for(size_t i = 0; i < items.size(); ++i)
1556 {
1557 VmaAllocationInfo allocInfo;
1558 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1559 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
1560 }
1561
1562 // Free one item.
1563 vmaDestroyBuffer(g_hAllocator, items.back().Buf, items.back().Alloc);
1564 items.pop_back();
1565
1566 // Validate statistics.
1567 {
1568 VmaPoolStats poolStats = {};
1569 vmaGetPoolStats(g_hAllocator, pool, &poolStats);
1570 assert(poolStats.allocationCount == items.size());
1571 assert(poolStats.size = BUF_COUNT * BUF_SIZE);
1572 assert(poolStats.unusedRangeCount == 1);
1573 assert(poolStats.unusedRangeSizeMax == BUF_SIZE);
1574 assert(poolStats.unusedSize == BUF_SIZE);
1575 }
1576
1577 // Free all remaining items.
1578 for(size_t i = items.size(); i--; )
1579 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1580 items.clear();
1581
1582 // Allocate maximum items again.
1583 for(size_t i = 0; i < BUF_COUNT; ++i)
1584 {
1585 BufItem item;
1586 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1587 assert(res == VK_SUCCESS);
1588 items.push_back(item);
1589 }
1590
1591 // Delete every other item.
1592 for(size_t i = 0; i < BUF_COUNT / 2; ++i)
1593 {
1594 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1595 items.erase(items.begin() + i);
1596 }
1597
1598 // Defragment!
1599 {
1600 std::vector<VmaAllocation> allocationsToDefragment(items.size());
1601 for(size_t i = 0; i < items.size(); ++i)
1602 allocationsToDefragment[i] = items[i].Alloc;
1603
1604 VmaDefragmentationStats defragmentationStats;
1605 res = vmaDefragment(g_hAllocator, allocationsToDefragment.data(), items.size(), nullptr, nullptr, &defragmentationStats);
1606 assert(res == VK_SUCCESS);
1607 assert(defragmentationStats.deviceMemoryBlocksFreed == 2);
1608 }
1609
1610 // Free all remaining items.
1611 for(size_t i = items.size(); i--; )
1612 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1613 items.clear();
1614
1615 ////////////////////////////////////////////////////////////////////////////////
1616 // Test for vmaMakePoolAllocationsLost
1617
1618 // Allocate 4 buffers on frame 10.
1619 vmaSetCurrentFrameIndex(g_hAllocator, 10);
1620 for(size_t i = 0; i < 4; ++i)
1621 {
1622 BufItem item;
1623 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
1624 assert(res == VK_SUCCESS);
1625 items.push_back(item);
1626 }
1627
1628 // Touch first 2 of them on frame 11.
1629 vmaSetCurrentFrameIndex(g_hAllocator, 11);
1630 for(size_t i = 0; i < 2; ++i)
1631 {
1632 VmaAllocationInfo allocInfo;
1633 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
1634 }
1635
1636 // vmaMakePoolAllocationsLost. Only remaining 2 should be lost.
1637 size_t lostCount = 0xDEADC0DE;
1638 vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount);
1639 assert(lostCount == 2);
1640
1641 // Make another call. Now 0 should be lost.
1642 vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount);
1643 assert(lostCount == 0);
1644
1645 // Make another call, with null count. Should not crash.
1646 vmaMakePoolAllocationsLost(g_hAllocator, pool, nullptr);
1647
1648 // END: Free all remaining items.
1649 for(size_t i = items.size(); i--; )
1650 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
1651
1652 items.clear();
1653
Adam Sawickid2924172018-06-11 12:48:46 +02001654 ////////////////////////////////////////////////////////////////////////////////
1655 // Test for allocation too large for pool
1656
1657 {
1658 VmaAllocationCreateInfo allocCreateInfo = {};
1659 allocCreateInfo.pool = pool;
1660
1661 VkMemoryRequirements memReq;
1662 memReq.memoryTypeBits = UINT32_MAX;
1663 memReq.alignment = 1;
1664 memReq.size = poolCreateInfo.blockSize + 4;
1665
1666 VmaAllocation alloc = nullptr;
1667 res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr);
1668 assert(res == VK_ERROR_OUT_OF_DEVICE_MEMORY && alloc == nullptr);
1669 }
1670
Adam Sawickib8333fb2018-03-13 16:15:53 +01001671 vmaDestroyPool(g_hAllocator, pool);
1672}
1673
Adam Sawickie44c6262018-06-15 14:30:39 +02001674static bool ValidatePattern(const void* pMemory, size_t size, uint8_t pattern)
1675{
1676 const uint8_t* pBytes = (const uint8_t*)pMemory;
1677 for(size_t i = 0; i < size; ++i)
1678 {
1679 if(pBytes[i] != pattern)
1680 {
1681 return false;
1682 }
1683 }
1684 return true;
1685}
1686
1687static void TestAllocationsInitialization()
1688{
1689 VkResult res;
1690
1691 const size_t BUF_SIZE = 1024;
1692
1693 // Create pool.
1694
1695 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1696 bufInfo.size = BUF_SIZE;
1697 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
1698
1699 VmaAllocationCreateInfo dummyBufAllocCreateInfo = {};
1700 dummyBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1701
1702 VmaPoolCreateInfo poolCreateInfo = {};
1703 poolCreateInfo.blockSize = BUF_SIZE * 10;
1704 poolCreateInfo.minBlockCount = 1; // To keep memory alive while pool exists.
1705 poolCreateInfo.maxBlockCount = 1;
1706 res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufInfo, &dummyBufAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
1707 assert(res == VK_SUCCESS);
1708
1709 VmaAllocationCreateInfo bufAllocCreateInfo = {};
1710 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &bufAllocCreateInfo.pool);
1711 assert(res == VK_SUCCESS);
1712
1713 // Create one persistently mapped buffer to keep memory of this block mapped,
1714 // so that pointer to mapped data will remain (more or less...) valid even
1715 // after destruction of other allocations.
1716
1717 bufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
1718 VkBuffer firstBuf;
1719 VmaAllocation firstAlloc;
1720 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &firstBuf, &firstAlloc, nullptr);
1721 assert(res == VK_SUCCESS);
1722
1723 // Test buffers.
1724
1725 for(uint32_t i = 0; i < 2; ++i)
1726 {
1727 const bool persistentlyMapped = i == 0;
1728 bufAllocCreateInfo.flags = persistentlyMapped ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0;
1729 VkBuffer buf;
1730 VmaAllocation alloc;
1731 VmaAllocationInfo allocInfo;
1732 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &buf, &alloc, &allocInfo);
1733 assert(res == VK_SUCCESS);
1734
1735 void* pMappedData;
1736 if(!persistentlyMapped)
1737 {
1738 res = vmaMapMemory(g_hAllocator, alloc, &pMappedData);
1739 assert(res == VK_SUCCESS);
1740 }
1741 else
1742 {
1743 pMappedData = allocInfo.pMappedData;
1744 }
1745
1746 // Validate initialized content
1747 bool valid = ValidatePattern(pMappedData, BUF_SIZE, 0xDC);
1748 assert(valid);
1749
1750 if(!persistentlyMapped)
1751 {
1752 vmaUnmapMemory(g_hAllocator, alloc);
1753 }
1754
1755 vmaDestroyBuffer(g_hAllocator, buf, alloc);
1756
1757 // Validate freed content
1758 valid = ValidatePattern(pMappedData, BUF_SIZE, 0xEF);
1759 assert(valid);
1760 }
1761
1762 vmaDestroyBuffer(g_hAllocator, firstBuf, firstAlloc);
1763 vmaDestroyPool(g_hAllocator, bufAllocCreateInfo.pool);
1764}
1765
Adam Sawickib8333fb2018-03-13 16:15:53 +01001766static void TestPool_Benchmark(
1767 PoolTestResult& outResult,
1768 const PoolTestConfig& config)
1769{
1770 assert(config.ThreadCount > 0);
1771
1772 RandomNumberGenerator mainRand{config.RandSeed};
1773
1774 uint32_t allocationSizeProbabilitySum = std::accumulate(
1775 config.AllocationSizes.begin(),
1776 config.AllocationSizes.end(),
1777 0u,
1778 [](uint32_t sum, const AllocationSize& allocSize) {
1779 return sum + allocSize.Probability;
1780 });
1781
1782 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1783 bufferInfo.size = 256; // Whatever.
1784 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1785
1786 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
1787 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1788 imageInfo.extent.width = 256; // Whatever.
1789 imageInfo.extent.height = 256; // Whatever.
1790 imageInfo.extent.depth = 1;
1791 imageInfo.mipLevels = 1;
1792 imageInfo.arrayLayers = 1;
1793 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
1794 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // LINEAR if CPU memory.
1795 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
1796 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // TRANSFER_SRC if CPU memory.
1797 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1798
1799 uint32_t bufferMemoryTypeBits = UINT32_MAX;
1800 {
1801 VkBuffer dummyBuffer;
1802 VkResult res = vkCreateBuffer(g_hDevice, &bufferInfo, nullptr, &dummyBuffer);
1803 assert(res == VK_SUCCESS);
1804
1805 VkMemoryRequirements memReq;
1806 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
1807 bufferMemoryTypeBits = memReq.memoryTypeBits;
1808
1809 vkDestroyBuffer(g_hDevice, dummyBuffer, nullptr);
1810 }
1811
1812 uint32_t imageMemoryTypeBits = UINT32_MAX;
1813 {
1814 VkImage dummyImage;
1815 VkResult res = vkCreateImage(g_hDevice, &imageInfo, nullptr, &dummyImage);
1816 assert(res == VK_SUCCESS);
1817
1818 VkMemoryRequirements memReq;
1819 vkGetImageMemoryRequirements(g_hDevice, dummyImage, &memReq);
1820 imageMemoryTypeBits = memReq.memoryTypeBits;
1821
1822 vkDestroyImage(g_hDevice, dummyImage, nullptr);
1823 }
1824
1825 uint32_t memoryTypeBits = 0;
1826 if(config.UsesBuffers() && config.UsesImages())
1827 {
1828 memoryTypeBits = bufferMemoryTypeBits & imageMemoryTypeBits;
1829 if(memoryTypeBits == 0)
1830 {
1831 PrintWarning(L"Cannot test buffers + images in the same memory pool on this GPU.");
1832 return;
1833 }
1834 }
1835 else if(config.UsesBuffers())
1836 memoryTypeBits = bufferMemoryTypeBits;
1837 else if(config.UsesImages())
1838 memoryTypeBits = imageMemoryTypeBits;
1839 else
1840 assert(0);
1841
1842 VmaPoolCreateInfo poolCreateInfo = {};
1843 poolCreateInfo.memoryTypeIndex = 0;
1844 poolCreateInfo.minBlockCount = 1;
1845 poolCreateInfo.maxBlockCount = 1;
1846 poolCreateInfo.blockSize = config.PoolSize;
1847 poolCreateInfo.frameInUseCount = 1;
1848
1849 VmaAllocationCreateInfo dummyAllocCreateInfo = {};
1850 dummyAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1851 vmaFindMemoryTypeIndex(g_hAllocator, memoryTypeBits, &dummyAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
1852
1853 VmaPool pool;
1854 VkResult res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
1855 assert(res == VK_SUCCESS);
1856
1857 // Start time measurement - after creating pool and initializing data structures.
1858 time_point timeBeg = std::chrono::high_resolution_clock::now();
1859
1860 ////////////////////////////////////////////////////////////////////////////////
1861 // ThreadProc
1862 auto ThreadProc = [&](
1863 PoolTestThreadResult* outThreadResult,
1864 uint32_t randSeed,
1865 HANDLE frameStartEvent,
1866 HANDLE frameEndEvent) -> void
1867 {
1868 RandomNumberGenerator threadRand{randSeed};
1869
1870 outThreadResult->AllocationTimeMin = duration::max();
1871 outThreadResult->AllocationTimeSum = duration::zero();
1872 outThreadResult->AllocationTimeMax = duration::min();
1873 outThreadResult->DeallocationTimeMin = duration::max();
1874 outThreadResult->DeallocationTimeSum = duration::zero();
1875 outThreadResult->DeallocationTimeMax = duration::min();
1876 outThreadResult->AllocationCount = 0;
1877 outThreadResult->DeallocationCount = 0;
1878 outThreadResult->LostAllocationCount = 0;
1879 outThreadResult->LostAllocationTotalSize = 0;
1880 outThreadResult->FailedAllocationCount = 0;
1881 outThreadResult->FailedAllocationTotalSize = 0;
1882
1883 struct Item
1884 {
1885 VkDeviceSize BufferSize;
1886 VkExtent2D ImageSize;
1887 VkBuffer Buf;
1888 VkImage Image;
1889 VmaAllocation Alloc;
1890
1891 VkDeviceSize CalcSizeBytes() const
1892 {
1893 return BufferSize +
1894 ImageSize.width * ImageSize.height * 4;
1895 }
1896 };
1897 std::vector<Item> unusedItems, usedItems;
1898
1899 const size_t threadTotalItemCount = config.TotalItemCount / config.ThreadCount;
1900
1901 // Create all items - all unused, not yet allocated.
1902 for(size_t i = 0; i < threadTotalItemCount; ++i)
1903 {
1904 Item item = {};
1905
1906 uint32_t allocSizeIndex = 0;
1907 uint32_t r = threadRand.Generate() % allocationSizeProbabilitySum;
1908 while(r >= config.AllocationSizes[allocSizeIndex].Probability)
1909 r -= config.AllocationSizes[allocSizeIndex++].Probability;
1910
1911 const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex];
1912 if(allocSize.BufferSizeMax > 0)
1913 {
1914 assert(allocSize.BufferSizeMin > 0);
1915 assert(allocSize.ImageSizeMin == 0 && allocSize.ImageSizeMax == 0);
1916 if(allocSize.BufferSizeMax == allocSize.BufferSizeMin)
1917 item.BufferSize = allocSize.BufferSizeMin;
1918 else
1919 {
1920 item.BufferSize = allocSize.BufferSizeMin + threadRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin);
1921 item.BufferSize = item.BufferSize / 16 * 16;
1922 }
1923 }
1924 else
1925 {
1926 assert(allocSize.ImageSizeMin > 0 && allocSize.ImageSizeMax > 0);
1927 if(allocSize.ImageSizeMax == allocSize.ImageSizeMin)
1928 item.ImageSize.width = item.ImageSize.height = allocSize.ImageSizeMax;
1929 else
1930 {
1931 item.ImageSize.width = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
1932 item.ImageSize.height = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
1933 }
1934 }
1935
1936 unusedItems.push_back(item);
1937 }
1938
1939 auto Allocate = [&](Item& item) -> VkResult
1940 {
1941 VmaAllocationCreateInfo allocCreateInfo = {};
1942 allocCreateInfo.pool = pool;
1943 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT |
1944 VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT;
1945
1946 if(item.BufferSize)
1947 {
1948 bufferInfo.size = item.BufferSize;
1949 PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
1950 return vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocCreateInfo, &item.Buf, &item.Alloc, nullptr);
1951 }
1952 else
1953 {
1954 assert(item.ImageSize.width && item.ImageSize.height);
1955
1956 imageInfo.extent.width = item.ImageSize.width;
1957 imageInfo.extent.height = item.ImageSize.height;
1958 PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
1959 return vmaCreateImage(g_hAllocator, &imageInfo, &allocCreateInfo, &item.Image, &item.Alloc, nullptr);
1960 }
1961 };
1962
1963 ////////////////////////////////////////////////////////////////////////////////
1964 // Frames
1965 for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex)
1966 {
1967 WaitForSingleObject(frameStartEvent, INFINITE);
1968
1969 // Always make some percent of used bufs unused, to choose different used ones.
1970 const size_t bufsToMakeUnused = usedItems.size() * config.ItemsToMakeUnusedPercent / 100;
1971 for(size_t i = 0; i < bufsToMakeUnused; ++i)
1972 {
1973 size_t index = threadRand.Generate() % usedItems.size();
1974 unusedItems.push_back(usedItems[index]);
1975 usedItems.erase(usedItems.begin() + index);
1976 }
1977
1978 // Determine which bufs we want to use in this frame.
1979 const size_t usedBufCount = (threadRand.Generate() % (config.UsedItemCountMax - config.UsedItemCountMin) + config.UsedItemCountMin)
1980 / config.ThreadCount;
1981 assert(usedBufCount < usedItems.size() + unusedItems.size());
1982 // Move some used to unused.
1983 while(usedBufCount < usedItems.size())
1984 {
1985 size_t index = threadRand.Generate() % usedItems.size();
1986 unusedItems.push_back(usedItems[index]);
1987 usedItems.erase(usedItems.begin() + index);
1988 }
1989 // Move some unused to used.
1990 while(usedBufCount > usedItems.size())
1991 {
1992 size_t index = threadRand.Generate() % unusedItems.size();
1993 usedItems.push_back(unusedItems[index]);
1994 unusedItems.erase(unusedItems.begin() + index);
1995 }
1996
1997 uint32_t touchExistingCount = 0;
1998 uint32_t touchLostCount = 0;
1999 uint32_t createSucceededCount = 0;
2000 uint32_t createFailedCount = 0;
2001
2002 // Touch all used bufs. If not created or lost, allocate.
2003 for(size_t i = 0; i < usedItems.size(); ++i)
2004 {
2005 Item& item = usedItems[i];
2006 // Not yet created.
2007 if(item.Alloc == VK_NULL_HANDLE)
2008 {
2009 res = Allocate(item);
2010 ++outThreadResult->AllocationCount;
2011 if(res != VK_SUCCESS)
2012 {
2013 item.Alloc = VK_NULL_HANDLE;
2014 item.Buf = VK_NULL_HANDLE;
2015 ++outThreadResult->FailedAllocationCount;
2016 outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes();
2017 ++createFailedCount;
2018 }
2019 else
2020 ++createSucceededCount;
2021 }
2022 else
2023 {
2024 // Touch.
2025 VmaAllocationInfo allocInfo;
2026 vmaGetAllocationInfo(g_hAllocator, item.Alloc, &allocInfo);
2027 // Lost.
2028 if(allocInfo.deviceMemory == VK_NULL_HANDLE)
2029 {
2030 ++touchLostCount;
2031
2032 // Destroy.
2033 {
2034 PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult);
2035 if(item.Buf)
2036 vmaDestroyBuffer(g_hAllocator, item.Buf, item.Alloc);
2037 else
2038 vmaDestroyImage(g_hAllocator, item.Image, item.Alloc);
2039 ++outThreadResult->DeallocationCount;
2040 }
2041 item.Alloc = VK_NULL_HANDLE;
2042 item.Buf = VK_NULL_HANDLE;
2043
2044 ++outThreadResult->LostAllocationCount;
2045 outThreadResult->LostAllocationTotalSize += item.CalcSizeBytes();
2046
2047 // Recreate.
2048 res = Allocate(item);
2049 ++outThreadResult->AllocationCount;
2050 // Creation failed.
2051 if(res != VK_SUCCESS)
2052 {
2053 ++outThreadResult->FailedAllocationCount;
2054 outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes();
2055 ++createFailedCount;
2056 }
2057 else
2058 ++createSucceededCount;
2059 }
2060 else
2061 ++touchExistingCount;
2062 }
2063 }
2064
2065 /*
2066 printf("Thread %u frame %u: Touch existing %u lost %u, create succeeded %u failed %u\n",
2067 randSeed, frameIndex,
2068 touchExistingCount, touchLostCount,
2069 createSucceededCount, createFailedCount);
2070 */
2071
2072 SetEvent(frameEndEvent);
2073 }
2074
2075 // Free all remaining items.
2076 for(size_t i = usedItems.size(); i--; )
2077 {
2078 PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult);
2079 if(usedItems[i].Buf)
2080 vmaDestroyBuffer(g_hAllocator, usedItems[i].Buf, usedItems[i].Alloc);
2081 else
2082 vmaDestroyImage(g_hAllocator, usedItems[i].Image, usedItems[i].Alloc);
2083 ++outThreadResult->DeallocationCount;
2084 }
2085 for(size_t i = unusedItems.size(); i--; )
2086 {
2087 PoolDeallocationTimeRegisterObj timeRegisterOb(*outThreadResult);
2088 if(unusedItems[i].Buf)
2089 vmaDestroyBuffer(g_hAllocator, unusedItems[i].Buf, unusedItems[i].Alloc);
2090 else
2091 vmaDestroyImage(g_hAllocator, unusedItems[i].Image, unusedItems[i].Alloc);
2092 ++outThreadResult->DeallocationCount;
2093 }
2094 };
2095
2096 // Launch threads.
2097 uint32_t threadRandSeed = mainRand.Generate();
2098 std::vector<HANDLE> frameStartEvents{config.ThreadCount};
2099 std::vector<HANDLE> frameEndEvents{config.ThreadCount};
2100 std::vector<std::thread> bkgThreads;
2101 std::vector<PoolTestThreadResult> threadResults{config.ThreadCount};
2102 for(uint32_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
2103 {
2104 frameStartEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL);
2105 frameEndEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL);
2106 bkgThreads.emplace_back(std::bind(
2107 ThreadProc,
2108 &threadResults[threadIndex],
2109 threadRandSeed + threadIndex,
2110 frameStartEvents[threadIndex],
2111 frameEndEvents[threadIndex]));
2112 }
2113
2114 // Execute frames.
2115 assert(config.ThreadCount <= MAXIMUM_WAIT_OBJECTS);
2116 for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex)
2117 {
2118 vmaSetCurrentFrameIndex(g_hAllocator, frameIndex);
2119 for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
2120 SetEvent(frameStartEvents[threadIndex]);
2121 WaitForMultipleObjects(config.ThreadCount, &frameEndEvents[0], TRUE, INFINITE);
2122 }
2123
2124 // Wait for threads finished
2125 for(size_t i = 0; i < bkgThreads.size(); ++i)
2126 {
2127 bkgThreads[i].join();
2128 CloseHandle(frameEndEvents[i]);
2129 CloseHandle(frameStartEvents[i]);
2130 }
2131 bkgThreads.clear();
2132
2133 // Finish time measurement - before destroying pool.
2134 outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg;
2135
2136 vmaDestroyPool(g_hAllocator, pool);
2137
2138 outResult.AllocationTimeMin = duration::max();
2139 outResult.AllocationTimeAvg = duration::zero();
2140 outResult.AllocationTimeMax = duration::min();
2141 outResult.DeallocationTimeMin = duration::max();
2142 outResult.DeallocationTimeAvg = duration::zero();
2143 outResult.DeallocationTimeMax = duration::min();
2144 outResult.LostAllocationCount = 0;
2145 outResult.LostAllocationTotalSize = 0;
2146 outResult.FailedAllocationCount = 0;
2147 outResult.FailedAllocationTotalSize = 0;
2148 size_t allocationCount = 0;
2149 size_t deallocationCount = 0;
2150 for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
2151 {
2152 const PoolTestThreadResult& threadResult = threadResults[threadIndex];
2153 outResult.AllocationTimeMin = std::min(outResult.AllocationTimeMin, threadResult.AllocationTimeMin);
2154 outResult.AllocationTimeMax = std::max(outResult.AllocationTimeMax, threadResult.AllocationTimeMax);
2155 outResult.AllocationTimeAvg += threadResult.AllocationTimeSum;
2156 outResult.DeallocationTimeMin = std::min(outResult.DeallocationTimeMin, threadResult.DeallocationTimeMin);
2157 outResult.DeallocationTimeMax = std::max(outResult.DeallocationTimeMax, threadResult.DeallocationTimeMax);
2158 outResult.DeallocationTimeAvg += threadResult.DeallocationTimeSum;
2159 allocationCount += threadResult.AllocationCount;
2160 deallocationCount += threadResult.DeallocationCount;
2161 outResult.FailedAllocationCount += threadResult.FailedAllocationCount;
2162 outResult.FailedAllocationTotalSize += threadResult.FailedAllocationTotalSize;
2163 outResult.LostAllocationCount += threadResult.LostAllocationCount;
2164 outResult.LostAllocationTotalSize += threadResult.LostAllocationTotalSize;
2165 }
2166 if(allocationCount)
2167 outResult.AllocationTimeAvg /= allocationCount;
2168 if(deallocationCount)
2169 outResult.DeallocationTimeAvg /= deallocationCount;
2170}
2171
2172static inline bool MemoryRegionsOverlap(char* ptr1, size_t size1, char* ptr2, size_t size2)
2173{
2174 if(ptr1 < ptr2)
2175 return ptr1 + size1 > ptr2;
2176 else if(ptr2 < ptr1)
2177 return ptr2 + size2 > ptr1;
2178 else
2179 return true;
2180}
2181
2182static void TestMapping()
2183{
2184 wprintf(L"Testing mapping...\n");
2185
2186 VkResult res;
2187 uint32_t memTypeIndex = UINT32_MAX;
2188
2189 enum TEST
2190 {
2191 TEST_NORMAL,
2192 TEST_POOL,
2193 TEST_DEDICATED,
2194 TEST_COUNT
2195 };
2196 for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex)
2197 {
2198 VmaPool pool = nullptr;
2199 if(testIndex == TEST_POOL)
2200 {
2201 assert(memTypeIndex != UINT32_MAX);
2202 VmaPoolCreateInfo poolInfo = {};
2203 poolInfo.memoryTypeIndex = memTypeIndex;
2204 res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
2205 assert(res == VK_SUCCESS);
2206 }
2207
2208 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2209 bufInfo.size = 0x10000;
2210 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2211
2212 VmaAllocationCreateInfo allocCreateInfo = {};
2213 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
2214 allocCreateInfo.pool = pool;
2215 if(testIndex == TEST_DEDICATED)
2216 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
2217
2218 VmaAllocationInfo allocInfo;
2219
2220 // Mapped manually
2221
2222 // Create 2 buffers.
2223 BufferInfo bufferInfos[3];
2224 for(size_t i = 0; i < 2; ++i)
2225 {
2226 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
2227 &bufferInfos[i].Buffer, &bufferInfos[i].Allocation, &allocInfo);
2228 assert(res == VK_SUCCESS);
2229 assert(allocInfo.pMappedData == nullptr);
2230 memTypeIndex = allocInfo.memoryType;
2231 }
2232
2233 // Map buffer 0.
2234 char* data00 = nullptr;
2235 res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data00);
2236 assert(res == VK_SUCCESS && data00 != nullptr);
2237 data00[0xFFFF] = data00[0];
2238
2239 // Map buffer 0 second time.
2240 char* data01 = nullptr;
2241 res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data01);
2242 assert(res == VK_SUCCESS && data01 == data00);
2243
2244 // Map buffer 1.
2245 char* data1 = nullptr;
2246 res = vmaMapMemory(g_hAllocator, bufferInfos[1].Allocation, (void**)&data1);
2247 assert(res == VK_SUCCESS && data1 != nullptr);
2248 assert(!MemoryRegionsOverlap(data00, (size_t)bufInfo.size, data1, (size_t)bufInfo.size));
2249 data1[0xFFFF] = data1[0];
2250
2251 // Unmap buffer 0 two times.
2252 vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
2253 vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
2254 vmaGetAllocationInfo(g_hAllocator, bufferInfos[0].Allocation, &allocInfo);
2255 assert(allocInfo.pMappedData == nullptr);
2256
2257 // Unmap buffer 1.
2258 vmaUnmapMemory(g_hAllocator, bufferInfos[1].Allocation);
2259 vmaGetAllocationInfo(g_hAllocator, bufferInfos[1].Allocation, &allocInfo);
2260 assert(allocInfo.pMappedData == nullptr);
2261
2262 // Create 3rd buffer - persistently mapped.
2263 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
2264 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
2265 &bufferInfos[2].Buffer, &bufferInfos[2].Allocation, &allocInfo);
2266 assert(res == VK_SUCCESS && allocInfo.pMappedData != nullptr);
2267
2268 // Map buffer 2.
2269 char* data2 = nullptr;
2270 res = vmaMapMemory(g_hAllocator, bufferInfos[2].Allocation, (void**)&data2);
2271 assert(res == VK_SUCCESS && data2 == allocInfo.pMappedData);
2272 data2[0xFFFF] = data2[0];
2273
2274 // Unmap buffer 2.
2275 vmaUnmapMemory(g_hAllocator, bufferInfos[2].Allocation);
2276 vmaGetAllocationInfo(g_hAllocator, bufferInfos[2].Allocation, &allocInfo);
2277 assert(allocInfo.pMappedData == data2);
2278
2279 // Destroy all buffers.
2280 for(size_t i = 3; i--; )
2281 vmaDestroyBuffer(g_hAllocator, bufferInfos[i].Buffer, bufferInfos[i].Allocation);
2282
2283 vmaDestroyPool(g_hAllocator, pool);
2284 }
2285}
2286
2287static void TestMappingMultithreaded()
2288{
2289 wprintf(L"Testing mapping multithreaded...\n");
2290
2291 static const uint32_t threadCount = 16;
2292 static const uint32_t bufferCount = 1024;
2293 static const uint32_t threadBufferCount = bufferCount / threadCount;
2294
2295 VkResult res;
2296 volatile uint32_t memTypeIndex = UINT32_MAX;
2297
2298 enum TEST
2299 {
2300 TEST_NORMAL,
2301 TEST_POOL,
2302 TEST_DEDICATED,
2303 TEST_COUNT
2304 };
2305 for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex)
2306 {
2307 VmaPool pool = nullptr;
2308 if(testIndex == TEST_POOL)
2309 {
2310 assert(memTypeIndex != UINT32_MAX);
2311 VmaPoolCreateInfo poolInfo = {};
2312 poolInfo.memoryTypeIndex = memTypeIndex;
2313 res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
2314 assert(res == VK_SUCCESS);
2315 }
2316
2317 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2318 bufCreateInfo.size = 0x10000;
2319 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2320
2321 VmaAllocationCreateInfo allocCreateInfo = {};
2322 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
2323 allocCreateInfo.pool = pool;
2324 if(testIndex == TEST_DEDICATED)
2325 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
2326
2327 std::thread threads[threadCount];
2328 for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex)
2329 {
2330 threads[threadIndex] = std::thread([=, &memTypeIndex](){
2331 // ======== THREAD FUNCTION ========
2332
2333 RandomNumberGenerator rand{threadIndex};
2334
2335 enum class MODE
2336 {
2337 // Don't map this buffer at all.
2338 DONT_MAP,
2339 // Map and quickly unmap.
2340 MAP_FOR_MOMENT,
2341 // Map and unmap before destruction.
2342 MAP_FOR_LONGER,
2343 // Map two times. Quickly unmap, second unmap before destruction.
2344 MAP_TWO_TIMES,
2345 // Create this buffer as persistently mapped.
2346 PERSISTENTLY_MAPPED,
2347 COUNT
2348 };
2349 std::vector<BufferInfo> bufInfos{threadBufferCount};
2350 std::vector<MODE> bufModes{threadBufferCount};
2351
2352 for(uint32_t bufferIndex = 0; bufferIndex < threadBufferCount; ++bufferIndex)
2353 {
2354 BufferInfo& bufInfo = bufInfos[bufferIndex];
2355 const MODE mode = (MODE)(rand.Generate() % (uint32_t)MODE::COUNT);
2356 bufModes[bufferIndex] = mode;
2357
2358 VmaAllocationCreateInfo localAllocCreateInfo = allocCreateInfo;
2359 if(mode == MODE::PERSISTENTLY_MAPPED)
2360 localAllocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
2361
2362 VmaAllocationInfo allocInfo;
2363 VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &localAllocCreateInfo,
2364 &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo);
2365 assert(res == VK_SUCCESS);
2366
2367 if(memTypeIndex == UINT32_MAX)
2368 memTypeIndex = allocInfo.memoryType;
2369
2370 char* data = nullptr;
2371
2372 if(mode == MODE::PERSISTENTLY_MAPPED)
2373 {
2374 data = (char*)allocInfo.pMappedData;
2375 assert(data != nullptr);
2376 }
2377 else if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_FOR_LONGER ||
2378 mode == MODE::MAP_TWO_TIMES)
2379 {
2380 assert(data == nullptr);
2381 res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data);
2382 assert(res == VK_SUCCESS && data != nullptr);
2383
2384 if(mode == MODE::MAP_TWO_TIMES)
2385 {
2386 char* data2 = nullptr;
2387 res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data2);
2388 assert(res == VK_SUCCESS && data2 == data);
2389 }
2390 }
2391 else if(mode == MODE::DONT_MAP)
2392 {
2393 assert(allocInfo.pMappedData == nullptr);
2394 }
2395 else
2396 assert(0);
2397
2398 // Test if reading and writing from the beginning and end of mapped memory doesn't crash.
2399 if(data)
2400 data[0xFFFF] = data[0];
2401
2402 if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_TWO_TIMES)
2403 {
2404 vmaUnmapMemory(g_hAllocator, bufInfo.Allocation);
2405
2406 VmaAllocationInfo allocInfo;
2407 vmaGetAllocationInfo(g_hAllocator, bufInfo.Allocation, &allocInfo);
2408 if(mode == MODE::MAP_FOR_MOMENT)
2409 assert(allocInfo.pMappedData == nullptr);
2410 else
2411 assert(allocInfo.pMappedData == data);
2412 }
2413
2414 switch(rand.Generate() % 3)
2415 {
2416 case 0: Sleep(0); break; // Yield.
2417 case 1: Sleep(10); break; // 10 ms
2418 // default: No sleep.
2419 }
2420
2421 // Test if reading and writing from the beginning and end of mapped memory doesn't crash.
2422 if(data)
2423 data[0xFFFF] = data[0];
2424 }
2425
2426 for(size_t bufferIndex = threadBufferCount; bufferIndex--; )
2427 {
2428 if(bufModes[bufferIndex] == MODE::MAP_FOR_LONGER ||
2429 bufModes[bufferIndex] == MODE::MAP_TWO_TIMES)
2430 {
2431 vmaUnmapMemory(g_hAllocator, bufInfos[bufferIndex].Allocation);
2432
2433 VmaAllocationInfo allocInfo;
2434 vmaGetAllocationInfo(g_hAllocator, bufInfos[bufferIndex].Allocation, &allocInfo);
2435 assert(allocInfo.pMappedData == nullptr);
2436 }
2437
2438 vmaDestroyBuffer(g_hAllocator, bufInfos[bufferIndex].Buffer, bufInfos[bufferIndex].Allocation);
2439 }
2440 });
2441 }
2442
2443 for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex)
2444 threads[threadIndex].join();
2445
2446 vmaDestroyPool(g_hAllocator, pool);
2447 }
2448}
2449
2450static void WriteMainTestResultHeader(FILE* file)
2451{
2452 fprintf(file,
2453 "Code,Test,Time,"
2454 "Config,"
2455 "Total Time (us),"
2456 "Allocation Time Min (us),"
2457 "Allocation Time Avg (us),"
2458 "Allocation Time Max (us),"
2459 "Deallocation Time Min (us),"
2460 "Deallocation Time Avg (us),"
2461 "Deallocation Time Max (us),"
2462 "Total Memory Allocated (B),"
2463 "Free Range Size Avg (B),"
2464 "Free Range Size Max (B)\n");
2465}
2466
2467static void WriteMainTestResult(
2468 FILE* file,
2469 const char* codeDescription,
2470 const char* testDescription,
2471 const Config& config, const Result& result)
2472{
2473 float totalTimeSeconds = ToFloatSeconds(result.TotalTime);
2474 float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin);
2475 float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg);
2476 float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax);
2477 float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin);
2478 float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg);
2479 float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax);
2480
2481 time_t rawTime; time(&rawTime);
2482 struct tm timeInfo; localtime_s(&timeInfo, &rawTime);
2483 char timeStr[128];
2484 strftime(timeStr, _countof(timeStr), "%c", &timeInfo);
2485
2486 fprintf(file,
2487 "%s,%s,%s,"
2488 "BeginBytesToAllocate=%I64u MaxBytesToAllocate=%I64u AdditionalOperationCount=%u ThreadCount=%u FreeOrder=%d,"
2489 "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n",
2490 codeDescription,
2491 testDescription,
2492 timeStr,
2493 config.BeginBytesToAllocate, config.MaxBytesToAllocate, config.AdditionalOperationCount, config.ThreadCount, (uint32_t)config.FreeOrder,
2494 totalTimeSeconds * 1e6f,
2495 allocationTimeMinSeconds * 1e6f,
2496 allocationTimeAvgSeconds * 1e6f,
2497 allocationTimeMaxSeconds * 1e6f,
2498 deallocationTimeMinSeconds * 1e6f,
2499 deallocationTimeAvgSeconds * 1e6f,
2500 deallocationTimeMaxSeconds * 1e6f,
2501 result.TotalMemoryAllocated,
2502 result.FreeRangeSizeAvg,
2503 result.FreeRangeSizeMax);
2504}
2505
2506static void WritePoolTestResultHeader(FILE* file)
2507{
2508 fprintf(file,
2509 "Code,Test,Time,"
2510 "Config,"
2511 "Total Time (us),"
2512 "Allocation Time Min (us),"
2513 "Allocation Time Avg (us),"
2514 "Allocation Time Max (us),"
2515 "Deallocation Time Min (us),"
2516 "Deallocation Time Avg (us),"
2517 "Deallocation Time Max (us),"
2518 "Lost Allocation Count,"
2519 "Lost Allocation Total Size (B),"
2520 "Failed Allocation Count,"
2521 "Failed Allocation Total Size (B)\n");
2522}
2523
2524static void WritePoolTestResult(
2525 FILE* file,
2526 const char* codeDescription,
2527 const char* testDescription,
2528 const PoolTestConfig& config,
2529 const PoolTestResult& result)
2530{
2531 float totalTimeSeconds = ToFloatSeconds(result.TotalTime);
2532 float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin);
2533 float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg);
2534 float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax);
2535 float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin);
2536 float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg);
2537 float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax);
2538
2539 time_t rawTime; time(&rawTime);
2540 struct tm timeInfo; localtime_s(&timeInfo, &rawTime);
2541 char timeStr[128];
2542 strftime(timeStr, _countof(timeStr), "%c", &timeInfo);
2543
2544 fprintf(file,
2545 "%s,%s,%s,"
2546 "ThreadCount=%u PoolSize=%llu FrameCount=%u TotalItemCount=%u UsedItemCount=%u...%u ItemsToMakeUnusedPercent=%u,"
2547 "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u,%I64u\n",
2548 // General
2549 codeDescription,
2550 testDescription,
2551 timeStr,
2552 // Config
2553 config.ThreadCount,
2554 (unsigned long long)config.PoolSize,
2555 config.FrameCount,
2556 config.TotalItemCount,
2557 config.UsedItemCountMin,
2558 config.UsedItemCountMax,
2559 config.ItemsToMakeUnusedPercent,
2560 // Results
2561 totalTimeSeconds * 1e6f,
2562 allocationTimeMinSeconds * 1e6f,
2563 allocationTimeAvgSeconds * 1e6f,
2564 allocationTimeMaxSeconds * 1e6f,
2565 deallocationTimeMinSeconds * 1e6f,
2566 deallocationTimeAvgSeconds * 1e6f,
2567 deallocationTimeMaxSeconds * 1e6f,
2568 result.LostAllocationCount,
2569 result.LostAllocationTotalSize,
2570 result.FailedAllocationCount,
2571 result.FailedAllocationTotalSize);
2572}
2573
2574static void PerformCustomMainTest(FILE* file)
2575{
2576 Config config{};
2577 config.RandSeed = 65735476;
2578 //config.MaxBytesToAllocate = 4ull * 1024 * 1024; // 4 MB
2579 config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB
2580 config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY
2581 config.FreeOrder = FREE_ORDER::FORWARD;
2582 config.ThreadCount = 16;
2583 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
2584
2585 // Buffers
2586 //config.AllocationSizes.push_back({4, 16, 1024});
2587 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
2588
2589 // Images
2590 //config.AllocationSizes.push_back({4, 0, 0, 4, 32});
2591 //config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
2592
2593 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100;
2594 config.AdditionalOperationCount = 1024;
2595
2596 Result result{};
2597 VkResult res = MainTest(result, config);
2598 assert(res == VK_SUCCESS);
2599 WriteMainTestResult(file, "Foo", "CustomTest", config, result);
2600}
2601
2602static void PerformCustomPoolTest(FILE* file)
2603{
2604 PoolTestConfig config;
2605 config.PoolSize = 100 * 1024 * 1024;
2606 config.RandSeed = 2345764;
2607 config.ThreadCount = 1;
2608 config.FrameCount = 200;
2609 config.ItemsToMakeUnusedPercent = 2;
2610
2611 AllocationSize allocSize = {};
2612 allocSize.BufferSizeMin = 1024;
2613 allocSize.BufferSizeMax = 1024 * 1024;
2614 allocSize.Probability = 1;
2615 config.AllocationSizes.push_back(allocSize);
2616
2617 allocSize.BufferSizeMin = 0;
2618 allocSize.BufferSizeMax = 0;
2619 allocSize.ImageSizeMin = 128;
2620 allocSize.ImageSizeMax = 1024;
2621 allocSize.Probability = 1;
2622 config.AllocationSizes.push_back(allocSize);
2623
2624 config.PoolSize = config.CalcAvgResourceSize() * 200;
2625 config.UsedItemCountMax = 160;
2626 config.TotalItemCount = config.UsedItemCountMax * 10;
2627 config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100;
2628
2629 g_MemoryAliasingWarningEnabled = false;
2630 PoolTestResult result = {};
2631 TestPool_Benchmark(result, config);
2632 g_MemoryAliasingWarningEnabled = true;
2633
2634 WritePoolTestResult(file, "Code desc", "Test desc", config, result);
2635}
2636
2637enum CONFIG_TYPE {
2638 CONFIG_TYPE_MINIMUM,
2639 CONFIG_TYPE_SMALL,
2640 CONFIG_TYPE_AVERAGE,
2641 CONFIG_TYPE_LARGE,
2642 CONFIG_TYPE_MAXIMUM,
2643 CONFIG_TYPE_COUNT
2644};
2645
2646static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_SMALL;
2647//static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_LARGE;
2648static const char* CODE_DESCRIPTION = "Foo";
2649
2650static void PerformMainTests(FILE* file)
2651{
2652 uint32_t repeatCount = 1;
2653 if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3;
2654
2655 Config config{};
2656 config.RandSeed = 65735476;
2657 config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY
2658 config.FreeOrder = FREE_ORDER::FORWARD;
2659
2660 size_t threadCountCount = 1;
2661 switch(ConfigType)
2662 {
2663 case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break;
2664 case CONFIG_TYPE_SMALL: threadCountCount = 2; break;
2665 case CONFIG_TYPE_AVERAGE: threadCountCount = 3; break;
2666 case CONFIG_TYPE_LARGE: threadCountCount = 5; break;
2667 case CONFIG_TYPE_MAXIMUM: threadCountCount = 7; break;
2668 default: assert(0);
2669 }
2670 for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex)
2671 {
2672 std::string desc1;
2673
2674 switch(threadCountIndex)
2675 {
2676 case 0:
2677 desc1 += "1_thread";
2678 config.ThreadCount = 1;
2679 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
2680 break;
2681 case 1:
2682 desc1 += "16_threads+0%_common";
2683 config.ThreadCount = 16;
2684 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
2685 break;
2686 case 2:
2687 desc1 += "16_threads+50%_common";
2688 config.ThreadCount = 16;
2689 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
2690 break;
2691 case 3:
2692 desc1 += "16_threads+100%_common";
2693 config.ThreadCount = 16;
2694 config.ThreadsUsingCommonAllocationsProbabilityPercent = 100;
2695 break;
2696 case 4:
2697 desc1 += "2_threads+0%_common";
2698 config.ThreadCount = 2;
2699 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
2700 break;
2701 case 5:
2702 desc1 += "2_threads+50%_common";
2703 config.ThreadCount = 2;
2704 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
2705 break;
2706 case 6:
2707 desc1 += "2_threads+100%_common";
2708 config.ThreadCount = 2;
2709 config.ThreadsUsingCommonAllocationsProbabilityPercent = 100;
2710 break;
2711 default:
2712 assert(0);
2713 }
2714
2715 // 0 = buffers, 1 = images, 2 = buffers and images
2716 size_t buffersVsImagesCount = 2;
2717 if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount;
2718 for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex)
2719 {
2720 std::string desc2 = desc1;
2721 switch(buffersVsImagesIndex)
2722 {
2723 case 0: desc2 += " Buffers"; break;
2724 case 1: desc2 += " Images"; break;
2725 case 2: desc2 += " Buffers+Images"; break;
2726 default: assert(0);
2727 }
2728
2729 // 0 = small, 1 = large, 2 = small and large
2730 size_t smallVsLargeCount = 2;
2731 if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount;
2732 for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex)
2733 {
2734 std::string desc3 = desc2;
2735 switch(smallVsLargeIndex)
2736 {
2737 case 0: desc3 += " Small"; break;
2738 case 1: desc3 += " Large"; break;
2739 case 2: desc3 += " Small+Large"; break;
2740 default: assert(0);
2741 }
2742
2743 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2744 config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB
2745 else
2746 config.MaxBytesToAllocate = 4ull * 1024 * 1024;
2747
2748 // 0 = varying sizes min...max, 1 = set of constant sizes
2749 size_t constantSizesCount = 1;
2750 if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount;
2751 for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex)
2752 {
2753 std::string desc4 = desc3;
2754 switch(constantSizesIndex)
2755 {
2756 case 0: desc4 += " Varying_sizes"; break;
2757 case 1: desc4 += " Constant_sizes"; break;
2758 default: assert(0);
2759 }
2760
2761 config.AllocationSizes.clear();
2762 // Buffers present
2763 if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
2764 {
2765 // Small
2766 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
2767 {
2768 // Varying size
2769 if(constantSizesIndex == 0)
2770 config.AllocationSizes.push_back({4, 16, 1024});
2771 // Constant sizes
2772 else
2773 {
2774 config.AllocationSizes.push_back({1, 16, 16});
2775 config.AllocationSizes.push_back({1, 64, 64});
2776 config.AllocationSizes.push_back({1, 256, 256});
2777 config.AllocationSizes.push_back({1, 1024, 1024});
2778 }
2779 }
2780 // Large
2781 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2782 {
2783 // Varying size
2784 if(constantSizesIndex == 0)
2785 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
2786 // Constant sizes
2787 else
2788 {
2789 config.AllocationSizes.push_back({1, 0x10000, 0x10000});
2790 config.AllocationSizes.push_back({1, 0x80000, 0x80000});
2791 config.AllocationSizes.push_back({1, 0x200000, 0x200000});
2792 config.AllocationSizes.push_back({1, 0xA00000, 0xA00000});
2793 }
2794 }
2795 }
2796 // Images present
2797 if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
2798 {
2799 // Small
2800 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
2801 {
2802 // Varying size
2803 if(constantSizesIndex == 0)
2804 config.AllocationSizes.push_back({4, 0, 0, 4, 32});
2805 // Constant sizes
2806 else
2807 {
2808 config.AllocationSizes.push_back({1, 0, 0, 4, 4});
2809 config.AllocationSizes.push_back({1, 0, 0, 8, 8});
2810 config.AllocationSizes.push_back({1, 0, 0, 16, 16});
2811 config.AllocationSizes.push_back({1, 0, 0, 32, 32});
2812 }
2813 }
2814 // Large
2815 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2816 {
2817 // Varying size
2818 if(constantSizesIndex == 0)
2819 config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
2820 // Constant sizes
2821 else
2822 {
2823 config.AllocationSizes.push_back({1, 0, 0, 256, 256});
2824 config.AllocationSizes.push_back({1, 0, 0, 512, 512});
2825 config.AllocationSizes.push_back({1, 0, 0, 1024, 1024});
2826 config.AllocationSizes.push_back({1, 0, 0, 2048, 2048});
2827 }
2828 }
2829 }
2830
2831 // 0 = 100%, additional_operations = 0, 1 = 50%, 2 = 5%, 3 = 95% additional_operations = a lot
2832 size_t beginBytesToAllocateCount = 1;
2833 if(ConfigType >= CONFIG_TYPE_SMALL) ++beginBytesToAllocateCount;
2834 if(ConfigType >= CONFIG_TYPE_AVERAGE) ++beginBytesToAllocateCount;
2835 if(ConfigType >= CONFIG_TYPE_LARGE) ++beginBytesToAllocateCount;
2836 for(size_t beginBytesToAllocateIndex = 0; beginBytesToAllocateIndex < beginBytesToAllocateCount; ++beginBytesToAllocateIndex)
2837 {
2838 std::string desc5 = desc4;
2839
2840 switch(beginBytesToAllocateIndex)
2841 {
2842 case 0:
2843 desc5 += " Allocate_100%";
2844 config.BeginBytesToAllocate = config.MaxBytesToAllocate;
2845 config.AdditionalOperationCount = 0;
2846 break;
2847 case 1:
2848 desc5 += " Allocate_50%+Operations";
2849 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 50 / 100;
2850 config.AdditionalOperationCount = 1024;
2851 break;
2852 case 2:
2853 desc5 += " Allocate_5%+Operations";
2854 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100;
2855 config.AdditionalOperationCount = 1024;
2856 break;
2857 case 3:
2858 desc5 += " Allocate_95%+Operations";
2859 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 95 / 100;
2860 config.AdditionalOperationCount = 1024;
2861 break;
2862 default:
2863 assert(0);
2864 }
2865
2866 const char* testDescription = desc5.c_str();
2867
2868 for(size_t repeat = 0; repeat < repeatCount; ++repeat)
2869 {
2870 printf("%s Repeat %u\n", testDescription, (uint32_t)repeat);
2871
2872 Result result{};
2873 VkResult res = MainTest(result, config);
2874 assert(res == VK_SUCCESS);
2875 WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
2876 }
2877 }
2878 }
2879 }
2880 }
2881 }
2882}
2883
2884static void PerformPoolTests(FILE* file)
2885{
2886 const size_t AVG_RESOURCES_PER_POOL = 300;
2887
2888 uint32_t repeatCount = 1;
2889 if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3;
2890
2891 PoolTestConfig config{};
2892 config.RandSeed = 2346343;
2893 config.FrameCount = 200;
2894 config.ItemsToMakeUnusedPercent = 2;
2895
2896 size_t threadCountCount = 1;
2897 switch(ConfigType)
2898 {
2899 case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break;
2900 case CONFIG_TYPE_SMALL: threadCountCount = 2; break;
2901 case CONFIG_TYPE_AVERAGE: threadCountCount = 2; break;
2902 case CONFIG_TYPE_LARGE: threadCountCount = 3; break;
2903 case CONFIG_TYPE_MAXIMUM: threadCountCount = 3; break;
2904 default: assert(0);
2905 }
2906 for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex)
2907 {
2908 std::string desc1;
2909
2910 switch(threadCountIndex)
2911 {
2912 case 0:
2913 desc1 += "1_thread";
2914 config.ThreadCount = 1;
2915 break;
2916 case 1:
2917 desc1 += "16_threads";
2918 config.ThreadCount = 16;
2919 break;
2920 case 2:
2921 desc1 += "2_threads";
2922 config.ThreadCount = 2;
2923 break;
2924 default:
2925 assert(0);
2926 }
2927
2928 // 0 = buffers, 1 = images, 2 = buffers and images
2929 size_t buffersVsImagesCount = 2;
2930 if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount;
2931 for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex)
2932 {
2933 std::string desc2 = desc1;
2934 switch(buffersVsImagesIndex)
2935 {
2936 case 0: desc2 += " Buffers"; break;
2937 case 1: desc2 += " Images"; break;
2938 case 2: desc2 += " Buffers+Images"; break;
2939 default: assert(0);
2940 }
2941
2942 // 0 = small, 1 = large, 2 = small and large
2943 size_t smallVsLargeCount = 2;
2944 if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount;
2945 for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex)
2946 {
2947 std::string desc3 = desc2;
2948 switch(smallVsLargeIndex)
2949 {
2950 case 0: desc3 += " Small"; break;
2951 case 1: desc3 += " Large"; break;
2952 case 2: desc3 += " Small+Large"; break;
2953 default: assert(0);
2954 }
2955
2956 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2957 config.PoolSize = 6ull * 1024 * 1024 * 1024; // 6 GB
2958 else
2959 config.PoolSize = 4ull * 1024 * 1024;
2960
2961 // 0 = varying sizes min...max, 1 = set of constant sizes
2962 size_t constantSizesCount = 1;
2963 if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount;
2964 for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex)
2965 {
2966 std::string desc4 = desc3;
2967 switch(constantSizesIndex)
2968 {
2969 case 0: desc4 += " Varying_sizes"; break;
2970 case 1: desc4 += " Constant_sizes"; break;
2971 default: assert(0);
2972 }
2973
2974 config.AllocationSizes.clear();
2975 // Buffers present
2976 if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
2977 {
2978 // Small
2979 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
2980 {
2981 // Varying size
2982 if(constantSizesIndex == 0)
2983 config.AllocationSizes.push_back({4, 16, 1024});
2984 // Constant sizes
2985 else
2986 {
2987 config.AllocationSizes.push_back({1, 16, 16});
2988 config.AllocationSizes.push_back({1, 64, 64});
2989 config.AllocationSizes.push_back({1, 256, 256});
2990 config.AllocationSizes.push_back({1, 1024, 1024});
2991 }
2992 }
2993 // Large
2994 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
2995 {
2996 // Varying size
2997 if(constantSizesIndex == 0)
2998 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
2999 // Constant sizes
3000 else
3001 {
3002 config.AllocationSizes.push_back({1, 0x10000, 0x10000});
3003 config.AllocationSizes.push_back({1, 0x80000, 0x80000});
3004 config.AllocationSizes.push_back({1, 0x200000, 0x200000});
3005 config.AllocationSizes.push_back({1, 0xA00000, 0xA00000});
3006 }
3007 }
3008 }
3009 // Images present
3010 if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
3011 {
3012 // Small
3013 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
3014 {
3015 // Varying size
3016 if(constantSizesIndex == 0)
3017 config.AllocationSizes.push_back({4, 0, 0, 4, 32});
3018 // Constant sizes
3019 else
3020 {
3021 config.AllocationSizes.push_back({1, 0, 0, 4, 4});
3022 config.AllocationSizes.push_back({1, 0, 0, 8, 8});
3023 config.AllocationSizes.push_back({1, 0, 0, 16, 16});
3024 config.AllocationSizes.push_back({1, 0, 0, 32, 32});
3025 }
3026 }
3027 // Large
3028 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
3029 {
3030 // Varying size
3031 if(constantSizesIndex == 0)
3032 config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
3033 // Constant sizes
3034 else
3035 {
3036 config.AllocationSizes.push_back({1, 0, 0, 256, 256});
3037 config.AllocationSizes.push_back({1, 0, 0, 512, 512});
3038 config.AllocationSizes.push_back({1, 0, 0, 1024, 1024});
3039 config.AllocationSizes.push_back({1, 0, 0, 2048, 2048});
3040 }
3041 }
3042 }
3043
3044 const VkDeviceSize avgResourceSize = config.CalcAvgResourceSize();
3045 config.PoolSize = avgResourceSize * AVG_RESOURCES_PER_POOL;
3046
3047 // 0 = 66%, 1 = 133%, 2 = 100%, 3 = 33%, 4 = 166%
3048 size_t subscriptionModeCount;
3049 switch(ConfigType)
3050 {
3051 case CONFIG_TYPE_MINIMUM: subscriptionModeCount = 2; break;
3052 case CONFIG_TYPE_SMALL: subscriptionModeCount = 2; break;
3053 case CONFIG_TYPE_AVERAGE: subscriptionModeCount = 3; break;
3054 case CONFIG_TYPE_LARGE: subscriptionModeCount = 5; break;
3055 case CONFIG_TYPE_MAXIMUM: subscriptionModeCount = 5; break;
3056 default: assert(0);
3057 }
3058 for(size_t subscriptionModeIndex = 0; subscriptionModeIndex < subscriptionModeCount; ++subscriptionModeIndex)
3059 {
3060 std::string desc5 = desc4;
3061
3062 switch(subscriptionModeIndex)
3063 {
3064 case 0:
3065 desc5 += " Subscription_66%";
3066 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 66 / 100;
3067 break;
3068 case 1:
3069 desc5 += " Subscription_133%";
3070 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 133 / 100;
3071 break;
3072 case 2:
3073 desc5 += " Subscription_100%";
3074 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL;
3075 break;
3076 case 3:
3077 desc5 += " Subscription_33%";
3078 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 33 / 100;
3079 break;
3080 case 4:
3081 desc5 += " Subscription_166%";
3082 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 166 / 100;
3083 break;
3084 default:
3085 assert(0);
3086 }
3087
3088 config.TotalItemCount = config.UsedItemCountMax * 5;
3089 config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100;
3090
3091 const char* testDescription = desc5.c_str();
3092
3093 for(size_t repeat = 0; repeat < repeatCount; ++repeat)
3094 {
3095 printf("%s Repeat %u\n", testDescription, (uint32_t)repeat);
3096
3097 PoolTestResult result{};
3098 g_MemoryAliasingWarningEnabled = false;
3099 TestPool_Benchmark(result, config);
3100 g_MemoryAliasingWarningEnabled = true;
3101 WritePoolTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
3102 }
3103 }
3104 }
3105 }
3106 }
3107 }
3108}
3109
3110void Test()
3111{
3112 wprintf(L"TESTING:\n");
3113
Adam Sawicki212a4a62018-06-14 15:44:45 +02003114 // TEMP tests
3115
Adam Sawickib8333fb2018-03-13 16:15:53 +01003116 // # Simple tests
3117
3118 TestBasics();
Adam Sawicki212a4a62018-06-14 15:44:45 +02003119#if VMA_DEBUG_MARGIN
3120 TestDebugMargin();
3121#else
3122 TestPool_SameSize();
3123 TestHeapSizeLimit();
3124#endif
Adam Sawickie44c6262018-06-15 14:30:39 +02003125#if VMA_DEBUG_INITIALIZE_ALLOCATIONS
3126 TestAllocationsInitialization();
3127#endif
Adam Sawickib8333fb2018-03-13 16:15:53 +01003128 TestMapping();
3129 TestMappingMultithreaded();
3130 TestDefragmentationSimple();
3131 TestDefragmentationFull();
3132
3133 // # Detailed tests
3134 FILE* file;
3135 fopen_s(&file, "Results.csv", "w");
3136 assert(file != NULL);
3137
3138 WriteMainTestResultHeader(file);
3139 PerformMainTests(file);
3140 //PerformCustomMainTest(file);
3141
3142 WritePoolTestResultHeader(file);
3143 PerformPoolTests(file);
3144 //PerformCustomPoolTest(file);
3145
3146 fclose(file);
3147
3148 wprintf(L"Done.\n");
3149}
3150
Adam Sawickif1a793c2018-03-13 15:42:22 +01003151#endif // #ifdef _WIN32