blob: bc70869ce277de84ad6efb4c57005aae9bd9ded1 [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.
Adam Sawicki0ebdf0c2018-08-22 17:02:44 +02001736 // This way we make sure ring buffers wraps around, front in in the middle.
Adam Sawicki8cfe05f2018-08-22 16:48:17 +02001737 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
Adam Sawicki0ebdf0c2018-08-22 17:02:44 +02001789 // Delete buffers that are lost.
1790 for(size_t i = bufInfo.size(); i--; )
1791 {
1792 vmaGetAllocationInfo(g_hAllocator, bufInfo[i].Allocation, &allocInfo);
1793 if(allocInfo.deviceMemory == VK_NULL_HANDLE)
1794 {
1795 vmaDestroyBuffer(g_hAllocator, bufInfo[i].Buffer, bufInfo[i].Allocation);
1796 bufInfo.erase(bufInfo.begin() + i);
1797 }
1798 }
1799
1800 // Test vmaMakePoolAllocationsLost
1801 {
1802 vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex);
1803
1804 size_t lostAllocCount = SIZE_MAX;
1805 vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostAllocCount);
1806 assert(lostAllocCount > 0);
1807
1808 size_t realLostAllocCount = 0;
1809 for(size_t i = 0; i < bufInfo.size(); ++i)
1810 {
1811 vmaGetAllocationInfo(g_hAllocator, bufInfo[i].Allocation, &allocInfo);
1812 if(allocInfo.deviceMemory == VK_NULL_HANDLE)
1813 ++realLostAllocCount;
1814 }
1815 assert(realLostAllocCount == lostAllocCount);
1816 }
1817
Adam Sawicki8cfe05f2018-08-22 16:48:17 +02001818 // Destroy all the buffers in forward order.
1819 for(size_t i = 0; i < bufInfo.size(); ++i)
1820 vmaDestroyBuffer(g_hAllocator, bufInfo[i].Buffer, bufInfo[i].Allocation);
1821 bufInfo.clear();
1822 }
1823
Adam Sawickif799c4f2018-08-23 10:40:30 +02001824 // Try to create pool with maxBlockCount higher than 1. It should fail.
1825 {
1826 VmaPoolCreateInfo altPoolCreateInfo = poolCreateInfo;
1827 altPoolCreateInfo.maxBlockCount = 2;
1828
1829 VmaPool altPool = nullptr;
1830 res = vmaCreatePool(g_hAllocator, &altPoolCreateInfo, &altPool);
1831 assert(res != VK_SUCCESS);
1832 }
1833
Adam Sawicki0876c0d2018-06-20 15:18:11 +02001834 vmaDestroyPool(g_hAllocator, pool);
1835}
1836
Adam Sawickifd11d752018-08-22 15:02:10 +02001837static void ManuallyTestLinearAllocator()
1838{
1839 VmaStats origStats;
1840 vmaCalculateStats(g_hAllocator, &origStats);
1841
1842 wprintf(L"Manually test linear allocator\n");
1843
1844 RandomNumberGenerator rand{645332};
1845
1846 VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1847 sampleBufCreateInfo.size = 1024; // Whatever.
1848 sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
1849
1850 VmaAllocationCreateInfo sampleAllocCreateInfo = {};
1851 sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1852
1853 VmaPoolCreateInfo poolCreateInfo = {};
1854 VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
1855 assert(res == VK_SUCCESS);
1856
1857 poolCreateInfo.blockSize = 10 * 1024;
1858 poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT;
1859 poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
1860
1861 VmaPool pool = nullptr;
1862 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
1863 assert(res == VK_SUCCESS);
1864
1865 VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo;
1866
1867 VmaAllocationCreateInfo allocCreateInfo = {};
1868 allocCreateInfo.pool = pool;
1869
1870 std::vector<BufferInfo> bufInfo;
1871 VmaAllocationInfo allocInfo;
1872 BufferInfo newBufInfo;
1873
1874 // Test double stack.
1875 {
1876 /*
1877 Lower: Buffer 32 B, Buffer 1024 B, Buffer 32 B
1878 Upper: Buffer 16 B, Buffer 1024 B, Buffer 128 B
1879
1880 Totally:
1881 1 block allocated
1882 10240 Vulkan bytes
1883 6 new allocations
1884 2256 bytes in allocations
1885 */
1886
1887 bufCreateInfo.size = 32;
1888 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
1889 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
1890 assert(res == VK_SUCCESS);
1891 bufInfo.push_back(newBufInfo);
1892
1893 bufCreateInfo.size = 1024;
1894 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
1895 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
1896 assert(res == VK_SUCCESS);
1897 bufInfo.push_back(newBufInfo);
1898
1899 bufCreateInfo.size = 32;
1900 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
1901 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
1902 assert(res == VK_SUCCESS);
1903 bufInfo.push_back(newBufInfo);
1904
1905 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
1906
1907 bufCreateInfo.size = 128;
1908 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
1909 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
1910 assert(res == VK_SUCCESS);
1911 bufInfo.push_back(newBufInfo);
1912
1913 bufCreateInfo.size = 1024;
1914 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
1915 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
1916 assert(res == VK_SUCCESS);
1917 bufInfo.push_back(newBufInfo);
1918
1919 bufCreateInfo.size = 16;
1920 res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
1921 &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
1922 assert(res == VK_SUCCESS);
1923 bufInfo.push_back(newBufInfo);
1924
1925 VmaStats currStats;
1926 vmaCalculateStats(g_hAllocator, &currStats);
1927 VmaPoolStats poolStats;
1928 vmaGetPoolStats(g_hAllocator, pool, &poolStats);
1929
1930 char* statsStr = nullptr;
1931 vmaBuildStatsString(g_hAllocator, &statsStr, VK_TRUE);
1932
1933 // PUT BREAKPOINT HERE TO CHECK.
1934 // Inspect: currStats versus origStats, poolStats, statsStr.
1935 int I = 0;
1936
1937 vmaFreeStatsString(g_hAllocator, statsStr);
1938
1939 // Destroy the buffers in reverse order.
1940 while(!bufInfo.empty())
1941 {
1942 const BufferInfo& currBufInfo = bufInfo.back();
1943 vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
1944 bufInfo.pop_back();
1945 }
1946 }
1947
1948 vmaDestroyPool(g_hAllocator, pool);
1949}
1950
Adam Sawickib8333fb2018-03-13 16:15:53 +01001951static void TestPool_SameSize()
1952{
1953 const VkDeviceSize BUF_SIZE = 1024 * 1024;
1954 const size_t BUF_COUNT = 100;
1955 VkResult res;
1956
1957 RandomNumberGenerator rand{123};
1958
1959 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
1960 bufferInfo.size = BUF_SIZE;
1961 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1962
1963 uint32_t memoryTypeBits = UINT32_MAX;
1964 {
1965 VkBuffer dummyBuffer;
1966 res = vkCreateBuffer(g_hDevice, &bufferInfo, nullptr, &dummyBuffer);
1967 assert(res == VK_SUCCESS);
1968
1969 VkMemoryRequirements memReq;
1970 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
1971 memoryTypeBits = memReq.memoryTypeBits;
1972
1973 vkDestroyBuffer(g_hDevice, dummyBuffer, nullptr);
1974 }
1975
1976 VmaAllocationCreateInfo poolAllocInfo = {};
1977 poolAllocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1978 uint32_t memTypeIndex;
1979 res = vmaFindMemoryTypeIndex(
1980 g_hAllocator,
1981 memoryTypeBits,
1982 &poolAllocInfo,
1983 &memTypeIndex);
1984
1985 VmaPoolCreateInfo poolCreateInfo = {};
1986 poolCreateInfo.memoryTypeIndex = memTypeIndex;
1987 poolCreateInfo.blockSize = BUF_SIZE * BUF_COUNT / 4;
1988 poolCreateInfo.minBlockCount = 1;
1989 poolCreateInfo.maxBlockCount = 4;
1990 poolCreateInfo.frameInUseCount = 0;
1991
1992 VmaPool pool;
1993 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
1994 assert(res == VK_SUCCESS);
1995
1996 vmaSetCurrentFrameIndex(g_hAllocator, 1);
1997
1998 VmaAllocationCreateInfo allocInfo = {};
1999 allocInfo.pool = pool;
2000 allocInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT |
2001 VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT;
2002
2003 struct BufItem
2004 {
2005 VkBuffer Buf;
2006 VmaAllocation Alloc;
2007 };
2008 std::vector<BufItem> items;
2009
2010 // Fill entire pool.
2011 for(size_t i = 0; i < BUF_COUNT; ++i)
2012 {
2013 BufItem item;
2014 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
2015 assert(res == VK_SUCCESS);
2016 items.push_back(item);
2017 }
2018
2019 // Make sure that another allocation would fail.
2020 {
2021 BufItem item;
2022 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
2023 assert(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
2024 }
2025
2026 // Validate that no buffer is lost. Also check that they are not mapped.
2027 for(size_t i = 0; i < items.size(); ++i)
2028 {
2029 VmaAllocationInfo allocInfo;
2030 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
2031 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
2032 assert(allocInfo.pMappedData == nullptr);
2033 }
2034
2035 // Free some percent of random items.
2036 {
2037 const size_t PERCENT_TO_FREE = 10;
2038 size_t itemsToFree = items.size() * PERCENT_TO_FREE / 100;
2039 for(size_t i = 0; i < itemsToFree; ++i)
2040 {
2041 size_t index = (size_t)rand.Generate() % items.size();
2042 vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc);
2043 items.erase(items.begin() + index);
2044 }
2045 }
2046
2047 // Randomly allocate and free items.
2048 {
2049 const size_t OPERATION_COUNT = BUF_COUNT;
2050 for(size_t i = 0; i < OPERATION_COUNT; ++i)
2051 {
2052 bool allocate = rand.Generate() % 2 != 0;
2053 if(allocate)
2054 {
2055 if(items.size() < BUF_COUNT)
2056 {
2057 BufItem item;
2058 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
2059 assert(res == VK_SUCCESS);
2060 items.push_back(item);
2061 }
2062 }
2063 else // Free
2064 {
2065 if(!items.empty())
2066 {
2067 size_t index = (size_t)rand.Generate() % items.size();
2068 vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc);
2069 items.erase(items.begin() + index);
2070 }
2071 }
2072 }
2073 }
2074
2075 // Allocate up to maximum.
2076 while(items.size() < BUF_COUNT)
2077 {
2078 BufItem item;
2079 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
2080 assert(res == VK_SUCCESS);
2081 items.push_back(item);
2082 }
2083
2084 // Validate that no buffer is lost.
2085 for(size_t i = 0; i < items.size(); ++i)
2086 {
2087 VmaAllocationInfo allocInfo;
2088 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
2089 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
2090 }
2091
2092 // Next frame.
2093 vmaSetCurrentFrameIndex(g_hAllocator, 2);
2094
2095 // Allocate another BUF_COUNT buffers.
2096 for(size_t i = 0; i < BUF_COUNT; ++i)
2097 {
2098 BufItem item;
2099 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
2100 assert(res == VK_SUCCESS);
2101 items.push_back(item);
2102 }
2103
2104 // Make sure the first BUF_COUNT is lost. Delete them.
2105 for(size_t i = 0; i < BUF_COUNT; ++i)
2106 {
2107 VmaAllocationInfo allocInfo;
2108 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
2109 assert(allocInfo.deviceMemory == VK_NULL_HANDLE);
2110 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
2111 }
2112 items.erase(items.begin(), items.begin() + BUF_COUNT);
2113
2114 // Validate that no buffer is lost.
2115 for(size_t i = 0; i < items.size(); ++i)
2116 {
2117 VmaAllocationInfo allocInfo;
2118 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
2119 assert(allocInfo.deviceMemory != VK_NULL_HANDLE);
2120 }
2121
2122 // Free one item.
2123 vmaDestroyBuffer(g_hAllocator, items.back().Buf, items.back().Alloc);
2124 items.pop_back();
2125
2126 // Validate statistics.
2127 {
2128 VmaPoolStats poolStats = {};
2129 vmaGetPoolStats(g_hAllocator, pool, &poolStats);
2130 assert(poolStats.allocationCount == items.size());
2131 assert(poolStats.size = BUF_COUNT * BUF_SIZE);
2132 assert(poolStats.unusedRangeCount == 1);
2133 assert(poolStats.unusedRangeSizeMax == BUF_SIZE);
2134 assert(poolStats.unusedSize == BUF_SIZE);
2135 }
2136
2137 // Free all remaining items.
2138 for(size_t i = items.size(); i--; )
2139 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
2140 items.clear();
2141
2142 // Allocate maximum items again.
2143 for(size_t i = 0; i < BUF_COUNT; ++i)
2144 {
2145 BufItem item;
2146 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
2147 assert(res == VK_SUCCESS);
2148 items.push_back(item);
2149 }
2150
2151 // Delete every other item.
2152 for(size_t i = 0; i < BUF_COUNT / 2; ++i)
2153 {
2154 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
2155 items.erase(items.begin() + i);
2156 }
2157
2158 // Defragment!
2159 {
2160 std::vector<VmaAllocation> allocationsToDefragment(items.size());
2161 for(size_t i = 0; i < items.size(); ++i)
2162 allocationsToDefragment[i] = items[i].Alloc;
2163
2164 VmaDefragmentationStats defragmentationStats;
2165 res = vmaDefragment(g_hAllocator, allocationsToDefragment.data(), items.size(), nullptr, nullptr, &defragmentationStats);
2166 assert(res == VK_SUCCESS);
2167 assert(defragmentationStats.deviceMemoryBlocksFreed == 2);
2168 }
2169
2170 // Free all remaining items.
2171 for(size_t i = items.size(); i--; )
2172 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
2173 items.clear();
2174
2175 ////////////////////////////////////////////////////////////////////////////////
2176 // Test for vmaMakePoolAllocationsLost
2177
2178 // Allocate 4 buffers on frame 10.
2179 vmaSetCurrentFrameIndex(g_hAllocator, 10);
2180 for(size_t i = 0; i < 4; ++i)
2181 {
2182 BufItem item;
2183 res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
2184 assert(res == VK_SUCCESS);
2185 items.push_back(item);
2186 }
2187
2188 // Touch first 2 of them on frame 11.
2189 vmaSetCurrentFrameIndex(g_hAllocator, 11);
2190 for(size_t i = 0; i < 2; ++i)
2191 {
2192 VmaAllocationInfo allocInfo;
2193 vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
2194 }
2195
2196 // vmaMakePoolAllocationsLost. Only remaining 2 should be lost.
2197 size_t lostCount = 0xDEADC0DE;
2198 vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount);
2199 assert(lostCount == 2);
2200
2201 // Make another call. Now 0 should be lost.
2202 vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount);
2203 assert(lostCount == 0);
2204
2205 // Make another call, with null count. Should not crash.
2206 vmaMakePoolAllocationsLost(g_hAllocator, pool, nullptr);
2207
2208 // END: Free all remaining items.
2209 for(size_t i = items.size(); i--; )
2210 vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
2211
2212 items.clear();
2213
Adam Sawickid2924172018-06-11 12:48:46 +02002214 ////////////////////////////////////////////////////////////////////////////////
2215 // Test for allocation too large for pool
2216
2217 {
2218 VmaAllocationCreateInfo allocCreateInfo = {};
2219 allocCreateInfo.pool = pool;
2220
2221 VkMemoryRequirements memReq;
2222 memReq.memoryTypeBits = UINT32_MAX;
2223 memReq.alignment = 1;
2224 memReq.size = poolCreateInfo.blockSize + 4;
2225
2226 VmaAllocation alloc = nullptr;
2227 res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr);
2228 assert(res == VK_ERROR_OUT_OF_DEVICE_MEMORY && alloc == nullptr);
2229 }
2230
Adam Sawickib8333fb2018-03-13 16:15:53 +01002231 vmaDestroyPool(g_hAllocator, pool);
2232}
2233
Adam Sawickie44c6262018-06-15 14:30:39 +02002234static bool ValidatePattern(const void* pMemory, size_t size, uint8_t pattern)
2235{
2236 const uint8_t* pBytes = (const uint8_t*)pMemory;
2237 for(size_t i = 0; i < size; ++i)
2238 {
2239 if(pBytes[i] != pattern)
2240 {
2241 return false;
2242 }
2243 }
2244 return true;
2245}
2246
2247static void TestAllocationsInitialization()
2248{
2249 VkResult res;
2250
2251 const size_t BUF_SIZE = 1024;
2252
2253 // Create pool.
2254
2255 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2256 bufInfo.size = BUF_SIZE;
2257 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2258
2259 VmaAllocationCreateInfo dummyBufAllocCreateInfo = {};
2260 dummyBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
2261
2262 VmaPoolCreateInfo poolCreateInfo = {};
2263 poolCreateInfo.blockSize = BUF_SIZE * 10;
2264 poolCreateInfo.minBlockCount = 1; // To keep memory alive while pool exists.
2265 poolCreateInfo.maxBlockCount = 1;
2266 res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufInfo, &dummyBufAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
2267 assert(res == VK_SUCCESS);
2268
2269 VmaAllocationCreateInfo bufAllocCreateInfo = {};
2270 res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &bufAllocCreateInfo.pool);
2271 assert(res == VK_SUCCESS);
2272
2273 // Create one persistently mapped buffer to keep memory of this block mapped,
2274 // so that pointer to mapped data will remain (more or less...) valid even
2275 // after destruction of other allocations.
2276
2277 bufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
2278 VkBuffer firstBuf;
2279 VmaAllocation firstAlloc;
2280 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &firstBuf, &firstAlloc, nullptr);
2281 assert(res == VK_SUCCESS);
2282
2283 // Test buffers.
2284
2285 for(uint32_t i = 0; i < 2; ++i)
2286 {
2287 const bool persistentlyMapped = i == 0;
2288 bufAllocCreateInfo.flags = persistentlyMapped ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0;
2289 VkBuffer buf;
2290 VmaAllocation alloc;
2291 VmaAllocationInfo allocInfo;
2292 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &buf, &alloc, &allocInfo);
2293 assert(res == VK_SUCCESS);
2294
2295 void* pMappedData;
2296 if(!persistentlyMapped)
2297 {
2298 res = vmaMapMemory(g_hAllocator, alloc, &pMappedData);
2299 assert(res == VK_SUCCESS);
2300 }
2301 else
2302 {
2303 pMappedData = allocInfo.pMappedData;
2304 }
2305
2306 // Validate initialized content
2307 bool valid = ValidatePattern(pMappedData, BUF_SIZE, 0xDC);
2308 assert(valid);
2309
2310 if(!persistentlyMapped)
2311 {
2312 vmaUnmapMemory(g_hAllocator, alloc);
2313 }
2314
2315 vmaDestroyBuffer(g_hAllocator, buf, alloc);
2316
2317 // Validate freed content
2318 valid = ValidatePattern(pMappedData, BUF_SIZE, 0xEF);
2319 assert(valid);
2320 }
2321
2322 vmaDestroyBuffer(g_hAllocator, firstBuf, firstAlloc);
2323 vmaDestroyPool(g_hAllocator, bufAllocCreateInfo.pool);
2324}
2325
Adam Sawickib8333fb2018-03-13 16:15:53 +01002326static void TestPool_Benchmark(
2327 PoolTestResult& outResult,
2328 const PoolTestConfig& config)
2329{
2330 assert(config.ThreadCount > 0);
2331
2332 RandomNumberGenerator mainRand{config.RandSeed};
2333
2334 uint32_t allocationSizeProbabilitySum = std::accumulate(
2335 config.AllocationSizes.begin(),
2336 config.AllocationSizes.end(),
2337 0u,
2338 [](uint32_t sum, const AllocationSize& allocSize) {
2339 return sum + allocSize.Probability;
2340 });
2341
2342 VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2343 bufferInfo.size = 256; // Whatever.
2344 bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
2345
2346 VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
2347 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2348 imageInfo.extent.width = 256; // Whatever.
2349 imageInfo.extent.height = 256; // Whatever.
2350 imageInfo.extent.depth = 1;
2351 imageInfo.mipLevels = 1;
2352 imageInfo.arrayLayers = 1;
2353 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
2354 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // LINEAR if CPU memory.
2355 imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
2356 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // TRANSFER_SRC if CPU memory.
2357 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2358
2359 uint32_t bufferMemoryTypeBits = UINT32_MAX;
2360 {
2361 VkBuffer dummyBuffer;
2362 VkResult res = vkCreateBuffer(g_hDevice, &bufferInfo, nullptr, &dummyBuffer);
2363 assert(res == VK_SUCCESS);
2364
2365 VkMemoryRequirements memReq;
2366 vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
2367 bufferMemoryTypeBits = memReq.memoryTypeBits;
2368
2369 vkDestroyBuffer(g_hDevice, dummyBuffer, nullptr);
2370 }
2371
2372 uint32_t imageMemoryTypeBits = UINT32_MAX;
2373 {
2374 VkImage dummyImage;
2375 VkResult res = vkCreateImage(g_hDevice, &imageInfo, nullptr, &dummyImage);
2376 assert(res == VK_SUCCESS);
2377
2378 VkMemoryRequirements memReq;
2379 vkGetImageMemoryRequirements(g_hDevice, dummyImage, &memReq);
2380 imageMemoryTypeBits = memReq.memoryTypeBits;
2381
2382 vkDestroyImage(g_hDevice, dummyImage, nullptr);
2383 }
2384
2385 uint32_t memoryTypeBits = 0;
2386 if(config.UsesBuffers() && config.UsesImages())
2387 {
2388 memoryTypeBits = bufferMemoryTypeBits & imageMemoryTypeBits;
2389 if(memoryTypeBits == 0)
2390 {
2391 PrintWarning(L"Cannot test buffers + images in the same memory pool on this GPU.");
2392 return;
2393 }
2394 }
2395 else if(config.UsesBuffers())
2396 memoryTypeBits = bufferMemoryTypeBits;
2397 else if(config.UsesImages())
2398 memoryTypeBits = imageMemoryTypeBits;
2399 else
2400 assert(0);
2401
2402 VmaPoolCreateInfo poolCreateInfo = {};
2403 poolCreateInfo.memoryTypeIndex = 0;
2404 poolCreateInfo.minBlockCount = 1;
2405 poolCreateInfo.maxBlockCount = 1;
2406 poolCreateInfo.blockSize = config.PoolSize;
2407 poolCreateInfo.frameInUseCount = 1;
2408
2409 VmaAllocationCreateInfo dummyAllocCreateInfo = {};
2410 dummyAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
2411 vmaFindMemoryTypeIndex(g_hAllocator, memoryTypeBits, &dummyAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
2412
2413 VmaPool pool;
2414 VkResult res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
2415 assert(res == VK_SUCCESS);
2416
2417 // Start time measurement - after creating pool and initializing data structures.
2418 time_point timeBeg = std::chrono::high_resolution_clock::now();
2419
2420 ////////////////////////////////////////////////////////////////////////////////
2421 // ThreadProc
2422 auto ThreadProc = [&](
2423 PoolTestThreadResult* outThreadResult,
2424 uint32_t randSeed,
2425 HANDLE frameStartEvent,
2426 HANDLE frameEndEvent) -> void
2427 {
2428 RandomNumberGenerator threadRand{randSeed};
2429
2430 outThreadResult->AllocationTimeMin = duration::max();
2431 outThreadResult->AllocationTimeSum = duration::zero();
2432 outThreadResult->AllocationTimeMax = duration::min();
2433 outThreadResult->DeallocationTimeMin = duration::max();
2434 outThreadResult->DeallocationTimeSum = duration::zero();
2435 outThreadResult->DeallocationTimeMax = duration::min();
2436 outThreadResult->AllocationCount = 0;
2437 outThreadResult->DeallocationCount = 0;
2438 outThreadResult->LostAllocationCount = 0;
2439 outThreadResult->LostAllocationTotalSize = 0;
2440 outThreadResult->FailedAllocationCount = 0;
2441 outThreadResult->FailedAllocationTotalSize = 0;
2442
2443 struct Item
2444 {
2445 VkDeviceSize BufferSize;
2446 VkExtent2D ImageSize;
2447 VkBuffer Buf;
2448 VkImage Image;
2449 VmaAllocation Alloc;
2450
2451 VkDeviceSize CalcSizeBytes() const
2452 {
2453 return BufferSize +
2454 ImageSize.width * ImageSize.height * 4;
2455 }
2456 };
2457 std::vector<Item> unusedItems, usedItems;
2458
2459 const size_t threadTotalItemCount = config.TotalItemCount / config.ThreadCount;
2460
2461 // Create all items - all unused, not yet allocated.
2462 for(size_t i = 0; i < threadTotalItemCount; ++i)
2463 {
2464 Item item = {};
2465
2466 uint32_t allocSizeIndex = 0;
2467 uint32_t r = threadRand.Generate() % allocationSizeProbabilitySum;
2468 while(r >= config.AllocationSizes[allocSizeIndex].Probability)
2469 r -= config.AllocationSizes[allocSizeIndex++].Probability;
2470
2471 const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex];
2472 if(allocSize.BufferSizeMax > 0)
2473 {
2474 assert(allocSize.BufferSizeMin > 0);
2475 assert(allocSize.ImageSizeMin == 0 && allocSize.ImageSizeMax == 0);
2476 if(allocSize.BufferSizeMax == allocSize.BufferSizeMin)
2477 item.BufferSize = allocSize.BufferSizeMin;
2478 else
2479 {
2480 item.BufferSize = allocSize.BufferSizeMin + threadRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin);
2481 item.BufferSize = item.BufferSize / 16 * 16;
2482 }
2483 }
2484 else
2485 {
2486 assert(allocSize.ImageSizeMin > 0 && allocSize.ImageSizeMax > 0);
2487 if(allocSize.ImageSizeMax == allocSize.ImageSizeMin)
2488 item.ImageSize.width = item.ImageSize.height = allocSize.ImageSizeMax;
2489 else
2490 {
2491 item.ImageSize.width = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
2492 item.ImageSize.height = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin);
2493 }
2494 }
2495
2496 unusedItems.push_back(item);
2497 }
2498
2499 auto Allocate = [&](Item& item) -> VkResult
2500 {
2501 VmaAllocationCreateInfo allocCreateInfo = {};
2502 allocCreateInfo.pool = pool;
2503 allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT |
2504 VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT;
2505
2506 if(item.BufferSize)
2507 {
2508 bufferInfo.size = item.BufferSize;
2509 PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
2510 return vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocCreateInfo, &item.Buf, &item.Alloc, nullptr);
2511 }
2512 else
2513 {
2514 assert(item.ImageSize.width && item.ImageSize.height);
2515
2516 imageInfo.extent.width = item.ImageSize.width;
2517 imageInfo.extent.height = item.ImageSize.height;
2518 PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
2519 return vmaCreateImage(g_hAllocator, &imageInfo, &allocCreateInfo, &item.Image, &item.Alloc, nullptr);
2520 }
2521 };
2522
2523 ////////////////////////////////////////////////////////////////////////////////
2524 // Frames
2525 for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex)
2526 {
2527 WaitForSingleObject(frameStartEvent, INFINITE);
2528
2529 // Always make some percent of used bufs unused, to choose different used ones.
2530 const size_t bufsToMakeUnused = usedItems.size() * config.ItemsToMakeUnusedPercent / 100;
2531 for(size_t i = 0; i < bufsToMakeUnused; ++i)
2532 {
2533 size_t index = threadRand.Generate() % usedItems.size();
2534 unusedItems.push_back(usedItems[index]);
2535 usedItems.erase(usedItems.begin() + index);
2536 }
2537
2538 // Determine which bufs we want to use in this frame.
2539 const size_t usedBufCount = (threadRand.Generate() % (config.UsedItemCountMax - config.UsedItemCountMin) + config.UsedItemCountMin)
2540 / config.ThreadCount;
2541 assert(usedBufCount < usedItems.size() + unusedItems.size());
2542 // Move some used to unused.
2543 while(usedBufCount < usedItems.size())
2544 {
2545 size_t index = threadRand.Generate() % usedItems.size();
2546 unusedItems.push_back(usedItems[index]);
2547 usedItems.erase(usedItems.begin() + index);
2548 }
2549 // Move some unused to used.
2550 while(usedBufCount > usedItems.size())
2551 {
2552 size_t index = threadRand.Generate() % unusedItems.size();
2553 usedItems.push_back(unusedItems[index]);
2554 unusedItems.erase(unusedItems.begin() + index);
2555 }
2556
2557 uint32_t touchExistingCount = 0;
2558 uint32_t touchLostCount = 0;
2559 uint32_t createSucceededCount = 0;
2560 uint32_t createFailedCount = 0;
2561
2562 // Touch all used bufs. If not created or lost, allocate.
2563 for(size_t i = 0; i < usedItems.size(); ++i)
2564 {
2565 Item& item = usedItems[i];
2566 // Not yet created.
2567 if(item.Alloc == VK_NULL_HANDLE)
2568 {
2569 res = Allocate(item);
2570 ++outThreadResult->AllocationCount;
2571 if(res != VK_SUCCESS)
2572 {
2573 item.Alloc = VK_NULL_HANDLE;
2574 item.Buf = VK_NULL_HANDLE;
2575 ++outThreadResult->FailedAllocationCount;
2576 outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes();
2577 ++createFailedCount;
2578 }
2579 else
2580 ++createSucceededCount;
2581 }
2582 else
2583 {
2584 // Touch.
2585 VmaAllocationInfo allocInfo;
2586 vmaGetAllocationInfo(g_hAllocator, item.Alloc, &allocInfo);
2587 // Lost.
2588 if(allocInfo.deviceMemory == VK_NULL_HANDLE)
2589 {
2590 ++touchLostCount;
2591
2592 // Destroy.
2593 {
2594 PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult);
2595 if(item.Buf)
2596 vmaDestroyBuffer(g_hAllocator, item.Buf, item.Alloc);
2597 else
2598 vmaDestroyImage(g_hAllocator, item.Image, item.Alloc);
2599 ++outThreadResult->DeallocationCount;
2600 }
2601 item.Alloc = VK_NULL_HANDLE;
2602 item.Buf = VK_NULL_HANDLE;
2603
2604 ++outThreadResult->LostAllocationCount;
2605 outThreadResult->LostAllocationTotalSize += item.CalcSizeBytes();
2606
2607 // Recreate.
2608 res = Allocate(item);
2609 ++outThreadResult->AllocationCount;
2610 // Creation failed.
2611 if(res != VK_SUCCESS)
2612 {
2613 ++outThreadResult->FailedAllocationCount;
2614 outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes();
2615 ++createFailedCount;
2616 }
2617 else
2618 ++createSucceededCount;
2619 }
2620 else
2621 ++touchExistingCount;
2622 }
2623 }
2624
2625 /*
2626 printf("Thread %u frame %u: Touch existing %u lost %u, create succeeded %u failed %u\n",
2627 randSeed, frameIndex,
2628 touchExistingCount, touchLostCount,
2629 createSucceededCount, createFailedCount);
2630 */
2631
2632 SetEvent(frameEndEvent);
2633 }
2634
2635 // Free all remaining items.
2636 for(size_t i = usedItems.size(); i--; )
2637 {
2638 PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult);
2639 if(usedItems[i].Buf)
2640 vmaDestroyBuffer(g_hAllocator, usedItems[i].Buf, usedItems[i].Alloc);
2641 else
2642 vmaDestroyImage(g_hAllocator, usedItems[i].Image, usedItems[i].Alloc);
2643 ++outThreadResult->DeallocationCount;
2644 }
2645 for(size_t i = unusedItems.size(); i--; )
2646 {
2647 PoolDeallocationTimeRegisterObj timeRegisterOb(*outThreadResult);
2648 if(unusedItems[i].Buf)
2649 vmaDestroyBuffer(g_hAllocator, unusedItems[i].Buf, unusedItems[i].Alloc);
2650 else
2651 vmaDestroyImage(g_hAllocator, unusedItems[i].Image, unusedItems[i].Alloc);
2652 ++outThreadResult->DeallocationCount;
2653 }
2654 };
2655
2656 // Launch threads.
2657 uint32_t threadRandSeed = mainRand.Generate();
2658 std::vector<HANDLE> frameStartEvents{config.ThreadCount};
2659 std::vector<HANDLE> frameEndEvents{config.ThreadCount};
2660 std::vector<std::thread> bkgThreads;
2661 std::vector<PoolTestThreadResult> threadResults{config.ThreadCount};
2662 for(uint32_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
2663 {
2664 frameStartEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL);
2665 frameEndEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL);
2666 bkgThreads.emplace_back(std::bind(
2667 ThreadProc,
2668 &threadResults[threadIndex],
2669 threadRandSeed + threadIndex,
2670 frameStartEvents[threadIndex],
2671 frameEndEvents[threadIndex]));
2672 }
2673
2674 // Execute frames.
2675 assert(config.ThreadCount <= MAXIMUM_WAIT_OBJECTS);
2676 for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex)
2677 {
2678 vmaSetCurrentFrameIndex(g_hAllocator, frameIndex);
2679 for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
2680 SetEvent(frameStartEvents[threadIndex]);
2681 WaitForMultipleObjects(config.ThreadCount, &frameEndEvents[0], TRUE, INFINITE);
2682 }
2683
2684 // Wait for threads finished
2685 for(size_t i = 0; i < bkgThreads.size(); ++i)
2686 {
2687 bkgThreads[i].join();
2688 CloseHandle(frameEndEvents[i]);
2689 CloseHandle(frameStartEvents[i]);
2690 }
2691 bkgThreads.clear();
2692
2693 // Finish time measurement - before destroying pool.
2694 outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg;
2695
2696 vmaDestroyPool(g_hAllocator, pool);
2697
2698 outResult.AllocationTimeMin = duration::max();
2699 outResult.AllocationTimeAvg = duration::zero();
2700 outResult.AllocationTimeMax = duration::min();
2701 outResult.DeallocationTimeMin = duration::max();
2702 outResult.DeallocationTimeAvg = duration::zero();
2703 outResult.DeallocationTimeMax = duration::min();
2704 outResult.LostAllocationCount = 0;
2705 outResult.LostAllocationTotalSize = 0;
2706 outResult.FailedAllocationCount = 0;
2707 outResult.FailedAllocationTotalSize = 0;
2708 size_t allocationCount = 0;
2709 size_t deallocationCount = 0;
2710 for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex)
2711 {
2712 const PoolTestThreadResult& threadResult = threadResults[threadIndex];
2713 outResult.AllocationTimeMin = std::min(outResult.AllocationTimeMin, threadResult.AllocationTimeMin);
2714 outResult.AllocationTimeMax = std::max(outResult.AllocationTimeMax, threadResult.AllocationTimeMax);
2715 outResult.AllocationTimeAvg += threadResult.AllocationTimeSum;
2716 outResult.DeallocationTimeMin = std::min(outResult.DeallocationTimeMin, threadResult.DeallocationTimeMin);
2717 outResult.DeallocationTimeMax = std::max(outResult.DeallocationTimeMax, threadResult.DeallocationTimeMax);
2718 outResult.DeallocationTimeAvg += threadResult.DeallocationTimeSum;
2719 allocationCount += threadResult.AllocationCount;
2720 deallocationCount += threadResult.DeallocationCount;
2721 outResult.FailedAllocationCount += threadResult.FailedAllocationCount;
2722 outResult.FailedAllocationTotalSize += threadResult.FailedAllocationTotalSize;
2723 outResult.LostAllocationCount += threadResult.LostAllocationCount;
2724 outResult.LostAllocationTotalSize += threadResult.LostAllocationTotalSize;
2725 }
2726 if(allocationCount)
2727 outResult.AllocationTimeAvg /= allocationCount;
2728 if(deallocationCount)
2729 outResult.DeallocationTimeAvg /= deallocationCount;
2730}
2731
2732static inline bool MemoryRegionsOverlap(char* ptr1, size_t size1, char* ptr2, size_t size2)
2733{
2734 if(ptr1 < ptr2)
2735 return ptr1 + size1 > ptr2;
2736 else if(ptr2 < ptr1)
2737 return ptr2 + size2 > ptr1;
2738 else
2739 return true;
2740}
2741
2742static void TestMapping()
2743{
2744 wprintf(L"Testing mapping...\n");
2745
2746 VkResult res;
2747 uint32_t memTypeIndex = UINT32_MAX;
2748
2749 enum TEST
2750 {
2751 TEST_NORMAL,
2752 TEST_POOL,
2753 TEST_DEDICATED,
2754 TEST_COUNT
2755 };
2756 for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex)
2757 {
2758 VmaPool pool = nullptr;
2759 if(testIndex == TEST_POOL)
2760 {
2761 assert(memTypeIndex != UINT32_MAX);
2762 VmaPoolCreateInfo poolInfo = {};
2763 poolInfo.memoryTypeIndex = memTypeIndex;
2764 res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
2765 assert(res == VK_SUCCESS);
2766 }
2767
2768 VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2769 bufInfo.size = 0x10000;
2770 bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2771
2772 VmaAllocationCreateInfo allocCreateInfo = {};
2773 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
2774 allocCreateInfo.pool = pool;
2775 if(testIndex == TEST_DEDICATED)
2776 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
2777
2778 VmaAllocationInfo allocInfo;
2779
2780 // Mapped manually
2781
2782 // Create 2 buffers.
2783 BufferInfo bufferInfos[3];
2784 for(size_t i = 0; i < 2; ++i)
2785 {
2786 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
2787 &bufferInfos[i].Buffer, &bufferInfos[i].Allocation, &allocInfo);
2788 assert(res == VK_SUCCESS);
2789 assert(allocInfo.pMappedData == nullptr);
2790 memTypeIndex = allocInfo.memoryType;
2791 }
2792
2793 // Map buffer 0.
2794 char* data00 = nullptr;
2795 res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data00);
2796 assert(res == VK_SUCCESS && data00 != nullptr);
2797 data00[0xFFFF] = data00[0];
2798
2799 // Map buffer 0 second time.
2800 char* data01 = nullptr;
2801 res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data01);
2802 assert(res == VK_SUCCESS && data01 == data00);
2803
2804 // Map buffer 1.
2805 char* data1 = nullptr;
2806 res = vmaMapMemory(g_hAllocator, bufferInfos[1].Allocation, (void**)&data1);
2807 assert(res == VK_SUCCESS && data1 != nullptr);
2808 assert(!MemoryRegionsOverlap(data00, (size_t)bufInfo.size, data1, (size_t)bufInfo.size));
2809 data1[0xFFFF] = data1[0];
2810
2811 // Unmap buffer 0 two times.
2812 vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
2813 vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
2814 vmaGetAllocationInfo(g_hAllocator, bufferInfos[0].Allocation, &allocInfo);
2815 assert(allocInfo.pMappedData == nullptr);
2816
2817 // Unmap buffer 1.
2818 vmaUnmapMemory(g_hAllocator, bufferInfos[1].Allocation);
2819 vmaGetAllocationInfo(g_hAllocator, bufferInfos[1].Allocation, &allocInfo);
2820 assert(allocInfo.pMappedData == nullptr);
2821
2822 // Create 3rd buffer - persistently mapped.
2823 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
2824 res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
2825 &bufferInfos[2].Buffer, &bufferInfos[2].Allocation, &allocInfo);
2826 assert(res == VK_SUCCESS && allocInfo.pMappedData != nullptr);
2827
2828 // Map buffer 2.
2829 char* data2 = nullptr;
2830 res = vmaMapMemory(g_hAllocator, bufferInfos[2].Allocation, (void**)&data2);
2831 assert(res == VK_SUCCESS && data2 == allocInfo.pMappedData);
2832 data2[0xFFFF] = data2[0];
2833
2834 // Unmap buffer 2.
2835 vmaUnmapMemory(g_hAllocator, bufferInfos[2].Allocation);
2836 vmaGetAllocationInfo(g_hAllocator, bufferInfos[2].Allocation, &allocInfo);
2837 assert(allocInfo.pMappedData == data2);
2838
2839 // Destroy all buffers.
2840 for(size_t i = 3; i--; )
2841 vmaDestroyBuffer(g_hAllocator, bufferInfos[i].Buffer, bufferInfos[i].Allocation);
2842
2843 vmaDestroyPool(g_hAllocator, pool);
2844 }
2845}
2846
2847static void TestMappingMultithreaded()
2848{
2849 wprintf(L"Testing mapping multithreaded...\n");
2850
2851 static const uint32_t threadCount = 16;
2852 static const uint32_t bufferCount = 1024;
2853 static const uint32_t threadBufferCount = bufferCount / threadCount;
2854
2855 VkResult res;
2856 volatile uint32_t memTypeIndex = UINT32_MAX;
2857
2858 enum TEST
2859 {
2860 TEST_NORMAL,
2861 TEST_POOL,
2862 TEST_DEDICATED,
2863 TEST_COUNT
2864 };
2865 for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex)
2866 {
2867 VmaPool pool = nullptr;
2868 if(testIndex == TEST_POOL)
2869 {
2870 assert(memTypeIndex != UINT32_MAX);
2871 VmaPoolCreateInfo poolInfo = {};
2872 poolInfo.memoryTypeIndex = memTypeIndex;
2873 res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
2874 assert(res == VK_SUCCESS);
2875 }
2876
2877 VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
2878 bufCreateInfo.size = 0x10000;
2879 bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
2880
2881 VmaAllocationCreateInfo allocCreateInfo = {};
2882 allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
2883 allocCreateInfo.pool = pool;
2884 if(testIndex == TEST_DEDICATED)
2885 allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
2886
2887 std::thread threads[threadCount];
2888 for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex)
2889 {
2890 threads[threadIndex] = std::thread([=, &memTypeIndex](){
2891 // ======== THREAD FUNCTION ========
2892
2893 RandomNumberGenerator rand{threadIndex};
2894
2895 enum class MODE
2896 {
2897 // Don't map this buffer at all.
2898 DONT_MAP,
2899 // Map and quickly unmap.
2900 MAP_FOR_MOMENT,
2901 // Map and unmap before destruction.
2902 MAP_FOR_LONGER,
2903 // Map two times. Quickly unmap, second unmap before destruction.
2904 MAP_TWO_TIMES,
2905 // Create this buffer as persistently mapped.
2906 PERSISTENTLY_MAPPED,
2907 COUNT
2908 };
2909 std::vector<BufferInfo> bufInfos{threadBufferCount};
2910 std::vector<MODE> bufModes{threadBufferCount};
2911
2912 for(uint32_t bufferIndex = 0; bufferIndex < threadBufferCount; ++bufferIndex)
2913 {
2914 BufferInfo& bufInfo = bufInfos[bufferIndex];
2915 const MODE mode = (MODE)(rand.Generate() % (uint32_t)MODE::COUNT);
2916 bufModes[bufferIndex] = mode;
2917
2918 VmaAllocationCreateInfo localAllocCreateInfo = allocCreateInfo;
2919 if(mode == MODE::PERSISTENTLY_MAPPED)
2920 localAllocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
2921
2922 VmaAllocationInfo allocInfo;
2923 VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &localAllocCreateInfo,
2924 &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo);
2925 assert(res == VK_SUCCESS);
2926
2927 if(memTypeIndex == UINT32_MAX)
2928 memTypeIndex = allocInfo.memoryType;
2929
2930 char* data = nullptr;
2931
2932 if(mode == MODE::PERSISTENTLY_MAPPED)
2933 {
2934 data = (char*)allocInfo.pMappedData;
2935 assert(data != nullptr);
2936 }
2937 else if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_FOR_LONGER ||
2938 mode == MODE::MAP_TWO_TIMES)
2939 {
2940 assert(data == nullptr);
2941 res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data);
2942 assert(res == VK_SUCCESS && data != nullptr);
2943
2944 if(mode == MODE::MAP_TWO_TIMES)
2945 {
2946 char* data2 = nullptr;
2947 res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data2);
2948 assert(res == VK_SUCCESS && data2 == data);
2949 }
2950 }
2951 else if(mode == MODE::DONT_MAP)
2952 {
2953 assert(allocInfo.pMappedData == nullptr);
2954 }
2955 else
2956 assert(0);
2957
2958 // Test if reading and writing from the beginning and end of mapped memory doesn't crash.
2959 if(data)
2960 data[0xFFFF] = data[0];
2961
2962 if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_TWO_TIMES)
2963 {
2964 vmaUnmapMemory(g_hAllocator, bufInfo.Allocation);
2965
2966 VmaAllocationInfo allocInfo;
2967 vmaGetAllocationInfo(g_hAllocator, bufInfo.Allocation, &allocInfo);
2968 if(mode == MODE::MAP_FOR_MOMENT)
2969 assert(allocInfo.pMappedData == nullptr);
2970 else
2971 assert(allocInfo.pMappedData == data);
2972 }
2973
2974 switch(rand.Generate() % 3)
2975 {
2976 case 0: Sleep(0); break; // Yield.
2977 case 1: Sleep(10); break; // 10 ms
2978 // default: No sleep.
2979 }
2980
2981 // Test if reading and writing from the beginning and end of mapped memory doesn't crash.
2982 if(data)
2983 data[0xFFFF] = data[0];
2984 }
2985
2986 for(size_t bufferIndex = threadBufferCount; bufferIndex--; )
2987 {
2988 if(bufModes[bufferIndex] == MODE::MAP_FOR_LONGER ||
2989 bufModes[bufferIndex] == MODE::MAP_TWO_TIMES)
2990 {
2991 vmaUnmapMemory(g_hAllocator, bufInfos[bufferIndex].Allocation);
2992
2993 VmaAllocationInfo allocInfo;
2994 vmaGetAllocationInfo(g_hAllocator, bufInfos[bufferIndex].Allocation, &allocInfo);
2995 assert(allocInfo.pMappedData == nullptr);
2996 }
2997
2998 vmaDestroyBuffer(g_hAllocator, bufInfos[bufferIndex].Buffer, bufInfos[bufferIndex].Allocation);
2999 }
3000 });
3001 }
3002
3003 for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex)
3004 threads[threadIndex].join();
3005
3006 vmaDestroyPool(g_hAllocator, pool);
3007 }
3008}
3009
3010static void WriteMainTestResultHeader(FILE* file)
3011{
3012 fprintf(file,
3013 "Code,Test,Time,"
3014 "Config,"
3015 "Total Time (us),"
3016 "Allocation Time Min (us),"
3017 "Allocation Time Avg (us),"
3018 "Allocation Time Max (us),"
3019 "Deallocation Time Min (us),"
3020 "Deallocation Time Avg (us),"
3021 "Deallocation Time Max (us),"
3022 "Total Memory Allocated (B),"
3023 "Free Range Size Avg (B),"
3024 "Free Range Size Max (B)\n");
3025}
3026
3027static void WriteMainTestResult(
3028 FILE* file,
3029 const char* codeDescription,
3030 const char* testDescription,
3031 const Config& config, const Result& result)
3032{
3033 float totalTimeSeconds = ToFloatSeconds(result.TotalTime);
3034 float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin);
3035 float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg);
3036 float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax);
3037 float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin);
3038 float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg);
3039 float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax);
3040
3041 time_t rawTime; time(&rawTime);
3042 struct tm timeInfo; localtime_s(&timeInfo, &rawTime);
3043 char timeStr[128];
3044 strftime(timeStr, _countof(timeStr), "%c", &timeInfo);
3045
3046 fprintf(file,
3047 "%s,%s,%s,"
3048 "BeginBytesToAllocate=%I64u MaxBytesToAllocate=%I64u AdditionalOperationCount=%u ThreadCount=%u FreeOrder=%d,"
3049 "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n",
3050 codeDescription,
3051 testDescription,
3052 timeStr,
3053 config.BeginBytesToAllocate, config.MaxBytesToAllocate, config.AdditionalOperationCount, config.ThreadCount, (uint32_t)config.FreeOrder,
3054 totalTimeSeconds * 1e6f,
3055 allocationTimeMinSeconds * 1e6f,
3056 allocationTimeAvgSeconds * 1e6f,
3057 allocationTimeMaxSeconds * 1e6f,
3058 deallocationTimeMinSeconds * 1e6f,
3059 deallocationTimeAvgSeconds * 1e6f,
3060 deallocationTimeMaxSeconds * 1e6f,
3061 result.TotalMemoryAllocated,
3062 result.FreeRangeSizeAvg,
3063 result.FreeRangeSizeMax);
3064}
3065
3066static void WritePoolTestResultHeader(FILE* file)
3067{
3068 fprintf(file,
3069 "Code,Test,Time,"
3070 "Config,"
3071 "Total Time (us),"
3072 "Allocation Time Min (us),"
3073 "Allocation Time Avg (us),"
3074 "Allocation Time Max (us),"
3075 "Deallocation Time Min (us),"
3076 "Deallocation Time Avg (us),"
3077 "Deallocation Time Max (us),"
3078 "Lost Allocation Count,"
3079 "Lost Allocation Total Size (B),"
3080 "Failed Allocation Count,"
3081 "Failed Allocation Total Size (B)\n");
3082}
3083
3084static void WritePoolTestResult(
3085 FILE* file,
3086 const char* codeDescription,
3087 const char* testDescription,
3088 const PoolTestConfig& config,
3089 const PoolTestResult& result)
3090{
3091 float totalTimeSeconds = ToFloatSeconds(result.TotalTime);
3092 float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin);
3093 float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg);
3094 float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax);
3095 float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin);
3096 float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg);
3097 float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax);
3098
3099 time_t rawTime; time(&rawTime);
3100 struct tm timeInfo; localtime_s(&timeInfo, &rawTime);
3101 char timeStr[128];
3102 strftime(timeStr, _countof(timeStr), "%c", &timeInfo);
3103
3104 fprintf(file,
3105 "%s,%s,%s,"
3106 "ThreadCount=%u PoolSize=%llu FrameCount=%u TotalItemCount=%u UsedItemCount=%u...%u ItemsToMakeUnusedPercent=%u,"
3107 "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u,%I64u\n",
3108 // General
3109 codeDescription,
3110 testDescription,
3111 timeStr,
3112 // Config
3113 config.ThreadCount,
3114 (unsigned long long)config.PoolSize,
3115 config.FrameCount,
3116 config.TotalItemCount,
3117 config.UsedItemCountMin,
3118 config.UsedItemCountMax,
3119 config.ItemsToMakeUnusedPercent,
3120 // Results
3121 totalTimeSeconds * 1e6f,
3122 allocationTimeMinSeconds * 1e6f,
3123 allocationTimeAvgSeconds * 1e6f,
3124 allocationTimeMaxSeconds * 1e6f,
3125 deallocationTimeMinSeconds * 1e6f,
3126 deallocationTimeAvgSeconds * 1e6f,
3127 deallocationTimeMaxSeconds * 1e6f,
3128 result.LostAllocationCount,
3129 result.LostAllocationTotalSize,
3130 result.FailedAllocationCount,
3131 result.FailedAllocationTotalSize);
3132}
3133
3134static void PerformCustomMainTest(FILE* file)
3135{
3136 Config config{};
3137 config.RandSeed = 65735476;
3138 //config.MaxBytesToAllocate = 4ull * 1024 * 1024; // 4 MB
3139 config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB
3140 config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY
3141 config.FreeOrder = FREE_ORDER::FORWARD;
3142 config.ThreadCount = 16;
3143 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
3144
3145 // Buffers
3146 //config.AllocationSizes.push_back({4, 16, 1024});
3147 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
3148
3149 // Images
3150 //config.AllocationSizes.push_back({4, 0, 0, 4, 32});
3151 //config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
3152
3153 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100;
3154 config.AdditionalOperationCount = 1024;
3155
3156 Result result{};
3157 VkResult res = MainTest(result, config);
3158 assert(res == VK_SUCCESS);
3159 WriteMainTestResult(file, "Foo", "CustomTest", config, result);
3160}
3161
3162static void PerformCustomPoolTest(FILE* file)
3163{
3164 PoolTestConfig config;
3165 config.PoolSize = 100 * 1024 * 1024;
3166 config.RandSeed = 2345764;
3167 config.ThreadCount = 1;
3168 config.FrameCount = 200;
3169 config.ItemsToMakeUnusedPercent = 2;
3170
3171 AllocationSize allocSize = {};
3172 allocSize.BufferSizeMin = 1024;
3173 allocSize.BufferSizeMax = 1024 * 1024;
3174 allocSize.Probability = 1;
3175 config.AllocationSizes.push_back(allocSize);
3176
3177 allocSize.BufferSizeMin = 0;
3178 allocSize.BufferSizeMax = 0;
3179 allocSize.ImageSizeMin = 128;
3180 allocSize.ImageSizeMax = 1024;
3181 allocSize.Probability = 1;
3182 config.AllocationSizes.push_back(allocSize);
3183
3184 config.PoolSize = config.CalcAvgResourceSize() * 200;
3185 config.UsedItemCountMax = 160;
3186 config.TotalItemCount = config.UsedItemCountMax * 10;
3187 config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100;
3188
3189 g_MemoryAliasingWarningEnabled = false;
3190 PoolTestResult result = {};
3191 TestPool_Benchmark(result, config);
3192 g_MemoryAliasingWarningEnabled = true;
3193
3194 WritePoolTestResult(file, "Code desc", "Test desc", config, result);
3195}
3196
3197enum CONFIG_TYPE {
3198 CONFIG_TYPE_MINIMUM,
3199 CONFIG_TYPE_SMALL,
3200 CONFIG_TYPE_AVERAGE,
3201 CONFIG_TYPE_LARGE,
3202 CONFIG_TYPE_MAXIMUM,
3203 CONFIG_TYPE_COUNT
3204};
3205
3206static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_SMALL;
3207//static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_LARGE;
3208static const char* CODE_DESCRIPTION = "Foo";
3209
3210static void PerformMainTests(FILE* file)
3211{
3212 uint32_t repeatCount = 1;
3213 if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3;
3214
3215 Config config{};
3216 config.RandSeed = 65735476;
3217 config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY
3218 config.FreeOrder = FREE_ORDER::FORWARD;
3219
3220 size_t threadCountCount = 1;
3221 switch(ConfigType)
3222 {
3223 case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break;
3224 case CONFIG_TYPE_SMALL: threadCountCount = 2; break;
3225 case CONFIG_TYPE_AVERAGE: threadCountCount = 3; break;
3226 case CONFIG_TYPE_LARGE: threadCountCount = 5; break;
3227 case CONFIG_TYPE_MAXIMUM: threadCountCount = 7; break;
3228 default: assert(0);
3229 }
3230 for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex)
3231 {
3232 std::string desc1;
3233
3234 switch(threadCountIndex)
3235 {
3236 case 0:
3237 desc1 += "1_thread";
3238 config.ThreadCount = 1;
3239 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
3240 break;
3241 case 1:
3242 desc1 += "16_threads+0%_common";
3243 config.ThreadCount = 16;
3244 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
3245 break;
3246 case 2:
3247 desc1 += "16_threads+50%_common";
3248 config.ThreadCount = 16;
3249 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
3250 break;
3251 case 3:
3252 desc1 += "16_threads+100%_common";
3253 config.ThreadCount = 16;
3254 config.ThreadsUsingCommonAllocationsProbabilityPercent = 100;
3255 break;
3256 case 4:
3257 desc1 += "2_threads+0%_common";
3258 config.ThreadCount = 2;
3259 config.ThreadsUsingCommonAllocationsProbabilityPercent = 0;
3260 break;
3261 case 5:
3262 desc1 += "2_threads+50%_common";
3263 config.ThreadCount = 2;
3264 config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
3265 break;
3266 case 6:
3267 desc1 += "2_threads+100%_common";
3268 config.ThreadCount = 2;
3269 config.ThreadsUsingCommonAllocationsProbabilityPercent = 100;
3270 break;
3271 default:
3272 assert(0);
3273 }
3274
3275 // 0 = buffers, 1 = images, 2 = buffers and images
3276 size_t buffersVsImagesCount = 2;
3277 if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount;
3278 for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex)
3279 {
3280 std::string desc2 = desc1;
3281 switch(buffersVsImagesIndex)
3282 {
3283 case 0: desc2 += " Buffers"; break;
3284 case 1: desc2 += " Images"; break;
3285 case 2: desc2 += " Buffers+Images"; break;
3286 default: assert(0);
3287 }
3288
3289 // 0 = small, 1 = large, 2 = small and large
3290 size_t smallVsLargeCount = 2;
3291 if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount;
3292 for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex)
3293 {
3294 std::string desc3 = desc2;
3295 switch(smallVsLargeIndex)
3296 {
3297 case 0: desc3 += " Small"; break;
3298 case 1: desc3 += " Large"; break;
3299 case 2: desc3 += " Small+Large"; break;
3300 default: assert(0);
3301 }
3302
3303 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
3304 config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB
3305 else
3306 config.MaxBytesToAllocate = 4ull * 1024 * 1024;
3307
3308 // 0 = varying sizes min...max, 1 = set of constant sizes
3309 size_t constantSizesCount = 1;
3310 if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount;
3311 for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex)
3312 {
3313 std::string desc4 = desc3;
3314 switch(constantSizesIndex)
3315 {
3316 case 0: desc4 += " Varying_sizes"; break;
3317 case 1: desc4 += " Constant_sizes"; break;
3318 default: assert(0);
3319 }
3320
3321 config.AllocationSizes.clear();
3322 // Buffers present
3323 if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
3324 {
3325 // Small
3326 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
3327 {
3328 // Varying size
3329 if(constantSizesIndex == 0)
3330 config.AllocationSizes.push_back({4, 16, 1024});
3331 // Constant sizes
3332 else
3333 {
3334 config.AllocationSizes.push_back({1, 16, 16});
3335 config.AllocationSizes.push_back({1, 64, 64});
3336 config.AllocationSizes.push_back({1, 256, 256});
3337 config.AllocationSizes.push_back({1, 1024, 1024});
3338 }
3339 }
3340 // Large
3341 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
3342 {
3343 // Varying size
3344 if(constantSizesIndex == 0)
3345 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
3346 // Constant sizes
3347 else
3348 {
3349 config.AllocationSizes.push_back({1, 0x10000, 0x10000});
3350 config.AllocationSizes.push_back({1, 0x80000, 0x80000});
3351 config.AllocationSizes.push_back({1, 0x200000, 0x200000});
3352 config.AllocationSizes.push_back({1, 0xA00000, 0xA00000});
3353 }
3354 }
3355 }
3356 // Images present
3357 if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
3358 {
3359 // Small
3360 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
3361 {
3362 // Varying size
3363 if(constantSizesIndex == 0)
3364 config.AllocationSizes.push_back({4, 0, 0, 4, 32});
3365 // Constant sizes
3366 else
3367 {
3368 config.AllocationSizes.push_back({1, 0, 0, 4, 4});
3369 config.AllocationSizes.push_back({1, 0, 0, 8, 8});
3370 config.AllocationSizes.push_back({1, 0, 0, 16, 16});
3371 config.AllocationSizes.push_back({1, 0, 0, 32, 32});
3372 }
3373 }
3374 // Large
3375 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
3376 {
3377 // Varying size
3378 if(constantSizesIndex == 0)
3379 config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
3380 // Constant sizes
3381 else
3382 {
3383 config.AllocationSizes.push_back({1, 0, 0, 256, 256});
3384 config.AllocationSizes.push_back({1, 0, 0, 512, 512});
3385 config.AllocationSizes.push_back({1, 0, 0, 1024, 1024});
3386 config.AllocationSizes.push_back({1, 0, 0, 2048, 2048});
3387 }
3388 }
3389 }
3390
3391 // 0 = 100%, additional_operations = 0, 1 = 50%, 2 = 5%, 3 = 95% additional_operations = a lot
3392 size_t beginBytesToAllocateCount = 1;
3393 if(ConfigType >= CONFIG_TYPE_SMALL) ++beginBytesToAllocateCount;
3394 if(ConfigType >= CONFIG_TYPE_AVERAGE) ++beginBytesToAllocateCount;
3395 if(ConfigType >= CONFIG_TYPE_LARGE) ++beginBytesToAllocateCount;
3396 for(size_t beginBytesToAllocateIndex = 0; beginBytesToAllocateIndex < beginBytesToAllocateCount; ++beginBytesToAllocateIndex)
3397 {
3398 std::string desc5 = desc4;
3399
3400 switch(beginBytesToAllocateIndex)
3401 {
3402 case 0:
3403 desc5 += " Allocate_100%";
3404 config.BeginBytesToAllocate = config.MaxBytesToAllocate;
3405 config.AdditionalOperationCount = 0;
3406 break;
3407 case 1:
3408 desc5 += " Allocate_50%+Operations";
3409 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 50 / 100;
3410 config.AdditionalOperationCount = 1024;
3411 break;
3412 case 2:
3413 desc5 += " Allocate_5%+Operations";
3414 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100;
3415 config.AdditionalOperationCount = 1024;
3416 break;
3417 case 3:
3418 desc5 += " Allocate_95%+Operations";
3419 config.BeginBytesToAllocate = config.MaxBytesToAllocate * 95 / 100;
3420 config.AdditionalOperationCount = 1024;
3421 break;
3422 default:
3423 assert(0);
3424 }
3425
3426 const char* testDescription = desc5.c_str();
3427
3428 for(size_t repeat = 0; repeat < repeatCount; ++repeat)
3429 {
3430 printf("%s Repeat %u\n", testDescription, (uint32_t)repeat);
3431
3432 Result result{};
3433 VkResult res = MainTest(result, config);
3434 assert(res == VK_SUCCESS);
3435 WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
3436 }
3437 }
3438 }
3439 }
3440 }
3441 }
3442}
3443
3444static void PerformPoolTests(FILE* file)
3445{
3446 const size_t AVG_RESOURCES_PER_POOL = 300;
3447
3448 uint32_t repeatCount = 1;
3449 if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3;
3450
3451 PoolTestConfig config{};
3452 config.RandSeed = 2346343;
3453 config.FrameCount = 200;
3454 config.ItemsToMakeUnusedPercent = 2;
3455
3456 size_t threadCountCount = 1;
3457 switch(ConfigType)
3458 {
3459 case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break;
3460 case CONFIG_TYPE_SMALL: threadCountCount = 2; break;
3461 case CONFIG_TYPE_AVERAGE: threadCountCount = 2; break;
3462 case CONFIG_TYPE_LARGE: threadCountCount = 3; break;
3463 case CONFIG_TYPE_MAXIMUM: threadCountCount = 3; break;
3464 default: assert(0);
3465 }
3466 for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex)
3467 {
3468 std::string desc1;
3469
3470 switch(threadCountIndex)
3471 {
3472 case 0:
3473 desc1 += "1_thread";
3474 config.ThreadCount = 1;
3475 break;
3476 case 1:
3477 desc1 += "16_threads";
3478 config.ThreadCount = 16;
3479 break;
3480 case 2:
3481 desc1 += "2_threads";
3482 config.ThreadCount = 2;
3483 break;
3484 default:
3485 assert(0);
3486 }
3487
3488 // 0 = buffers, 1 = images, 2 = buffers and images
3489 size_t buffersVsImagesCount = 2;
3490 if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount;
3491 for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex)
3492 {
3493 std::string desc2 = desc1;
3494 switch(buffersVsImagesIndex)
3495 {
3496 case 0: desc2 += " Buffers"; break;
3497 case 1: desc2 += " Images"; break;
3498 case 2: desc2 += " Buffers+Images"; break;
3499 default: assert(0);
3500 }
3501
3502 // 0 = small, 1 = large, 2 = small and large
3503 size_t smallVsLargeCount = 2;
3504 if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount;
3505 for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex)
3506 {
3507 std::string desc3 = desc2;
3508 switch(smallVsLargeIndex)
3509 {
3510 case 0: desc3 += " Small"; break;
3511 case 1: desc3 += " Large"; break;
3512 case 2: desc3 += " Small+Large"; break;
3513 default: assert(0);
3514 }
3515
3516 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
3517 config.PoolSize = 6ull * 1024 * 1024 * 1024; // 6 GB
3518 else
3519 config.PoolSize = 4ull * 1024 * 1024;
3520
3521 // 0 = varying sizes min...max, 1 = set of constant sizes
3522 size_t constantSizesCount = 1;
3523 if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount;
3524 for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex)
3525 {
3526 std::string desc4 = desc3;
3527 switch(constantSizesIndex)
3528 {
3529 case 0: desc4 += " Varying_sizes"; break;
3530 case 1: desc4 += " Constant_sizes"; break;
3531 default: assert(0);
3532 }
3533
3534 config.AllocationSizes.clear();
3535 // Buffers present
3536 if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
3537 {
3538 // Small
3539 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
3540 {
3541 // Varying size
3542 if(constantSizesIndex == 0)
3543 config.AllocationSizes.push_back({4, 16, 1024});
3544 // Constant sizes
3545 else
3546 {
3547 config.AllocationSizes.push_back({1, 16, 16});
3548 config.AllocationSizes.push_back({1, 64, 64});
3549 config.AllocationSizes.push_back({1, 256, 256});
3550 config.AllocationSizes.push_back({1, 1024, 1024});
3551 }
3552 }
3553 // Large
3554 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
3555 {
3556 // Varying size
3557 if(constantSizesIndex == 0)
3558 config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB
3559 // Constant sizes
3560 else
3561 {
3562 config.AllocationSizes.push_back({1, 0x10000, 0x10000});
3563 config.AllocationSizes.push_back({1, 0x80000, 0x80000});
3564 config.AllocationSizes.push_back({1, 0x200000, 0x200000});
3565 config.AllocationSizes.push_back({1, 0xA00000, 0xA00000});
3566 }
3567 }
3568 }
3569 // Images present
3570 if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
3571 {
3572 // Small
3573 if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
3574 {
3575 // Varying size
3576 if(constantSizesIndex == 0)
3577 config.AllocationSizes.push_back({4, 0, 0, 4, 32});
3578 // Constant sizes
3579 else
3580 {
3581 config.AllocationSizes.push_back({1, 0, 0, 4, 4});
3582 config.AllocationSizes.push_back({1, 0, 0, 8, 8});
3583 config.AllocationSizes.push_back({1, 0, 0, 16, 16});
3584 config.AllocationSizes.push_back({1, 0, 0, 32, 32});
3585 }
3586 }
3587 // Large
3588 if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
3589 {
3590 // Varying size
3591 if(constantSizesIndex == 0)
3592 config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
3593 // Constant sizes
3594 else
3595 {
3596 config.AllocationSizes.push_back({1, 0, 0, 256, 256});
3597 config.AllocationSizes.push_back({1, 0, 0, 512, 512});
3598 config.AllocationSizes.push_back({1, 0, 0, 1024, 1024});
3599 config.AllocationSizes.push_back({1, 0, 0, 2048, 2048});
3600 }
3601 }
3602 }
3603
3604 const VkDeviceSize avgResourceSize = config.CalcAvgResourceSize();
3605 config.PoolSize = avgResourceSize * AVG_RESOURCES_PER_POOL;
3606
3607 // 0 = 66%, 1 = 133%, 2 = 100%, 3 = 33%, 4 = 166%
3608 size_t subscriptionModeCount;
3609 switch(ConfigType)
3610 {
3611 case CONFIG_TYPE_MINIMUM: subscriptionModeCount = 2; break;
3612 case CONFIG_TYPE_SMALL: subscriptionModeCount = 2; break;
3613 case CONFIG_TYPE_AVERAGE: subscriptionModeCount = 3; break;
3614 case CONFIG_TYPE_LARGE: subscriptionModeCount = 5; break;
3615 case CONFIG_TYPE_MAXIMUM: subscriptionModeCount = 5; break;
3616 default: assert(0);
3617 }
3618 for(size_t subscriptionModeIndex = 0; subscriptionModeIndex < subscriptionModeCount; ++subscriptionModeIndex)
3619 {
3620 std::string desc5 = desc4;
3621
3622 switch(subscriptionModeIndex)
3623 {
3624 case 0:
3625 desc5 += " Subscription_66%";
3626 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 66 / 100;
3627 break;
3628 case 1:
3629 desc5 += " Subscription_133%";
3630 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 133 / 100;
3631 break;
3632 case 2:
3633 desc5 += " Subscription_100%";
3634 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL;
3635 break;
3636 case 3:
3637 desc5 += " Subscription_33%";
3638 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 33 / 100;
3639 break;
3640 case 4:
3641 desc5 += " Subscription_166%";
3642 config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 166 / 100;
3643 break;
3644 default:
3645 assert(0);
3646 }
3647
3648 config.TotalItemCount = config.UsedItemCountMax * 5;
3649 config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100;
3650
3651 const char* testDescription = desc5.c_str();
3652
3653 for(size_t repeat = 0; repeat < repeatCount; ++repeat)
3654 {
3655 printf("%s Repeat %u\n", testDescription, (uint32_t)repeat);
3656
3657 PoolTestResult result{};
3658 g_MemoryAliasingWarningEnabled = false;
3659 TestPool_Benchmark(result, config);
3660 g_MemoryAliasingWarningEnabled = true;
3661 WritePoolTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
3662 }
3663 }
3664 }
3665 }
3666 }
3667 }
3668}
3669
3670void Test()
3671{
3672 wprintf(L"TESTING:\n");
3673
3674 // # Simple tests
3675
3676 TestBasics();
Adam Sawicki212a4a62018-06-14 15:44:45 +02003677#if VMA_DEBUG_MARGIN
3678 TestDebugMargin();
3679#else
3680 TestPool_SameSize();
3681 TestHeapSizeLimit();
3682#endif
Adam Sawickie44c6262018-06-15 14:30:39 +02003683#if VMA_DEBUG_INITIALIZE_ALLOCATIONS
3684 TestAllocationsInitialization();
3685#endif
Adam Sawickib8333fb2018-03-13 16:15:53 +01003686 TestMapping();
3687 TestMappingMultithreaded();
Adam Sawicki0876c0d2018-06-20 15:18:11 +02003688 TestLinearAllocator();
Adam Sawicki8cfe05f2018-08-22 16:48:17 +02003689 ManuallyTestLinearAllocator();
Adam Sawickib8333fb2018-03-13 16:15:53 +01003690 TestDefragmentationSimple();
3691 TestDefragmentationFull();
3692
3693 // # Detailed tests
3694 FILE* file;
3695 fopen_s(&file, "Results.csv", "w");
3696 assert(file != NULL);
3697
3698 WriteMainTestResultHeader(file);
3699 PerformMainTests(file);
3700 //PerformCustomMainTest(file);
3701
3702 WritePoolTestResultHeader(file);
3703 PerformPoolTests(file);
3704 //PerformCustomPoolTest(file);
3705
3706 fclose(file);
3707
3708 wprintf(L"Done.\n");
3709}
3710
Adam Sawickif1a793c2018-03-13 15:42:22 +01003711#endif // #ifdef _WIN32