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