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