From b8333fb9250dd0b2222d7e266d6f348b794921e6 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Tue, 13 Mar 2018 16:15:53 +0100 Subject: [PATCH] Publishing testing environment. Tests are available under [T] key. --- bin/VulkanSample_Release_2015.exe | Bin 102400 -> 0 bytes bin/VulkanSample_Release_vs2015.exe | Bin 0 -> 178176 bytes src/Common.cpp | 153 ++ src/Common.h | 80 +- src/Tests.cpp | 2960 +++++++++++++++++++++++++++ src/Tests.h | 1 + src/VulkanSample.cpp | 54 +- 7 files changed, 3240 insertions(+), 8 deletions(-) delete mode 100644 bin/VulkanSample_Release_2015.exe create mode 100644 bin/VulkanSample_Release_vs2015.exe diff --git a/bin/VulkanSample_Release_2015.exe b/bin/VulkanSample_Release_2015.exe deleted file mode 100644 index 381625f6c29f1d42ee615947295fc9fc7ada8748..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102400 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 Tb`0*AymQG;7M=Y42V3CZXH}ki diff --git a/bin/VulkanSample_Release_vs2015.exe b/bin/VulkanSample_Release_vs2015.exe new file mode 100644 index 0000000000000000000000000000000000000000..c60440d814d72d2f307927851728352c0a162423 GIT binary patch literal 178176 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%0ASoq{S?fCcG9+6#= z8Gps^?BNlv!#_?~cpLr{;X~jpciX&vTO+bTvg%#5%dOZ805{_62uHy?-_7_TdVc7p#x;Ashj}i@%ky>}>pE znClv0*~pOnisPb#p6v_2jSJbj_&q-I#sUXF^JR1(Ec_`xL0Gmae*Z1(yAU?trtco3 zTx>yv!J~N19luQo*@`GU!B~e!xE9|}I08P3KS+2KeCzk@+&KhZ`fv0L^e8y}2ZXO@ zoPa$~*t(~{d+{o31nm2v8yA2D!dCKZA3aS2H)}v zOFsl=o?_m?GXN%@w&4`m{2w+P2RHuGhNED^xD7|ZivNiYk%=FZOfZF)%zp4gZukLk_MZagBs|sNE%>tA3d{lUSNPS0p8=OVYh{MP{dmc_ z72Nb3_M_n00{$4EAbb)GngWMMxWWxLfOojz1K{7{rRTk1G{E-{$a^dJ*Lcw%1bkDnT0qnp_PvIp+$V1K=@T2(85tbc^U*g3hTMyUZ z6+Qrd1+P9j4xYjvhhG?+YQw^B1(_?*WtZfi@zVKma7hVm0A2QWK8}Bi@CaC0O8(oK zYv6kP7<7F@N#9zkxWVGpWsDcX`nHh1D3uRwKVWI4YC@hq%5QT+%@CVRG-`e}U=+tEdET3t^72xaKa2))o8y*J# zuYNl?P%V-Wc8wueyx~j>Ed0zwTt#AdlxlcR$j7(UpyuEw07+eA)vc=?IKw>Rt>D{w*@A5??4I;~kYQ8}`& zS?0AHk^Ih>EFLN_?}mQrBjX=2gTcXy!Ro=#;NrpX;OfE1;FiJY;I6^=V8>u;uz&E- z;Nij3gXUQ9SjDmGW1(Ysb6)$1yV$9I6& out, const char* fileName) +{ + std::ifstream file(fileName, std::ios::ate | std::ios::binary); + assert(file.is_open()); + size_t fileSize = (size_t)file.tellg(); + if(fileSize > 0) + { + out.resize(fileSize); + file.seekg(0); + file.read(out.data(), fileSize); + } + else + out.clear(); +} + +void SetConsoleColor(CONSOLE_COLOR color) +{ + WORD attr = 0; + switch(color) + { + case CONSOLE_COLOR::INFO: + attr = FOREGROUND_INTENSITY;; + break; + case CONSOLE_COLOR::NORMAL: + attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + break; + case CONSOLE_COLOR::WARNING: + attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; + break; + case CONSOLE_COLOR::ERROR_: + attr = FOREGROUND_RED | FOREGROUND_INTENSITY; + break; + default: + assert(0); + } + + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(out, attr); +} + +void PrintMessage(CONSOLE_COLOR color, const char* msg) +{ + if(color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(color); + + printf("%s\n", msg); + + if (color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(CONSOLE_COLOR::NORMAL); +} + +void PrintMessage(CONSOLE_COLOR color, const wchar_t* msg) +{ + if(color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(color); + + wprintf(L"%s\n", msg); + + if (color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(CONSOLE_COLOR::NORMAL); +} + +static const size_t CONSOLE_SMALL_BUF_SIZE = 256; + +void PrintMessageV(CONSOLE_COLOR color, const char* format, va_list argList) +{ + size_t dstLen = (size_t)::_vscprintf(format, argList); + if(dstLen) + { + bool useSmallBuf = dstLen < CONSOLE_SMALL_BUF_SIZE; + char smallBuf[CONSOLE_SMALL_BUF_SIZE]; + std::vector bigBuf(useSmallBuf ? 0 : dstLen + 1); + char* bufPtr = useSmallBuf ? smallBuf : bigBuf.data(); + ::vsprintf_s(bufPtr, dstLen + 1, format, argList); + PrintMessage(color, bufPtr); + } +} + +void PrintMessageV(CONSOLE_COLOR color, const wchar_t* format, va_list argList) +{ + size_t dstLen = (size_t)::_vcwprintf(format, argList); + if(dstLen) + { + bool useSmallBuf = dstLen < CONSOLE_SMALL_BUF_SIZE; + wchar_t smallBuf[CONSOLE_SMALL_BUF_SIZE]; + std::vector bigBuf(useSmallBuf ? 0 : dstLen + 1); + wchar_t* bufPtr = useSmallBuf ? smallBuf : bigBuf.data(); + ::vswprintf_s(bufPtr, dstLen + 1, format, argList); + PrintMessage(color, bufPtr); + } +} + +void PrintMessageF(CONSOLE_COLOR color, const char* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(color, format, argList); + va_end(argList); +} + +void PrintMessageF(CONSOLE_COLOR color, const wchar_t* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(color, format, argList); + va_end(argList); +} + +void PrintWarningF(const char* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} + +void PrintWarningF(const wchar_t* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} + +void PrintErrorF(const char* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} + +void PrintErrorF(const wchar_t* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} + +void SaveFile(const wchar_t* filePath, const void* data, size_t dataSize) +{ + FILE* f = nullptr; + _wfopen_s(&f, filePath, L"wb"); + if(f) + { + fwrite(data, 1, dataSize, f); + fclose(f); + } + else + assert(0); +} + #endif // #ifdef _WIN32 diff --git a/src/Common.h b/src/Common.h index 95a0929..aa8e504 100644 --- a/src/Common.h +++ b/src/Common.h @@ -1,29 +1,105 @@ #ifndef COMMON_H_ #define COMMON_H_ +#include "VmaUsage.h" + #ifdef _WIN32 #define MATHFU_COMPILE_WITHOUT_SIMD_SUPPORT #include #include +#include #include #include -#include #include #include #include #include #include #include +#include +#include -#include #include #include #include +#include + +typedef std::chrono::high_resolution_clock::time_point time_point; +typedef std::chrono::high_resolution_clock::duration duration; #define ERR_GUARD_VULKAN(Expr) do { VkResult res__ = (Expr); if (res__ < 0) assert(0); } while(0) +extern VkPhysicalDevice g_hPhysicalDevice; +extern VkDevice g_hDevice; +extern VmaAllocator g_hAllocator; +extern bool g_MemoryAliasingWarningEnabled; + +inline float ToFloatSeconds(duration d) +{ + return std::chrono::duration_cast>(d).count(); +} + +template +inline T ceil_div(T x, T y) +{ + return (x+y-1) / y; +} + +template +static inline T align_up(T val, T align) +{ + return (val + align - 1) / align * align; +} + +class RandomNumberGenerator +{ +public: + RandomNumberGenerator() : m_Value{GetTickCount()} {} + RandomNumberGenerator(uint32_t seed) : m_Value{seed} { } + void Seed(uint32_t seed) { m_Value = seed; } + uint32_t Generate() { return GenerateFast() ^ (GenerateFast() >> 7); } + +private: + uint32_t m_Value; + uint32_t GenerateFast() { return m_Value = (m_Value * 196314165 + 907633515); } +}; + +void ReadFile(std::vector& out, const char* fileName); + +enum class CONSOLE_COLOR +{ + INFO, + NORMAL, + WARNING, + ERROR_, + COUNT +}; + +void SetConsoleColor(CONSOLE_COLOR color); + +void PrintMessage(CONSOLE_COLOR color, const char* msg); +void PrintMessage(CONSOLE_COLOR color, const wchar_t* msg); + +inline void Print(const char* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); } +inline void Print(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); } +inline void PrintWarning(const char* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); } +inline void PrintWarning(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); } +inline void PrintError(const char* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); } +inline void PrintError(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); } + +void PrintMessageV(CONSOLE_COLOR color, const char* format, va_list argList); +void PrintMessageV(CONSOLE_COLOR color, const wchar_t* format, va_list argList); +void PrintMessageF(CONSOLE_COLOR color, const char* format, ...); +void PrintMessageF(CONSOLE_COLOR color, const wchar_t* format, ...); +void PrintWarningF(const char* format, ...); +void PrintWarningF(const wchar_t* format, ...); +void PrintErrorF(const char* format, ...); +void PrintErrorF(const wchar_t* format, ...); + +void SaveFile(const wchar_t* filePath, const void* data, size_t dataSize); + #endif // #ifdef _WIN32 #endif diff --git a/src/Tests.cpp b/src/Tests.cpp index acb73c0..46b456e 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -1,7 +1,2967 @@ #include "Tests.h" #include "VmaUsage.h" #include "Common.h" +#include +#include +#include #ifdef _WIN32 +enum class FREE_ORDER { FORWARD, BACKWARD, RANDOM, COUNT }; + +struct AllocationSize +{ + uint32_t Probability; + VkDeviceSize BufferSizeMin, BufferSizeMax; + uint32_t ImageSizeMin, ImageSizeMax; +}; + +struct Config +{ + uint32_t RandSeed; + VkDeviceSize BeginBytesToAllocate; + uint32_t AdditionalOperationCount; + VkDeviceSize MaxBytesToAllocate; + uint32_t MemUsageProbability[4]; // For VMA_MEMORY_USAGE_* + std::vector AllocationSizes; + uint32_t ThreadCount; + uint32_t ThreadsUsingCommonAllocationsProbabilityPercent; + FREE_ORDER FreeOrder; +}; + +struct Result +{ + duration TotalTime; + duration AllocationTimeMin, AllocationTimeAvg, AllocationTimeMax; + duration DeallocationTimeMin, DeallocationTimeAvg, DeallocationTimeMax; + VkDeviceSize TotalMemoryAllocated; + VkDeviceSize FreeRangeSizeAvg, FreeRangeSizeMax; +}; + +void TestDefragmentationSimple(); +void TestDefragmentationFull(); + +struct PoolTestConfig +{ + uint32_t RandSeed; + uint32_t ThreadCount; + VkDeviceSize PoolSize; + uint32_t FrameCount; + uint32_t TotalItemCount; + // Range for number of items used in each frame. + uint32_t UsedItemCountMin, UsedItemCountMax; + // Percent of items to make unused, and possibly make some others used in each frame. + uint32_t ItemsToMakeUnusedPercent; + std::vector AllocationSizes; + + VkDeviceSize CalcAvgResourceSize() const + { + uint32_t probabilitySum = 0; + VkDeviceSize sizeSum = 0; + for(size_t i = 0; i < AllocationSizes.size(); ++i) + { + const AllocationSize& allocSize = AllocationSizes[i]; + if(allocSize.BufferSizeMax > 0) + sizeSum += (allocSize.BufferSizeMin + allocSize.BufferSizeMax) / 2 * allocSize.Probability; + else + { + const VkDeviceSize avgDimension = (allocSize.ImageSizeMin + allocSize.ImageSizeMax) / 2; + sizeSum += avgDimension * avgDimension * 4 * allocSize.Probability; + } + probabilitySum += allocSize.Probability; + } + return sizeSum / probabilitySum; + } + + bool UsesBuffers() const + { + for(size_t i = 0; i < AllocationSizes.size(); ++i) + if(AllocationSizes[i].BufferSizeMax > 0) + return true; + return false; + } + + bool UsesImages() const + { + for(size_t i = 0; i < AllocationSizes.size(); ++i) + if(AllocationSizes[i].ImageSizeMax > 0) + return true; + return false; + } +}; + +struct PoolTestResult +{ + duration TotalTime; + duration AllocationTimeMin, AllocationTimeAvg, AllocationTimeMax; + duration DeallocationTimeMin, DeallocationTimeAvg, DeallocationTimeMax; + size_t LostAllocationCount, LostAllocationTotalSize; + size_t FailedAllocationCount, FailedAllocationTotalSize; +}; + +static const uint32_t IMAGE_BYTES_PER_PIXEL = 1; + +struct BufferInfo +{ + VkBuffer Buffer = VK_NULL_HANDLE; + VmaAllocation Allocation = VK_NULL_HANDLE; +}; + +static void InitResult(Result& outResult) +{ + outResult.TotalTime = duration::zero(); + outResult.AllocationTimeMin = duration::max(); + outResult.AllocationTimeAvg = duration::zero(); + outResult.AllocationTimeMax = duration::min(); + outResult.DeallocationTimeMin = duration::max(); + outResult.DeallocationTimeAvg = duration::zero(); + outResult.DeallocationTimeMax = duration::min(); + outResult.TotalMemoryAllocated = 0; + outResult.FreeRangeSizeAvg = 0; + outResult.FreeRangeSizeMax = 0; +} + +class TimeRegisterObj +{ +public: + TimeRegisterObj(duration& min, duration& sum, duration& max) : + m_Min(min), + m_Sum(sum), + m_Max(max), + m_TimeBeg(std::chrono::high_resolution_clock::now()) + { + } + + ~TimeRegisterObj() + { + duration d = std::chrono::high_resolution_clock::now() - m_TimeBeg; + m_Sum += d; + if(d < m_Min) m_Min = d; + if(d > m_Max) m_Max = d; + } + +private: + duration& m_Min; + duration& m_Sum; + duration& m_Max; + time_point m_TimeBeg; +}; + +struct PoolTestThreadResult +{ + duration AllocationTimeMin, AllocationTimeSum, AllocationTimeMax; + duration DeallocationTimeMin, DeallocationTimeSum, DeallocationTimeMax; + size_t AllocationCount, DeallocationCount; + size_t LostAllocationCount, LostAllocationTotalSize; + size_t FailedAllocationCount, FailedAllocationTotalSize; +}; + +class AllocationTimeRegisterObj : public TimeRegisterObj +{ +public: + AllocationTimeRegisterObj(Result& result) : + TimeRegisterObj(result.AllocationTimeMin, result.AllocationTimeAvg, result.AllocationTimeMax) + { + } +}; + +class DeallocationTimeRegisterObj : public TimeRegisterObj +{ +public: + DeallocationTimeRegisterObj(Result& result) : + TimeRegisterObj(result.DeallocationTimeMin, result.DeallocationTimeAvg, result.DeallocationTimeMax) + { + } +}; + +class PoolAllocationTimeRegisterObj : public TimeRegisterObj +{ +public: + PoolAllocationTimeRegisterObj(PoolTestThreadResult& result) : + TimeRegisterObj(result.AllocationTimeMin, result.AllocationTimeSum, result.AllocationTimeMax) + { + } +}; + +class PoolDeallocationTimeRegisterObj : public TimeRegisterObj +{ +public: + PoolDeallocationTimeRegisterObj(PoolTestThreadResult& result) : + TimeRegisterObj(result.DeallocationTimeMin, result.DeallocationTimeSum, result.DeallocationTimeMax) + { + } +}; + +VkResult MainTest(Result& outResult, const Config& config) +{ + assert(config.ThreadCount > 0); + + InitResult(outResult); + + RandomNumberGenerator mainRand{config.RandSeed}; + + time_point timeBeg = std::chrono::high_resolution_clock::now(); + + std::atomic allocationCount = 0; + VkResult res = VK_SUCCESS; + + uint32_t memUsageProbabilitySum = + config.MemUsageProbability[0] + config.MemUsageProbability[1] + + config.MemUsageProbability[2] + config.MemUsageProbability[3]; + assert(memUsageProbabilitySum > 0); + + uint32_t allocationSizeProbabilitySum = std::accumulate( + config.AllocationSizes.begin(), + config.AllocationSizes.end(), + 0u, + [](uint32_t sum, const AllocationSize& allocSize) { + return sum + allocSize.Probability; + }); + + struct Allocation + { + VkBuffer Buffer; + VkImage Image; + VmaAllocation Alloc; + }; + + std::vector commonAllocations; + std::mutex commonAllocationsMutex; + + auto Allocate = [&]( + VkDeviceSize bufferSize, + const VkExtent2D imageExtent, + RandomNumberGenerator& localRand, + VkDeviceSize& totalAllocatedBytes, + std::vector& allocations) -> VkResult + { + assert((bufferSize == 0) != (imageExtent.width == 0 && imageExtent.height == 0)); + + uint32_t memUsageIndex = 0; + uint32_t memUsageRand = localRand.Generate() % memUsageProbabilitySum; + while(memUsageRand >= config.MemUsageProbability[memUsageIndex]) + memUsageRand -= config.MemUsageProbability[memUsageIndex++]; + + VmaAllocationCreateInfo memReq = {}; + memReq.usage = (VmaMemoryUsage)(VMA_MEMORY_USAGE_GPU_ONLY + memUsageIndex); + + Allocation allocation = {}; + VmaAllocationInfo allocationInfo; + + // Buffer + if(bufferSize > 0) + { + assert(imageExtent.width == 0); + VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferInfo.size = bufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + { + AllocationTimeRegisterObj timeRegisterObj{outResult}; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &memReq, &allocation.Buffer, &allocation.Alloc, &allocationInfo); + } + } + // Image + else + { + VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = imageExtent.width; + imageInfo.extent.height = imageExtent.height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageInfo.tiling = memReq.usage == VMA_MEMORY_USAGE_GPU_ONLY ? + VK_IMAGE_TILING_OPTIMAL : + VK_IMAGE_TILING_LINEAR; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + switch(memReq.usage) + { + case VMA_MEMORY_USAGE_GPU_ONLY: + switch(localRand.Generate() % 3) + { + case 0: + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + break; + case 1: + imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + break; + case 2: + imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + break; + } + break; + case VMA_MEMORY_USAGE_CPU_ONLY: + case VMA_MEMORY_USAGE_CPU_TO_GPU: + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + break; + case VMA_MEMORY_USAGE_GPU_TO_CPU: + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; + break; + } + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.flags = 0; + + { + AllocationTimeRegisterObj timeRegisterObj{outResult}; + res = vmaCreateImage(g_hAllocator, &imageInfo, &memReq, &allocation.Image, &allocation.Alloc, &allocationInfo); + } + } + + if(res == VK_SUCCESS) + { + ++allocationCount; + totalAllocatedBytes += allocationInfo.size; + bool useCommonAllocations = localRand.Generate() % 100 < config.ThreadsUsingCommonAllocationsProbabilityPercent; + if(useCommonAllocations) + { + std::unique_lock lock(commonAllocationsMutex); + commonAllocations.push_back(allocation); + } + else + allocations.push_back(allocation); + } + else + { + assert(0); + } + return res; + }; + + auto GetNextAllocationSize = [&]( + VkDeviceSize& outBufSize, + VkExtent2D& outImageSize, + RandomNumberGenerator& localRand) + { + outBufSize = 0; + outImageSize = {0, 0}; + + uint32_t allocSizeIndex = 0; + uint32_t r = localRand.Generate() % allocationSizeProbabilitySum; + while(r >= config.AllocationSizes[allocSizeIndex].Probability) + r -= config.AllocationSizes[allocSizeIndex++].Probability; + + const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex]; + if(allocSize.BufferSizeMax > 0) + { + assert(allocSize.ImageSizeMax == 0); + if(allocSize.BufferSizeMax == allocSize.BufferSizeMin) + outBufSize = allocSize.BufferSizeMin; + else + { + outBufSize = allocSize.BufferSizeMin + localRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin); + outBufSize = outBufSize / 16 * 16; + } + } + else + { + if(allocSize.ImageSizeMax == allocSize.ImageSizeMin) + outImageSize.width = outImageSize.height = allocSize.ImageSizeMax; + else + { + outImageSize.width = allocSize.ImageSizeMin + localRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); + outImageSize.height = allocSize.ImageSizeMin + localRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); + } + } + }; + + std::atomic numThreadsReachedMaxAllocations = 0; + HANDLE threadsFinishEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + auto ThreadProc = [&](uint32_t randSeed) -> void + { + RandomNumberGenerator threadRand(randSeed); + VkDeviceSize threadTotalAllocatedBytes = 0; + std::vector threadAllocations; + VkDeviceSize threadBeginBytesToAllocate = config.BeginBytesToAllocate / config.ThreadCount; + VkDeviceSize threadMaxBytesToAllocate = config.MaxBytesToAllocate / config.ThreadCount; + uint32_t threadAdditionalOperationCount = config.AdditionalOperationCount / config.ThreadCount; + + // BEGIN ALLOCATIONS + for(;;) + { + VkDeviceSize bufferSize = 0; + VkExtent2D imageExtent = {}; + GetNextAllocationSize(bufferSize, imageExtent, threadRand); + if(threadTotalAllocatedBytes + bufferSize + imageExtent.width * imageExtent.height * IMAGE_BYTES_PER_PIXEL < + threadBeginBytesToAllocate) + { + if(Allocate(bufferSize, imageExtent, threadRand, threadTotalAllocatedBytes, threadAllocations) != VK_SUCCESS) + break; + } + else + break; + } + + // ADDITIONAL ALLOCATIONS AND FREES + for(size_t i = 0; i < threadAdditionalOperationCount; ++i) + { + VkDeviceSize bufferSize = 0; + VkExtent2D imageExtent = {}; + GetNextAllocationSize(bufferSize, imageExtent, threadRand); + + // true = allocate, false = free + bool allocate = threadRand.Generate() % 2 != 0; + + if(allocate) + { + if(threadTotalAllocatedBytes + + bufferSize + + imageExtent.width * imageExtent.height * IMAGE_BYTES_PER_PIXEL < + threadMaxBytesToAllocate) + { + if(Allocate(bufferSize, imageExtent, threadRand, threadTotalAllocatedBytes, threadAllocations) != VK_SUCCESS) + break; + } + } + else + { + bool useCommonAllocations = threadRand.Generate() % 100 < config.ThreadsUsingCommonAllocationsProbabilityPercent; + if(useCommonAllocations) + { + std::unique_lock lock(commonAllocationsMutex); + if(!commonAllocations.empty()) + { + size_t indexToFree = threadRand.Generate() % commonAllocations.size(); + VmaAllocationInfo allocationInfo; + vmaGetAllocationInfo(g_hAllocator, commonAllocations[indexToFree].Alloc, &allocationInfo); + if(threadTotalAllocatedBytes >= allocationInfo.size) + { + DeallocationTimeRegisterObj timeRegisterObj{outResult}; + if(commonAllocations[indexToFree].Buffer != VK_NULL_HANDLE) + vmaDestroyBuffer(g_hAllocator, commonAllocations[indexToFree].Buffer, commonAllocations[indexToFree].Alloc); + else + vmaDestroyImage(g_hAllocator, commonAllocations[indexToFree].Image, commonAllocations[indexToFree].Alloc); + threadTotalAllocatedBytes -= allocationInfo.size; + commonAllocations.erase(commonAllocations.begin() + indexToFree); + } + } + } + else + { + if(!threadAllocations.empty()) + { + size_t indexToFree = threadRand.Generate() % threadAllocations.size(); + VmaAllocationInfo allocationInfo; + vmaGetAllocationInfo(g_hAllocator, threadAllocations[indexToFree].Alloc, &allocationInfo); + if(threadTotalAllocatedBytes >= allocationInfo.size) + { + DeallocationTimeRegisterObj timeRegisterObj{outResult}; + if(threadAllocations[indexToFree].Buffer != VK_NULL_HANDLE) + vmaDestroyBuffer(g_hAllocator, threadAllocations[indexToFree].Buffer, threadAllocations[indexToFree].Alloc); + else + vmaDestroyImage(g_hAllocator, threadAllocations[indexToFree].Image, threadAllocations[indexToFree].Alloc); + threadTotalAllocatedBytes -= allocationInfo.size; + threadAllocations.erase(threadAllocations.begin() + indexToFree); + } + } + } + } + } + + ++numThreadsReachedMaxAllocations; + + WaitForSingleObject(threadsFinishEvent, INFINITE); + + // DEALLOCATION + while(!threadAllocations.empty()) + { + size_t indexToFree = 0; + switch(config.FreeOrder) + { + case FREE_ORDER::FORWARD: + indexToFree = 0; + break; + case FREE_ORDER::BACKWARD: + indexToFree = threadAllocations.size() - 1; + break; + case FREE_ORDER::RANDOM: + indexToFree = mainRand.Generate() % threadAllocations.size(); + break; + } + + { + DeallocationTimeRegisterObj timeRegisterObj{outResult}; + if(threadAllocations[indexToFree].Buffer != VK_NULL_HANDLE) + vmaDestroyBuffer(g_hAllocator, threadAllocations[indexToFree].Buffer, threadAllocations[indexToFree].Alloc); + else + vmaDestroyImage(g_hAllocator, threadAllocations[indexToFree].Image, threadAllocations[indexToFree].Alloc); + } + threadAllocations.erase(threadAllocations.begin() + indexToFree); + } + }; + + uint32_t threadRandSeed = mainRand.Generate(); + std::vector bkgThreads; + for(size_t i = 0; i < config.ThreadCount; ++i) + { + bkgThreads.emplace_back(std::bind(ThreadProc, threadRandSeed + (uint32_t)i)); + } + + // Wait for threads reached max allocations + while(numThreadsReachedMaxAllocations < config.ThreadCount) + Sleep(0); + + // CALCULATE MEMORY STATISTICS ON FINAL USAGE + VmaStats vmaStats = {}; + vmaCalculateStats(g_hAllocator, &vmaStats); + outResult.TotalMemoryAllocated = vmaStats.total.usedBytes + vmaStats.total.unusedBytes; + outResult.FreeRangeSizeMax = vmaStats.total.unusedRangeSizeMax; + outResult.FreeRangeSizeAvg = vmaStats.total.unusedRangeSizeAvg; + + // Signal threads to deallocate + SetEvent(threadsFinishEvent); + + // Wait for threads finished + for(size_t i = 0; i < bkgThreads.size(); ++i) + bkgThreads[i].join(); + bkgThreads.clear(); + + CloseHandle(threadsFinishEvent); + + // Deallocate remaining common resources + while(!commonAllocations.empty()) + { + size_t indexToFree = 0; + switch(config.FreeOrder) + { + case FREE_ORDER::FORWARD: + indexToFree = 0; + break; + case FREE_ORDER::BACKWARD: + indexToFree = commonAllocations.size() - 1; + break; + case FREE_ORDER::RANDOM: + indexToFree = mainRand.Generate() % commonAllocations.size(); + break; + } + + { + DeallocationTimeRegisterObj timeRegisterObj{outResult}; + if(commonAllocations[indexToFree].Buffer != VK_NULL_HANDLE) + vmaDestroyBuffer(g_hAllocator, commonAllocations[indexToFree].Buffer, commonAllocations[indexToFree].Alloc); + else + vmaDestroyImage(g_hAllocator, commonAllocations[indexToFree].Image, commonAllocations[indexToFree].Alloc); + } + commonAllocations.erase(commonAllocations.begin() + indexToFree); + } + + if(allocationCount) + { + outResult.AllocationTimeAvg /= allocationCount; + outResult.DeallocationTimeAvg /= allocationCount; + } + + outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg; + + return res; +} + +static void SaveAllocatorStatsToFile(VmaAllocator alloc, const wchar_t* filePath) +{ + char* stats; + vmaBuildStatsString(alloc, &stats, VK_TRUE); + SaveFile(filePath, stats, strlen(stats)); + vmaFreeStatsString(alloc, stats); +} + +struct AllocInfo +{ + VmaAllocation m_Allocation; + VkBuffer m_Buffer; + VkImage m_Image; + uint32_t m_StartValue; + union + { + VkBufferCreateInfo m_BufferInfo; + VkImageCreateInfo m_ImageInfo; + }; +}; + +static void GetMemReq(VmaAllocationCreateInfo& outMemReq) +{ + outMemReq = {}; + outMemReq.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; + //outMemReq.flags = VMA_ALLOCATION_CREATE_PERSISTENT_MAP_BIT; +} + +static void CreateBuffer( + VmaPool pool, + const VkBufferCreateInfo& bufCreateInfo, + bool persistentlyMapped, + AllocInfo& outAllocInfo) +{ + outAllocInfo = {}; + outAllocInfo.m_BufferInfo = bufCreateInfo; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = pool; + if(persistentlyMapped) + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VmaAllocationInfo vmaAllocInfo = {}; + ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &outAllocInfo.m_Buffer, &outAllocInfo.m_Allocation, &vmaAllocInfo) ); + + // Setup StartValue and fill. + { + outAllocInfo.m_StartValue = (uint32_t)rand(); + uint32_t* data = (uint32_t*)vmaAllocInfo.pMappedData; + assert((data != nullptr) == persistentlyMapped); + if(!persistentlyMapped) + { + ERR_GUARD_VULKAN( vmaMapMemory(g_hAllocator, outAllocInfo.m_Allocation, (void**)&data) ); + } + + uint32_t value = outAllocInfo.m_StartValue; + assert(bufCreateInfo.size % 4 == 0); + for(size_t i = 0; i < bufCreateInfo.size / sizeof(uint32_t); ++i) + data[i] = value++; + + if(!persistentlyMapped) + vmaUnmapMemory(g_hAllocator, outAllocInfo.m_Allocation); + } +} + +static void CreateAllocation(AllocInfo& outAllocation, VmaAllocator allocator) +{ + outAllocation.m_Allocation = nullptr; + outAllocation.m_Buffer = nullptr; + outAllocation.m_Image = nullptr; + outAllocation.m_StartValue = (uint32_t)rand(); + + VmaAllocationCreateInfo vmaMemReq; + GetMemReq(vmaMemReq); + + VmaAllocationInfo allocInfo; + + const bool isBuffer = true;//(rand() & 0x1) != 0; + const bool isLarge = (rand() % 16) == 0; + if(isBuffer) + { + const uint32_t bufferSize = isLarge ? + (rand() % 10 + 1) * (1024 * 1024) : // 1 MB ... 10 MB + (rand() % 1024 + 1) * 1024; // 1 KB ... 1 MB + + VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferInfo.size = bufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VkResult res = vmaCreateBuffer(allocator, &bufferInfo, &vmaMemReq, &outAllocation.m_Buffer, &outAllocation.m_Allocation, &allocInfo); + outAllocation.m_BufferInfo = bufferInfo; + assert(res == VK_SUCCESS); + } + else + { + const uint32_t imageSizeX = isLarge ? + 1024 + rand() % (4096 - 1024) : // 1024 ... 4096 + rand() % 1024 + 1; // 1 ... 1024 + const uint32_t imageSizeY = isLarge ? + 1024 + rand() % (4096 - 1024) : // 1024 ... 4096 + rand() % 1024 + 1; // 1 ... 1024 + + VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageInfo.extent.width = imageSizeX; + imageInfo.extent.height = imageSizeY; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + + VkResult res = vmaCreateImage(allocator, &imageInfo, &vmaMemReq, &outAllocation.m_Image, &outAllocation.m_Allocation, &allocInfo); + outAllocation.m_ImageInfo = imageInfo; + assert(res == VK_SUCCESS); + } + + uint32_t* data = (uint32_t*)allocInfo.pMappedData; + if(allocInfo.pMappedData == nullptr) + { + VkResult res = vmaMapMemory(allocator, outAllocation.m_Allocation, (void**)&data); + assert(res == VK_SUCCESS); + } + + uint32_t value = outAllocation.m_StartValue; + assert(allocInfo.size % 4 == 0); + for(size_t i = 0; i < allocInfo.size / sizeof(uint32_t); ++i) + data[i] = value++; + + if(allocInfo.pMappedData == nullptr) + vmaUnmapMemory(allocator, outAllocation.m_Allocation); +} + +static void DestroyAllocation(const AllocInfo& allocation) +{ + if(allocation.m_Buffer) + vmaDestroyBuffer(g_hAllocator, allocation.m_Buffer, allocation.m_Allocation); + else + vmaDestroyImage(g_hAllocator, allocation.m_Image, allocation.m_Allocation); +} + +static void DestroyAllAllocations(std::vector& allocations) +{ + for(size_t i = allocations.size(); i--; ) + DestroyAllocation(allocations[i]); + allocations.clear(); +} + +static void ValidateAllocationData(const AllocInfo& allocation) +{ + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, allocation.m_Allocation, &allocInfo); + + uint32_t* data = (uint32_t*)allocInfo.pMappedData; + if(allocInfo.pMappedData == nullptr) + { + VkResult res = vmaMapMemory(g_hAllocator, allocation.m_Allocation, (void**)&data); + assert(res == VK_SUCCESS); + } + + uint32_t value = allocation.m_StartValue; + bool ok = true; + size_t i; + assert(allocInfo.size % 4 == 0); + for(i = 0; i < allocInfo.size / sizeof(uint32_t); ++i) + { + if(data[i] != value++) + { + ok = false; + break; + } + } + assert(ok); + + if(allocInfo.pMappedData == nullptr) + vmaUnmapMemory(g_hAllocator, allocation.m_Allocation); +} + +static void RecreateAllocationResource(AllocInfo& allocation) +{ + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, allocation.m_Allocation, &allocInfo); + + if(allocation.m_Buffer) + { + vkDestroyBuffer(g_hDevice, allocation.m_Buffer, nullptr); + + VkResult res = vkCreateBuffer(g_hDevice, &allocation.m_BufferInfo, nullptr, &allocation.m_Buffer); + assert(res == VK_SUCCESS); + + // Just to silence validation layer warnings. + VkMemoryRequirements vkMemReq; + vkGetBufferMemoryRequirements(g_hDevice, allocation.m_Buffer, &vkMemReq); + assert(vkMemReq.size == allocation.m_BufferInfo.size); + + res = vkBindBufferMemory(g_hDevice, allocation.m_Buffer, allocInfo.deviceMemory, allocInfo.offset); + assert(res == VK_SUCCESS); + } + else + { + vkDestroyImage(g_hDevice, allocation.m_Image, nullptr); + + VkResult res = vkCreateImage(g_hDevice, &allocation.m_ImageInfo, nullptr, &allocation.m_Image); + assert(res == VK_SUCCESS); + + // Just to silence validation layer warnings. + VkMemoryRequirements vkMemReq; + vkGetImageMemoryRequirements(g_hDevice, allocation.m_Image, &vkMemReq); + + res = vkBindImageMemory(g_hDevice, allocation.m_Image, allocInfo.deviceMemory, allocInfo.offset); + assert(res == VK_SUCCESS); + } +} + +static void Defragment(AllocInfo* allocs, size_t allocCount, + const VmaDefragmentationInfo* defragmentationInfo = nullptr, + VmaDefragmentationStats* defragmentationStats = nullptr) +{ + std::vector vmaAllocs(allocCount); + for(size_t i = 0; i < allocCount; ++i) + vmaAllocs[i] = allocs[i].m_Allocation; + + std::vector allocChanged(allocCount); + + ERR_GUARD_VULKAN( vmaDefragment(g_hAllocator, vmaAllocs.data(), allocCount, allocChanged.data(), + defragmentationInfo, defragmentationStats) ); + + for(size_t i = 0; i < allocCount; ++i) + { + if(allocChanged[i]) + { + RecreateAllocationResource(allocs[i]); + } + } +} + +static void ValidateAllocationsData(const AllocInfo* allocs, size_t allocCount) +{ + std::for_each(allocs, allocs + allocCount, [](const AllocInfo& allocInfo) { + ValidateAllocationData(allocInfo); + }); +} + +void TestDefragmentationSimple() +{ + wprintf(L"Test defragmentation simple\n"); + + RandomNumberGenerator rand(667); + + const VkDeviceSize BUF_SIZE = 0x10000; + const VkDeviceSize BLOCK_SIZE = BUF_SIZE * 8; + + const VkDeviceSize MIN_BUF_SIZE = 32; + const VkDeviceSize MAX_BUF_SIZE = BUF_SIZE * 4; + auto RandomBufSize = [&]() -> VkDeviceSize { + return align_up(rand.Generate() % (MAX_BUF_SIZE - MIN_BUF_SIZE + 1) + MIN_BUF_SIZE, 32); + }; + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = BUF_SIZE; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo exampleAllocCreateInfo = {}; + exampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + + uint32_t memTypeIndex = UINT32_MAX; + vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &exampleAllocCreateInfo, &memTypeIndex); + + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.blockSize = BLOCK_SIZE; + poolCreateInfo.memoryTypeIndex = memTypeIndex; + + VmaPool pool; + ERR_GUARD_VULKAN( vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) ); + + std::vector allocations; + + // persistentlyMappedOption = 0 - not persistently mapped. + // persistentlyMappedOption = 1 - persistently mapped. + for(uint32_t persistentlyMappedOption = 0; persistentlyMappedOption < 2; ++persistentlyMappedOption) + { + wprintf(L" Persistently mapped option = %u\n", persistentlyMappedOption); + const bool persistentlyMapped = persistentlyMappedOption != 0; + + // # Test 1 + // Buffers of fixed size. + // Fill 2 blocks. Remove odd buffers. Defragment everything. + // Expected result: at least 1 block freed. + { + for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i) + { + AllocInfo allocInfo; + CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo); + allocations.push_back(allocInfo); + } + + for(size_t i = 1; i < allocations.size(); ++i) + { + DestroyAllocation(allocations[i]); + allocations.erase(allocations.begin() + i); + } + + VmaDefragmentationStats defragStats; + Defragment(allocations.data(), allocations.size(), nullptr, &defragStats); + assert(defragStats.allocationsMoved > 0 && defragStats.bytesMoved > 0); + assert(defragStats.deviceMemoryBlocksFreed >= 1); + + ValidateAllocationsData(allocations.data(), allocations.size()); + + DestroyAllAllocations(allocations); + } + + // # Test 2 + // Buffers of fixed size. + // Fill 2 blocks. Remove odd buffers. Defragment one buffer at time. + // Expected result: Each of 4 interations makes some progress. + { + for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i) + { + AllocInfo allocInfo; + CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo); + allocations.push_back(allocInfo); + } + + for(size_t i = 1; i < allocations.size(); ++i) + { + DestroyAllocation(allocations[i]); + allocations.erase(allocations.begin() + i); + } + + VmaDefragmentationInfo defragInfo = {}; + defragInfo.maxAllocationsToMove = 1; + defragInfo.maxBytesToMove = BUF_SIZE; + + for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE / 2; ++i) + { + VmaDefragmentationStats defragStats; + Defragment(allocations.data(), allocations.size(), &defragInfo, &defragStats); + assert(defragStats.allocationsMoved > 0 && defragStats.bytesMoved > 0); + } + + ValidateAllocationsData(allocations.data(), allocations.size()); + + DestroyAllAllocations(allocations); + } + + // # Test 3 + // Buffers of variable size. + // Create a number of buffers. Remove some percent of them. + // Defragment while having some percent of them unmovable. + // Expected result: Just simple validation. + { + for(size_t i = 0; i < 100; ++i) + { + VkBufferCreateInfo localBufCreateInfo = bufCreateInfo; + localBufCreateInfo.size = RandomBufSize(); + + AllocInfo allocInfo; + CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo); + allocations.push_back(allocInfo); + } + + const uint32_t percentToDelete = 60; + const size_t numberToDelete = allocations.size() * percentToDelete / 100; + for(size_t i = 0; i < numberToDelete; ++i) + { + size_t indexToDelete = rand.Generate() % (uint32_t)allocations.size(); + DestroyAllocation(allocations[indexToDelete]); + allocations.erase(allocations.begin() + indexToDelete); + } + + // Non-movable allocations will be at the beginning of allocations array. + const uint32_t percentNonMovable = 20; + const size_t numberNonMovable = allocations.size() * percentNonMovable / 100; + for(size_t i = 0; i < numberNonMovable; ++i) + { + size_t indexNonMovable = i + rand.Generate() % (uint32_t)(allocations.size() - i); + if(indexNonMovable != i) + std::swap(allocations[i], allocations[indexNonMovable]); + } + + VmaDefragmentationStats defragStats; + Defragment( + allocations.data() + numberNonMovable, + allocations.size() - numberNonMovable, + nullptr, &defragStats); + + ValidateAllocationsData(allocations.data(), allocations.size()); + + DestroyAllAllocations(allocations); + } + } + + vmaDestroyPool(g_hAllocator, pool); +} + +void TestDefragmentationFull() +{ + std::vector allocations; + + // Create initial allocations. + for(size_t i = 0; i < 400; ++i) + { + AllocInfo allocation; + CreateAllocation(allocation, g_hAllocator); + allocations.push_back(allocation); + } + + // Delete random allocations + const size_t allocationsToDeletePercent = 80; + size_t allocationsToDelete = allocations.size() * allocationsToDeletePercent / 100; + for(size_t i = 0; i < allocationsToDelete; ++i) + { + size_t index = (size_t)rand() % allocations.size(); + DestroyAllocation(allocations[index]); + allocations.erase(allocations.begin() + index); + } + + for(size_t i = 0; i < allocations.size(); ++i) + ValidateAllocationData(allocations[i]); + + SaveAllocatorStatsToFile(g_hAllocator, L"Before.csv"); + + { + std::vector vmaAllocations(allocations.size()); + for(size_t i = 0; i < allocations.size(); ++i) + vmaAllocations[i] = allocations[i].m_Allocation; + + const size_t nonMovablePercent = 0; + size_t nonMovableCount = vmaAllocations.size() * nonMovablePercent / 100; + for(size_t i = 0; i < nonMovableCount; ++i) + { + size_t index = (size_t)rand() % vmaAllocations.size(); + vmaAllocations.erase(vmaAllocations.begin() + index); + } + + const uint32_t defragCount = 1; + for(uint32_t defragIndex = 0; defragIndex < defragCount; ++defragIndex) + { + std::vector allocationsChanged(vmaAllocations.size()); + + VmaDefragmentationInfo defragmentationInfo; + defragmentationInfo.maxAllocationsToMove = UINT_MAX; + defragmentationInfo.maxBytesToMove = SIZE_MAX; + + wprintf(L"Defragmentation #%u\n", defragIndex); + + time_point begTime = std::chrono::high_resolution_clock::now(); + + VmaDefragmentationStats stats; + VkResult res = vmaDefragment(g_hAllocator, vmaAllocations.data(), vmaAllocations.size(), allocationsChanged.data(), &defragmentationInfo, &stats); + assert(res >= 0); + + float defragmentDuration = ToFloatSeconds(std::chrono::high_resolution_clock::now() - begTime); + + wprintf(L"Moved allocations %u, bytes %llu\n", stats.allocationsMoved, stats.bytesMoved); + wprintf(L"Freed blocks %u, bytes %llu\n", stats.deviceMemoryBlocksFreed, stats.bytesFreed); + wprintf(L"Time: %.2f s\n", defragmentDuration); + + for(size_t i = 0; i < vmaAllocations.size(); ++i) + { + if(allocationsChanged[i]) + { + RecreateAllocationResource(allocations[i]); + } + } + + for(size_t i = 0; i < allocations.size(); ++i) + ValidateAllocationData(allocations[i]); + + wchar_t fileName[MAX_PATH]; + swprintf(fileName, MAX_PATH, L"After_%02u.csv", defragIndex); + SaveAllocatorStatsToFile(g_hAllocator, fileName); + } + } + + // Destroy all remaining allocations. + DestroyAllAllocations(allocations); +} + +static void TestUserData() +{ + VkResult res; + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + bufCreateInfo.size = 0x10000; + + for(uint32_t testIndex = 0; testIndex < 2; ++testIndex) + { + // Opaque pointer + { + + void* numberAsPointer = (void*)(size_t)0xC2501FF3u; + void* pointerToSomething = &res; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.pUserData = numberAsPointer; + if(testIndex == 1) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + assert(res == VK_SUCCESS); + assert(allocInfo.pUserData = numberAsPointer); + + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + assert(allocInfo.pUserData == numberAsPointer); + + vmaSetAllocationUserData(g_hAllocator, alloc, pointerToSomething); + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + assert(allocInfo.pUserData == pointerToSomething); + + vmaDestroyBuffer(g_hAllocator, buf, alloc); + } + + // String + { + const char* name1 = "Buffer name \\\"\'<>&% \nSecond line .,;="; + const char* name2 = "2"; + const size_t name1Len = strlen(name1); + + char* name1Buf = new char[name1Len + 1]; + strcpy_s(name1Buf, name1Len + 1, name1); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT; + allocCreateInfo.pUserData = name1Buf; + if(testIndex == 1) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + assert(res == VK_SUCCESS); + assert(allocInfo.pUserData != nullptr && allocInfo.pUserData != name1Buf); + assert(strcmp(name1, (const char*)allocInfo.pUserData) == 0); + + delete[] name1Buf; + + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + assert(strcmp(name1, (const char*)allocInfo.pUserData) == 0); + + vmaSetAllocationUserData(g_hAllocator, alloc, (void*)name2); + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + assert(strcmp(name2, (const char*)allocInfo.pUserData) == 0); + + vmaSetAllocationUserData(g_hAllocator, alloc, nullptr); + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + assert(allocInfo.pUserData == nullptr); + + vmaDestroyBuffer(g_hAllocator, buf, alloc); + } + } +} + +static void TestMemoryRequirements() +{ + VkResult res; + VkBuffer buf; + VmaAllocation alloc; + VmaAllocationInfo allocInfo; + + const VkPhysicalDeviceMemoryProperties* memProps; + vmaGetMemoryProperties(g_hAllocator, &memProps); + + VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufInfo.size = 128; + + VmaAllocationCreateInfo allocCreateInfo = {}; + + // No requirements. + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + assert(res == VK_SUCCESS); + vmaDestroyBuffer(g_hAllocator, buf, alloc); + + // Usage. + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.requiredFlags = 0; + allocCreateInfo.preferredFlags = 0; + allocCreateInfo.memoryTypeBits = UINT32_MAX; + + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + assert(res == VK_SUCCESS); + assert(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + vmaDestroyBuffer(g_hAllocator, buf, alloc); + + // Required flags, preferred flags. + allocCreateInfo.usage = VMA_MEMORY_USAGE_UNKNOWN; + allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + allocCreateInfo.memoryTypeBits = 0; + + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + assert(res == VK_SUCCESS); + assert(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + assert(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + vmaDestroyBuffer(g_hAllocator, buf, alloc); + + // memoryTypeBits. + const uint32_t memType = allocInfo.memoryType; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.requiredFlags = 0; + allocCreateInfo.preferredFlags = 0; + allocCreateInfo.memoryTypeBits = 1u << memType; + + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + assert(res == VK_SUCCESS); + assert(allocInfo.memoryType == memType); + vmaDestroyBuffer(g_hAllocator, buf, alloc); + +} + +static void TestBasics() +{ + VkResult res; + + TestMemoryRequirements(); + + // Lost allocation + { + VmaAllocation alloc = VK_NULL_HANDLE; + vmaCreateLostAllocation(g_hAllocator, &alloc); + assert(alloc != VK_NULL_HANDLE); + + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + assert(allocInfo.deviceMemory == VK_NULL_HANDLE); + assert(allocInfo.size == 0); + + vmaFreeMemory(g_hAllocator, alloc); + } + + // Allocation that is MAPPED and not necessarily HOST_VISIBLE. + { + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + bufCreateInfo.size = 128; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + assert(res == VK_SUCCESS); + + vmaDestroyBuffer(g_hAllocator, buf, alloc); + + // Same with OWN_MEMORY. + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + assert(res == VK_SUCCESS); + + vmaDestroyBuffer(g_hAllocator, buf, alloc); + } + + TestUserData(); +} + +void TestHeapSizeLimit() +{ + const VkDeviceSize HEAP_SIZE_LIMIT = 1ull * 1024 * 1024 * 1024; // 1 GB + const VkDeviceSize BLOCK_SIZE = 128ull * 1024 * 1024; // 128 MB + + VkDeviceSize heapSizeLimit[VK_MAX_MEMORY_HEAPS]; + for(uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i) + { + heapSizeLimit[i] = HEAP_SIZE_LIMIT; + } + + VmaAllocatorCreateInfo allocatorCreateInfo = {}; + allocatorCreateInfo.physicalDevice = g_hPhysicalDevice; + allocatorCreateInfo.device = g_hDevice; + allocatorCreateInfo.pHeapSizeLimit = heapSizeLimit; + + VmaAllocator hAllocator; + VkResult res = vmaCreateAllocator(&allocatorCreateInfo, &hAllocator); + assert(res == VK_SUCCESS); + + struct Item + { + VkBuffer hBuf; + VmaAllocation hAlloc; + }; + std::vector items; + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + // 1. Allocate two blocks of Own Memory, half the size of BLOCK_SIZE. + VmaAllocationInfo ownAllocInfo; + { + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + bufCreateInfo.size = BLOCK_SIZE / 2; + + for(size_t i = 0; i < 2; ++i) + { + Item item; + res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, &ownAllocInfo); + assert(res == VK_SUCCESS); + items.push_back(item); + } + } + + // Create pool to make sure allocations must be out of this memory type. + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.memoryTypeIndex = ownAllocInfo.memoryType; + poolCreateInfo.blockSize = BLOCK_SIZE; + + VmaPool hPool; + res = vmaCreatePool(hAllocator, &poolCreateInfo, &hPool); + assert(res == VK_SUCCESS); + + // 2. Allocate normal buffers from all the remaining memory. + { + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = hPool; + + bufCreateInfo.size = BLOCK_SIZE / 2; + + const size_t bufCount = ((HEAP_SIZE_LIMIT / BLOCK_SIZE) - 1) * 2; + for(size_t i = 0; i < bufCount; ++i) + { + Item item; + res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, nullptr); + assert(res == VK_SUCCESS); + items.push_back(item); + } + } + + // 3. Allocation of one more (even small) buffer should fail. + { + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = hPool; + + bufCreateInfo.size = 128; + + VkBuffer hBuf; + VmaAllocation hAlloc; + res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &hBuf, &hAlloc, nullptr); + assert(res == VK_ERROR_OUT_OF_DEVICE_MEMORY); + } + + // Destroy everything. + for(size_t i = items.size(); i--; ) + { + vmaDestroyBuffer(hAllocator, items[i].hBuf, items[i].hAlloc); + } + + vmaDestroyPool(hAllocator, hPool); + + vmaDestroyAllocator(hAllocator); +} + +static void TestPool_SameSize() +{ + const VkDeviceSize BUF_SIZE = 1024 * 1024; + const size_t BUF_COUNT = 100; + VkResult res; + + RandomNumberGenerator rand{123}; + + VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferInfo.size = BUF_SIZE; + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + uint32_t memoryTypeBits = UINT32_MAX; + { + VkBuffer dummyBuffer; + res = vkCreateBuffer(g_hDevice, &bufferInfo, nullptr, &dummyBuffer); + assert(res == VK_SUCCESS); + + VkMemoryRequirements memReq; + vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq); + memoryTypeBits = memReq.memoryTypeBits; + + vkDestroyBuffer(g_hDevice, dummyBuffer, nullptr); + } + + VmaAllocationCreateInfo poolAllocInfo = {}; + poolAllocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + uint32_t memTypeIndex; + res = vmaFindMemoryTypeIndex( + g_hAllocator, + memoryTypeBits, + &poolAllocInfo, + &memTypeIndex); + + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.memoryTypeIndex = memTypeIndex; + poolCreateInfo.blockSize = BUF_SIZE * BUF_COUNT / 4; + poolCreateInfo.minBlockCount = 1; + poolCreateInfo.maxBlockCount = 4; + poolCreateInfo.frameInUseCount = 0; + + VmaPool pool; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + assert(res == VK_SUCCESS); + + vmaSetCurrentFrameIndex(g_hAllocator, 1); + + VmaAllocationCreateInfo allocInfo = {}; + allocInfo.pool = pool; + allocInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT | + VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; + + struct BufItem + { + VkBuffer Buf; + VmaAllocation Alloc; + }; + std::vector items; + + // Fill entire pool. + for(size_t i = 0; i < BUF_COUNT; ++i) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + assert(res == VK_SUCCESS); + items.push_back(item); + } + + // Make sure that another allocation would fail. + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + assert(res == VK_ERROR_OUT_OF_DEVICE_MEMORY); + } + + // Validate that no buffer is lost. Also check that they are not mapped. + for(size_t i = 0; i < items.size(); ++i) + { + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); + assert(allocInfo.deviceMemory != VK_NULL_HANDLE); + assert(allocInfo.pMappedData == nullptr); + } + + // Free some percent of random items. + { + const size_t PERCENT_TO_FREE = 10; + size_t itemsToFree = items.size() * PERCENT_TO_FREE / 100; + for(size_t i = 0; i < itemsToFree; ++i) + { + size_t index = (size_t)rand.Generate() % items.size(); + vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc); + items.erase(items.begin() + index); + } + } + + // Randomly allocate and free items. + { + const size_t OPERATION_COUNT = BUF_COUNT; + for(size_t i = 0; i < OPERATION_COUNT; ++i) + { + bool allocate = rand.Generate() % 2 != 0; + if(allocate) + { + if(items.size() < BUF_COUNT) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + assert(res == VK_SUCCESS); + items.push_back(item); + } + } + else // Free + { + if(!items.empty()) + { + size_t index = (size_t)rand.Generate() % items.size(); + vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc); + items.erase(items.begin() + index); + } + } + } + } + + // Allocate up to maximum. + while(items.size() < BUF_COUNT) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + assert(res == VK_SUCCESS); + items.push_back(item); + } + + // Validate that no buffer is lost. + for(size_t i = 0; i < items.size(); ++i) + { + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); + assert(allocInfo.deviceMemory != VK_NULL_HANDLE); + } + + // Next frame. + vmaSetCurrentFrameIndex(g_hAllocator, 2); + + // Allocate another BUF_COUNT buffers. + for(size_t i = 0; i < BUF_COUNT; ++i) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + assert(res == VK_SUCCESS); + items.push_back(item); + } + + // Make sure the first BUF_COUNT is lost. Delete them. + for(size_t i = 0; i < BUF_COUNT; ++i) + { + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); + assert(allocInfo.deviceMemory == VK_NULL_HANDLE); + vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); + } + items.erase(items.begin(), items.begin() + BUF_COUNT); + + // Validate that no buffer is lost. + for(size_t i = 0; i < items.size(); ++i) + { + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); + assert(allocInfo.deviceMemory != VK_NULL_HANDLE); + } + + // Free one item. + vmaDestroyBuffer(g_hAllocator, items.back().Buf, items.back().Alloc); + items.pop_back(); + + // Validate statistics. + { + VmaPoolStats poolStats = {}; + vmaGetPoolStats(g_hAllocator, pool, &poolStats); + assert(poolStats.allocationCount == items.size()); + assert(poolStats.size = BUF_COUNT * BUF_SIZE); + assert(poolStats.unusedRangeCount == 1); + assert(poolStats.unusedRangeSizeMax == BUF_SIZE); + assert(poolStats.unusedSize == BUF_SIZE); + } + + // Free all remaining items. + for(size_t i = items.size(); i--; ) + vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); + items.clear(); + + // Allocate maximum items again. + for(size_t i = 0; i < BUF_COUNT; ++i) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + assert(res == VK_SUCCESS); + items.push_back(item); + } + + // Delete every other item. + for(size_t i = 0; i < BUF_COUNT / 2; ++i) + { + vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); + items.erase(items.begin() + i); + } + + // Defragment! + { + std::vector allocationsToDefragment(items.size()); + for(size_t i = 0; i < items.size(); ++i) + allocationsToDefragment[i] = items[i].Alloc; + + VmaDefragmentationStats defragmentationStats; + res = vmaDefragment(g_hAllocator, allocationsToDefragment.data(), items.size(), nullptr, nullptr, &defragmentationStats); + assert(res == VK_SUCCESS); + assert(defragmentationStats.deviceMemoryBlocksFreed == 2); + } + + // Free all remaining items. + for(size_t i = items.size(); i--; ) + vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); + items.clear(); + + //////////////////////////////////////////////////////////////////////////////// + // Test for vmaMakePoolAllocationsLost + + // Allocate 4 buffers on frame 10. + vmaSetCurrentFrameIndex(g_hAllocator, 10); + for(size_t i = 0; i < 4; ++i) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + assert(res == VK_SUCCESS); + items.push_back(item); + } + + // Touch first 2 of them on frame 11. + vmaSetCurrentFrameIndex(g_hAllocator, 11); + for(size_t i = 0; i < 2; ++i) + { + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); + } + + // vmaMakePoolAllocationsLost. Only remaining 2 should be lost. + size_t lostCount = 0xDEADC0DE; + vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount); + assert(lostCount == 2); + + // Make another call. Now 0 should be lost. + vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount); + assert(lostCount == 0); + + // Make another call, with null count. Should not crash. + vmaMakePoolAllocationsLost(g_hAllocator, pool, nullptr); + + // END: Free all remaining items. + for(size_t i = items.size(); i--; ) + vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); + + items.clear(); + + vmaDestroyPool(g_hAllocator, pool); +} + +static void TestPool_Benchmark( + PoolTestResult& outResult, + const PoolTestConfig& config) +{ + assert(config.ThreadCount > 0); + + RandomNumberGenerator mainRand{config.RandSeed}; + + uint32_t allocationSizeProbabilitySum = std::accumulate( + config.AllocationSizes.begin(), + config.AllocationSizes.end(), + 0u, + [](uint32_t sum, const AllocationSize& allocSize) { + return sum + allocSize.Probability; + }); + + VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferInfo.size = 256; // Whatever. + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = 256; // Whatever. + imageInfo.extent.height = 256; // Whatever. + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // LINEAR if CPU memory. + imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // TRANSFER_SRC if CPU memory. + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + uint32_t bufferMemoryTypeBits = UINT32_MAX; + { + VkBuffer dummyBuffer; + VkResult res = vkCreateBuffer(g_hDevice, &bufferInfo, nullptr, &dummyBuffer); + assert(res == VK_SUCCESS); + + VkMemoryRequirements memReq; + vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq); + bufferMemoryTypeBits = memReq.memoryTypeBits; + + vkDestroyBuffer(g_hDevice, dummyBuffer, nullptr); + } + + uint32_t imageMemoryTypeBits = UINT32_MAX; + { + VkImage dummyImage; + VkResult res = vkCreateImage(g_hDevice, &imageInfo, nullptr, &dummyImage); + assert(res == VK_SUCCESS); + + VkMemoryRequirements memReq; + vkGetImageMemoryRequirements(g_hDevice, dummyImage, &memReq); + imageMemoryTypeBits = memReq.memoryTypeBits; + + vkDestroyImage(g_hDevice, dummyImage, nullptr); + } + + uint32_t memoryTypeBits = 0; + if(config.UsesBuffers() && config.UsesImages()) + { + memoryTypeBits = bufferMemoryTypeBits & imageMemoryTypeBits; + if(memoryTypeBits == 0) + { + PrintWarning(L"Cannot test buffers + images in the same memory pool on this GPU."); + return; + } + } + else if(config.UsesBuffers()) + memoryTypeBits = bufferMemoryTypeBits; + else if(config.UsesImages()) + memoryTypeBits = imageMemoryTypeBits; + else + assert(0); + + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.memoryTypeIndex = 0; + poolCreateInfo.minBlockCount = 1; + poolCreateInfo.maxBlockCount = 1; + poolCreateInfo.blockSize = config.PoolSize; + poolCreateInfo.frameInUseCount = 1; + + VmaAllocationCreateInfo dummyAllocCreateInfo = {}; + dummyAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + vmaFindMemoryTypeIndex(g_hAllocator, memoryTypeBits, &dummyAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); + + VmaPool pool; + VkResult res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + assert(res == VK_SUCCESS); + + // Start time measurement - after creating pool and initializing data structures. + time_point timeBeg = std::chrono::high_resolution_clock::now(); + + //////////////////////////////////////////////////////////////////////////////// + // ThreadProc + auto ThreadProc = [&]( + PoolTestThreadResult* outThreadResult, + uint32_t randSeed, + HANDLE frameStartEvent, + HANDLE frameEndEvent) -> void + { + RandomNumberGenerator threadRand{randSeed}; + + outThreadResult->AllocationTimeMin = duration::max(); + outThreadResult->AllocationTimeSum = duration::zero(); + outThreadResult->AllocationTimeMax = duration::min(); + outThreadResult->DeallocationTimeMin = duration::max(); + outThreadResult->DeallocationTimeSum = duration::zero(); + outThreadResult->DeallocationTimeMax = duration::min(); + outThreadResult->AllocationCount = 0; + outThreadResult->DeallocationCount = 0; + outThreadResult->LostAllocationCount = 0; + outThreadResult->LostAllocationTotalSize = 0; + outThreadResult->FailedAllocationCount = 0; + outThreadResult->FailedAllocationTotalSize = 0; + + struct Item + { + VkDeviceSize BufferSize; + VkExtent2D ImageSize; + VkBuffer Buf; + VkImage Image; + VmaAllocation Alloc; + + VkDeviceSize CalcSizeBytes() const + { + return BufferSize + + ImageSize.width * ImageSize.height * 4; + } + }; + std::vector unusedItems, usedItems; + + const size_t threadTotalItemCount = config.TotalItemCount / config.ThreadCount; + + // Create all items - all unused, not yet allocated. + for(size_t i = 0; i < threadTotalItemCount; ++i) + { + Item item = {}; + + uint32_t allocSizeIndex = 0; + uint32_t r = threadRand.Generate() % allocationSizeProbabilitySum; + while(r >= config.AllocationSizes[allocSizeIndex].Probability) + r -= config.AllocationSizes[allocSizeIndex++].Probability; + + const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex]; + if(allocSize.BufferSizeMax > 0) + { + assert(allocSize.BufferSizeMin > 0); + assert(allocSize.ImageSizeMin == 0 && allocSize.ImageSizeMax == 0); + if(allocSize.BufferSizeMax == allocSize.BufferSizeMin) + item.BufferSize = allocSize.BufferSizeMin; + else + { + item.BufferSize = allocSize.BufferSizeMin + threadRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin); + item.BufferSize = item.BufferSize / 16 * 16; + } + } + else + { + assert(allocSize.ImageSizeMin > 0 && allocSize.ImageSizeMax > 0); + if(allocSize.ImageSizeMax == allocSize.ImageSizeMin) + item.ImageSize.width = item.ImageSize.height = allocSize.ImageSizeMax; + else + { + item.ImageSize.width = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); + item.ImageSize.height = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); + } + } + + unusedItems.push_back(item); + } + + auto Allocate = [&](Item& item) -> VkResult + { + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = pool; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT | + VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; + + if(item.BufferSize) + { + bufferInfo.size = item.BufferSize; + PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult); + return vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocCreateInfo, &item.Buf, &item.Alloc, nullptr); + } + else + { + assert(item.ImageSize.width && item.ImageSize.height); + + imageInfo.extent.width = item.ImageSize.width; + imageInfo.extent.height = item.ImageSize.height; + PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult); + return vmaCreateImage(g_hAllocator, &imageInfo, &allocCreateInfo, &item.Image, &item.Alloc, nullptr); + } + }; + + //////////////////////////////////////////////////////////////////////////////// + // Frames + for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex) + { + WaitForSingleObject(frameStartEvent, INFINITE); + + // Always make some percent of used bufs unused, to choose different used ones. + const size_t bufsToMakeUnused = usedItems.size() * config.ItemsToMakeUnusedPercent / 100; + for(size_t i = 0; i < bufsToMakeUnused; ++i) + { + size_t index = threadRand.Generate() % usedItems.size(); + unusedItems.push_back(usedItems[index]); + usedItems.erase(usedItems.begin() + index); + } + + // Determine which bufs we want to use in this frame. + const size_t usedBufCount = (threadRand.Generate() % (config.UsedItemCountMax - config.UsedItemCountMin) + config.UsedItemCountMin) + / config.ThreadCount; + assert(usedBufCount < usedItems.size() + unusedItems.size()); + // Move some used to unused. + while(usedBufCount < usedItems.size()) + { + size_t index = threadRand.Generate() % usedItems.size(); + unusedItems.push_back(usedItems[index]); + usedItems.erase(usedItems.begin() + index); + } + // Move some unused to used. + while(usedBufCount > usedItems.size()) + { + size_t index = threadRand.Generate() % unusedItems.size(); + usedItems.push_back(unusedItems[index]); + unusedItems.erase(unusedItems.begin() + index); + } + + uint32_t touchExistingCount = 0; + uint32_t touchLostCount = 0; + uint32_t createSucceededCount = 0; + uint32_t createFailedCount = 0; + + // Touch all used bufs. If not created or lost, allocate. + for(size_t i = 0; i < usedItems.size(); ++i) + { + Item& item = usedItems[i]; + // Not yet created. + if(item.Alloc == VK_NULL_HANDLE) + { + res = Allocate(item); + ++outThreadResult->AllocationCount; + if(res != VK_SUCCESS) + { + item.Alloc = VK_NULL_HANDLE; + item.Buf = VK_NULL_HANDLE; + ++outThreadResult->FailedAllocationCount; + outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes(); + ++createFailedCount; + } + else + ++createSucceededCount; + } + else + { + // Touch. + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, item.Alloc, &allocInfo); + // Lost. + if(allocInfo.deviceMemory == VK_NULL_HANDLE) + { + ++touchLostCount; + + // Destroy. + { + PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult); + if(item.Buf) + vmaDestroyBuffer(g_hAllocator, item.Buf, item.Alloc); + else + vmaDestroyImage(g_hAllocator, item.Image, item.Alloc); + ++outThreadResult->DeallocationCount; + } + item.Alloc = VK_NULL_HANDLE; + item.Buf = VK_NULL_HANDLE; + + ++outThreadResult->LostAllocationCount; + outThreadResult->LostAllocationTotalSize += item.CalcSizeBytes(); + + // Recreate. + res = Allocate(item); + ++outThreadResult->AllocationCount; + // Creation failed. + if(res != VK_SUCCESS) + { + ++outThreadResult->FailedAllocationCount; + outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes(); + ++createFailedCount; + } + else + ++createSucceededCount; + } + else + ++touchExistingCount; + } + } + + /* + printf("Thread %u frame %u: Touch existing %u lost %u, create succeeded %u failed %u\n", + randSeed, frameIndex, + touchExistingCount, touchLostCount, + createSucceededCount, createFailedCount); + */ + + SetEvent(frameEndEvent); + } + + // Free all remaining items. + for(size_t i = usedItems.size(); i--; ) + { + PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult); + if(usedItems[i].Buf) + vmaDestroyBuffer(g_hAllocator, usedItems[i].Buf, usedItems[i].Alloc); + else + vmaDestroyImage(g_hAllocator, usedItems[i].Image, usedItems[i].Alloc); + ++outThreadResult->DeallocationCount; + } + for(size_t i = unusedItems.size(); i--; ) + { + PoolDeallocationTimeRegisterObj timeRegisterOb(*outThreadResult); + if(unusedItems[i].Buf) + vmaDestroyBuffer(g_hAllocator, unusedItems[i].Buf, unusedItems[i].Alloc); + else + vmaDestroyImage(g_hAllocator, unusedItems[i].Image, unusedItems[i].Alloc); + ++outThreadResult->DeallocationCount; + } + }; + + // Launch threads. + uint32_t threadRandSeed = mainRand.Generate(); + std::vector frameStartEvents{config.ThreadCount}; + std::vector frameEndEvents{config.ThreadCount}; + std::vector bkgThreads; + std::vector threadResults{config.ThreadCount}; + for(uint32_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex) + { + frameStartEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL); + frameEndEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL); + bkgThreads.emplace_back(std::bind( + ThreadProc, + &threadResults[threadIndex], + threadRandSeed + threadIndex, + frameStartEvents[threadIndex], + frameEndEvents[threadIndex])); + } + + // Execute frames. + assert(config.ThreadCount <= MAXIMUM_WAIT_OBJECTS); + for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex) + { + vmaSetCurrentFrameIndex(g_hAllocator, frameIndex); + for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex) + SetEvent(frameStartEvents[threadIndex]); + WaitForMultipleObjects(config.ThreadCount, &frameEndEvents[0], TRUE, INFINITE); + } + + // Wait for threads finished + for(size_t i = 0; i < bkgThreads.size(); ++i) + { + bkgThreads[i].join(); + CloseHandle(frameEndEvents[i]); + CloseHandle(frameStartEvents[i]); + } + bkgThreads.clear(); + + // Finish time measurement - before destroying pool. + outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg; + + vmaDestroyPool(g_hAllocator, pool); + + outResult.AllocationTimeMin = duration::max(); + outResult.AllocationTimeAvg = duration::zero(); + outResult.AllocationTimeMax = duration::min(); + outResult.DeallocationTimeMin = duration::max(); + outResult.DeallocationTimeAvg = duration::zero(); + outResult.DeallocationTimeMax = duration::min(); + outResult.LostAllocationCount = 0; + outResult.LostAllocationTotalSize = 0; + outResult.FailedAllocationCount = 0; + outResult.FailedAllocationTotalSize = 0; + size_t allocationCount = 0; + size_t deallocationCount = 0; + for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex) + { + const PoolTestThreadResult& threadResult = threadResults[threadIndex]; + outResult.AllocationTimeMin = std::min(outResult.AllocationTimeMin, threadResult.AllocationTimeMin); + outResult.AllocationTimeMax = std::max(outResult.AllocationTimeMax, threadResult.AllocationTimeMax); + outResult.AllocationTimeAvg += threadResult.AllocationTimeSum; + outResult.DeallocationTimeMin = std::min(outResult.DeallocationTimeMin, threadResult.DeallocationTimeMin); + outResult.DeallocationTimeMax = std::max(outResult.DeallocationTimeMax, threadResult.DeallocationTimeMax); + outResult.DeallocationTimeAvg += threadResult.DeallocationTimeSum; + allocationCount += threadResult.AllocationCount; + deallocationCount += threadResult.DeallocationCount; + outResult.FailedAllocationCount += threadResult.FailedAllocationCount; + outResult.FailedAllocationTotalSize += threadResult.FailedAllocationTotalSize; + outResult.LostAllocationCount += threadResult.LostAllocationCount; + outResult.LostAllocationTotalSize += threadResult.LostAllocationTotalSize; + } + if(allocationCount) + outResult.AllocationTimeAvg /= allocationCount; + if(deallocationCount) + outResult.DeallocationTimeAvg /= deallocationCount; +} + +static inline bool MemoryRegionsOverlap(char* ptr1, size_t size1, char* ptr2, size_t size2) +{ + if(ptr1 < ptr2) + return ptr1 + size1 > ptr2; + else if(ptr2 < ptr1) + return ptr2 + size2 > ptr1; + else + return true; +} + +static void TestMapping() +{ + wprintf(L"Testing mapping...\n"); + + VkResult res; + uint32_t memTypeIndex = UINT32_MAX; + + enum TEST + { + TEST_NORMAL, + TEST_POOL, + TEST_DEDICATED, + TEST_COUNT + }; + for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex) + { + VmaPool pool = nullptr; + if(testIndex == TEST_POOL) + { + assert(memTypeIndex != UINT32_MAX); + VmaPoolCreateInfo poolInfo = {}; + poolInfo.memoryTypeIndex = memTypeIndex; + res = vmaCreatePool(g_hAllocator, &poolInfo, &pool); + assert(res == VK_SUCCESS); + } + + VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufInfo.size = 0x10000; + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.pool = pool; + if(testIndex == TEST_DEDICATED) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + VmaAllocationInfo allocInfo; + + // Mapped manually + + // Create 2 buffers. + BufferInfo bufferInfos[3]; + for(size_t i = 0; i < 2; ++i) + { + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, + &bufferInfos[i].Buffer, &bufferInfos[i].Allocation, &allocInfo); + assert(res == VK_SUCCESS); + assert(allocInfo.pMappedData == nullptr); + memTypeIndex = allocInfo.memoryType; + } + + // Map buffer 0. + char* data00 = nullptr; + res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data00); + assert(res == VK_SUCCESS && data00 != nullptr); + data00[0xFFFF] = data00[0]; + + // Map buffer 0 second time. + char* data01 = nullptr; + res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data01); + assert(res == VK_SUCCESS && data01 == data00); + + // Map buffer 1. + char* data1 = nullptr; + res = vmaMapMemory(g_hAllocator, bufferInfos[1].Allocation, (void**)&data1); + assert(res == VK_SUCCESS && data1 != nullptr); + assert(!MemoryRegionsOverlap(data00, (size_t)bufInfo.size, data1, (size_t)bufInfo.size)); + data1[0xFFFF] = data1[0]; + + // Unmap buffer 0 two times. + vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation); + vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation); + vmaGetAllocationInfo(g_hAllocator, bufferInfos[0].Allocation, &allocInfo); + assert(allocInfo.pMappedData == nullptr); + + // Unmap buffer 1. + vmaUnmapMemory(g_hAllocator, bufferInfos[1].Allocation); + vmaGetAllocationInfo(g_hAllocator, bufferInfos[1].Allocation, &allocInfo); + assert(allocInfo.pMappedData == nullptr); + + // Create 3rd buffer - persistently mapped. + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT; + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, + &bufferInfos[2].Buffer, &bufferInfos[2].Allocation, &allocInfo); + assert(res == VK_SUCCESS && allocInfo.pMappedData != nullptr); + + // Map buffer 2. + char* data2 = nullptr; + res = vmaMapMemory(g_hAllocator, bufferInfos[2].Allocation, (void**)&data2); + assert(res == VK_SUCCESS && data2 == allocInfo.pMappedData); + data2[0xFFFF] = data2[0]; + + // Unmap buffer 2. + vmaUnmapMemory(g_hAllocator, bufferInfos[2].Allocation); + vmaGetAllocationInfo(g_hAllocator, bufferInfos[2].Allocation, &allocInfo); + assert(allocInfo.pMappedData == data2); + + // Destroy all buffers. + for(size_t i = 3; i--; ) + vmaDestroyBuffer(g_hAllocator, bufferInfos[i].Buffer, bufferInfos[i].Allocation); + + vmaDestroyPool(g_hAllocator, pool); + } +} + +static void TestMappingMultithreaded() +{ + wprintf(L"Testing mapping multithreaded...\n"); + + static const uint32_t threadCount = 16; + static const uint32_t bufferCount = 1024; + static const uint32_t threadBufferCount = bufferCount / threadCount; + + VkResult res; + volatile uint32_t memTypeIndex = UINT32_MAX; + + enum TEST + { + TEST_NORMAL, + TEST_POOL, + TEST_DEDICATED, + TEST_COUNT + }; + for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex) + { + VmaPool pool = nullptr; + if(testIndex == TEST_POOL) + { + assert(memTypeIndex != UINT32_MAX); + VmaPoolCreateInfo poolInfo = {}; + poolInfo.memoryTypeIndex = memTypeIndex; + res = vmaCreatePool(g_hAllocator, &poolInfo, &pool); + assert(res == VK_SUCCESS); + } + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = 0x10000; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.pool = pool; + if(testIndex == TEST_DEDICATED) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + std::thread threads[threadCount]; + for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex) + { + threads[threadIndex] = std::thread([=, &memTypeIndex](){ + // ======== THREAD FUNCTION ======== + + RandomNumberGenerator rand{threadIndex}; + + enum class MODE + { + // Don't map this buffer at all. + DONT_MAP, + // Map and quickly unmap. + MAP_FOR_MOMENT, + // Map and unmap before destruction. + MAP_FOR_LONGER, + // Map two times. Quickly unmap, second unmap before destruction. + MAP_TWO_TIMES, + // Create this buffer as persistently mapped. + PERSISTENTLY_MAPPED, + COUNT + }; + std::vector bufInfos{threadBufferCount}; + std::vector bufModes{threadBufferCount}; + + for(uint32_t bufferIndex = 0; bufferIndex < threadBufferCount; ++bufferIndex) + { + BufferInfo& bufInfo = bufInfos[bufferIndex]; + const MODE mode = (MODE)(rand.Generate() % (uint32_t)MODE::COUNT); + bufModes[bufferIndex] = mode; + + VmaAllocationCreateInfo localAllocCreateInfo = allocCreateInfo; + if(mode == MODE::PERSISTENTLY_MAPPED) + localAllocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VmaAllocationInfo allocInfo; + VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &localAllocCreateInfo, + &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo); + assert(res == VK_SUCCESS); + + if(memTypeIndex == UINT32_MAX) + memTypeIndex = allocInfo.memoryType; + + char* data = nullptr; + + if(mode == MODE::PERSISTENTLY_MAPPED) + { + data = (char*)allocInfo.pMappedData; + assert(data != nullptr); + } + else if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_FOR_LONGER || + mode == MODE::MAP_TWO_TIMES) + { + assert(data == nullptr); + res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data); + assert(res == VK_SUCCESS && data != nullptr); + + if(mode == MODE::MAP_TWO_TIMES) + { + char* data2 = nullptr; + res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data2); + assert(res == VK_SUCCESS && data2 == data); + } + } + else if(mode == MODE::DONT_MAP) + { + assert(allocInfo.pMappedData == nullptr); + } + else + assert(0); + + // Test if reading and writing from the beginning and end of mapped memory doesn't crash. + if(data) + data[0xFFFF] = data[0]; + + if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_TWO_TIMES) + { + vmaUnmapMemory(g_hAllocator, bufInfo.Allocation); + + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, bufInfo.Allocation, &allocInfo); + if(mode == MODE::MAP_FOR_MOMENT) + assert(allocInfo.pMappedData == nullptr); + else + assert(allocInfo.pMappedData == data); + } + + switch(rand.Generate() % 3) + { + case 0: Sleep(0); break; // Yield. + case 1: Sleep(10); break; // 10 ms + // default: No sleep. + } + + // Test if reading and writing from the beginning and end of mapped memory doesn't crash. + if(data) + data[0xFFFF] = data[0]; + } + + for(size_t bufferIndex = threadBufferCount; bufferIndex--; ) + { + if(bufModes[bufferIndex] == MODE::MAP_FOR_LONGER || + bufModes[bufferIndex] == MODE::MAP_TWO_TIMES) + { + vmaUnmapMemory(g_hAllocator, bufInfos[bufferIndex].Allocation); + + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, bufInfos[bufferIndex].Allocation, &allocInfo); + assert(allocInfo.pMappedData == nullptr); + } + + vmaDestroyBuffer(g_hAllocator, bufInfos[bufferIndex].Buffer, bufInfos[bufferIndex].Allocation); + } + }); + } + + for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex) + threads[threadIndex].join(); + + vmaDestroyPool(g_hAllocator, pool); + } +} + +static void WriteMainTestResultHeader(FILE* file) +{ + fprintf(file, + "Code,Test,Time," + "Config," + "Total Time (us)," + "Allocation Time Min (us)," + "Allocation Time Avg (us)," + "Allocation Time Max (us)," + "Deallocation Time Min (us)," + "Deallocation Time Avg (us)," + "Deallocation Time Max (us)," + "Total Memory Allocated (B)," + "Free Range Size Avg (B)," + "Free Range Size Max (B)\n"); +} + +static void WriteMainTestResult( + FILE* file, + const char* codeDescription, + const char* testDescription, + const Config& config, const Result& result) +{ + float totalTimeSeconds = ToFloatSeconds(result.TotalTime); + float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin); + float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg); + float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax); + float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin); + float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg); + float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax); + + time_t rawTime; time(&rawTime); + struct tm timeInfo; localtime_s(&timeInfo, &rawTime); + char timeStr[128]; + strftime(timeStr, _countof(timeStr), "%c", &timeInfo); + + fprintf(file, + "%s,%s,%s," + "BeginBytesToAllocate=%I64u MaxBytesToAllocate=%I64u AdditionalOperationCount=%u ThreadCount=%u FreeOrder=%d," + "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n", + codeDescription, + testDescription, + timeStr, + config.BeginBytesToAllocate, config.MaxBytesToAllocate, config.AdditionalOperationCount, config.ThreadCount, (uint32_t)config.FreeOrder, + totalTimeSeconds * 1e6f, + allocationTimeMinSeconds * 1e6f, + allocationTimeAvgSeconds * 1e6f, + allocationTimeMaxSeconds * 1e6f, + deallocationTimeMinSeconds * 1e6f, + deallocationTimeAvgSeconds * 1e6f, + deallocationTimeMaxSeconds * 1e6f, + result.TotalMemoryAllocated, + result.FreeRangeSizeAvg, + result.FreeRangeSizeMax); +} + +static void WritePoolTestResultHeader(FILE* file) +{ + fprintf(file, + "Code,Test,Time," + "Config," + "Total Time (us)," + "Allocation Time Min (us)," + "Allocation Time Avg (us)," + "Allocation Time Max (us)," + "Deallocation Time Min (us)," + "Deallocation Time Avg (us)," + "Deallocation Time Max (us)," + "Lost Allocation Count," + "Lost Allocation Total Size (B)," + "Failed Allocation Count," + "Failed Allocation Total Size (B)\n"); +} + +static void WritePoolTestResult( + FILE* file, + const char* codeDescription, + const char* testDescription, + const PoolTestConfig& config, + const PoolTestResult& result) +{ + float totalTimeSeconds = ToFloatSeconds(result.TotalTime); + float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin); + float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg); + float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax); + float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin); + float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg); + float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax); + + time_t rawTime; time(&rawTime); + struct tm timeInfo; localtime_s(&timeInfo, &rawTime); + char timeStr[128]; + strftime(timeStr, _countof(timeStr), "%c", &timeInfo); + + fprintf(file, + "%s,%s,%s," + "ThreadCount=%u PoolSize=%llu FrameCount=%u TotalItemCount=%u UsedItemCount=%u...%u ItemsToMakeUnusedPercent=%u," + "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u,%I64u\n", + // General + codeDescription, + testDescription, + timeStr, + // Config + config.ThreadCount, + (unsigned long long)config.PoolSize, + config.FrameCount, + config.TotalItemCount, + config.UsedItemCountMin, + config.UsedItemCountMax, + config.ItemsToMakeUnusedPercent, + // Results + totalTimeSeconds * 1e6f, + allocationTimeMinSeconds * 1e6f, + allocationTimeAvgSeconds * 1e6f, + allocationTimeMaxSeconds * 1e6f, + deallocationTimeMinSeconds * 1e6f, + deallocationTimeAvgSeconds * 1e6f, + deallocationTimeMaxSeconds * 1e6f, + result.LostAllocationCount, + result.LostAllocationTotalSize, + result.FailedAllocationCount, + result.FailedAllocationTotalSize); +} + +static void PerformCustomMainTest(FILE* file) +{ + Config config{}; + config.RandSeed = 65735476; + //config.MaxBytesToAllocate = 4ull * 1024 * 1024; // 4 MB + config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB + config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY + config.FreeOrder = FREE_ORDER::FORWARD; + config.ThreadCount = 16; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 50; + + // Buffers + //config.AllocationSizes.push_back({4, 16, 1024}); + config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB + + // Images + //config.AllocationSizes.push_back({4, 0, 0, 4, 32}); + //config.AllocationSizes.push_back({4, 0, 0, 256, 2048}); + + config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100; + config.AdditionalOperationCount = 1024; + + Result result{}; + VkResult res = MainTest(result, config); + assert(res == VK_SUCCESS); + WriteMainTestResult(file, "Foo", "CustomTest", config, result); +} + +static void PerformCustomPoolTest(FILE* file) +{ + PoolTestConfig config; + config.PoolSize = 100 * 1024 * 1024; + config.RandSeed = 2345764; + config.ThreadCount = 1; + config.FrameCount = 200; + config.ItemsToMakeUnusedPercent = 2; + + AllocationSize allocSize = {}; + allocSize.BufferSizeMin = 1024; + allocSize.BufferSizeMax = 1024 * 1024; + allocSize.Probability = 1; + config.AllocationSizes.push_back(allocSize); + + allocSize.BufferSizeMin = 0; + allocSize.BufferSizeMax = 0; + allocSize.ImageSizeMin = 128; + allocSize.ImageSizeMax = 1024; + allocSize.Probability = 1; + config.AllocationSizes.push_back(allocSize); + + config.PoolSize = config.CalcAvgResourceSize() * 200; + config.UsedItemCountMax = 160; + config.TotalItemCount = config.UsedItemCountMax * 10; + config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100; + + g_MemoryAliasingWarningEnabled = false; + PoolTestResult result = {}; + TestPool_Benchmark(result, config); + g_MemoryAliasingWarningEnabled = true; + + WritePoolTestResult(file, "Code desc", "Test desc", config, result); +} + +enum CONFIG_TYPE { + CONFIG_TYPE_MINIMUM, + CONFIG_TYPE_SMALL, + CONFIG_TYPE_AVERAGE, + CONFIG_TYPE_LARGE, + CONFIG_TYPE_MAXIMUM, + CONFIG_TYPE_COUNT +}; + +static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_SMALL; +//static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_LARGE; +static const char* CODE_DESCRIPTION = "Foo"; + +static void PerformMainTests(FILE* file) +{ + uint32_t repeatCount = 1; + if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3; + + Config config{}; + config.RandSeed = 65735476; + config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY + config.FreeOrder = FREE_ORDER::FORWARD; + + size_t threadCountCount = 1; + switch(ConfigType) + { + case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break; + case CONFIG_TYPE_SMALL: threadCountCount = 2; break; + case CONFIG_TYPE_AVERAGE: threadCountCount = 3; break; + case CONFIG_TYPE_LARGE: threadCountCount = 5; break; + case CONFIG_TYPE_MAXIMUM: threadCountCount = 7; break; + default: assert(0); + } + for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex) + { + std::string desc1; + + switch(threadCountIndex) + { + case 0: + desc1 += "1_thread"; + config.ThreadCount = 1; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 0; + break; + case 1: + desc1 += "16_threads+0%_common"; + config.ThreadCount = 16; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 0; + break; + case 2: + desc1 += "16_threads+50%_common"; + config.ThreadCount = 16; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 50; + break; + case 3: + desc1 += "16_threads+100%_common"; + config.ThreadCount = 16; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 100; + break; + case 4: + desc1 += "2_threads+0%_common"; + config.ThreadCount = 2; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 0; + break; + case 5: + desc1 += "2_threads+50%_common"; + config.ThreadCount = 2; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 50; + break; + case 6: + desc1 += "2_threads+100%_common"; + config.ThreadCount = 2; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 100; + break; + default: + assert(0); + } + + // 0 = buffers, 1 = images, 2 = buffers and images + size_t buffersVsImagesCount = 2; + if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount; + for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex) + { + std::string desc2 = desc1; + switch(buffersVsImagesIndex) + { + case 0: desc2 += " Buffers"; break; + case 1: desc2 += " Images"; break; + case 2: desc2 += " Buffers+Images"; break; + default: assert(0); + } + + // 0 = small, 1 = large, 2 = small and large + size_t smallVsLargeCount = 2; + if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount; + for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex) + { + std::string desc3 = desc2; + switch(smallVsLargeIndex) + { + case 0: desc3 += " Small"; break; + case 1: desc3 += " Large"; break; + case 2: desc3 += " Small+Large"; break; + default: assert(0); + } + + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB + else + config.MaxBytesToAllocate = 4ull * 1024 * 1024; + + // 0 = varying sizes min...max, 1 = set of constant sizes + size_t constantSizesCount = 1; + if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount; + for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex) + { + std::string desc4 = desc3; + switch(constantSizesIndex) + { + case 0: desc4 += " Varying_sizes"; break; + case 1: desc4 += " Constant_sizes"; break; + default: assert(0); + } + + config.AllocationSizes.clear(); + // Buffers present + if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2) + { + // Small + if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 16, 1024}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 16, 16}); + config.AllocationSizes.push_back({1, 64, 64}); + config.AllocationSizes.push_back({1, 256, 256}); + config.AllocationSizes.push_back({1, 1024, 1024}); + } + } + // Large + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0x10000, 0x10000}); + config.AllocationSizes.push_back({1, 0x80000, 0x80000}); + config.AllocationSizes.push_back({1, 0x200000, 0x200000}); + config.AllocationSizes.push_back({1, 0xA00000, 0xA00000}); + } + } + } + // Images present + if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2) + { + // Small + if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0, 0, 4, 32}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0, 0, 4, 4}); + config.AllocationSizes.push_back({1, 0, 0, 8, 8}); + config.AllocationSizes.push_back({1, 0, 0, 16, 16}); + config.AllocationSizes.push_back({1, 0, 0, 32, 32}); + } + } + // Large + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0, 0, 256, 2048}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0, 0, 256, 256}); + config.AllocationSizes.push_back({1, 0, 0, 512, 512}); + config.AllocationSizes.push_back({1, 0, 0, 1024, 1024}); + config.AllocationSizes.push_back({1, 0, 0, 2048, 2048}); + } + } + } + + // 0 = 100%, additional_operations = 0, 1 = 50%, 2 = 5%, 3 = 95% additional_operations = a lot + size_t beginBytesToAllocateCount = 1; + if(ConfigType >= CONFIG_TYPE_SMALL) ++beginBytesToAllocateCount; + if(ConfigType >= CONFIG_TYPE_AVERAGE) ++beginBytesToAllocateCount; + if(ConfigType >= CONFIG_TYPE_LARGE) ++beginBytesToAllocateCount; + for(size_t beginBytesToAllocateIndex = 0; beginBytesToAllocateIndex < beginBytesToAllocateCount; ++beginBytesToAllocateIndex) + { + std::string desc5 = desc4; + + switch(beginBytesToAllocateIndex) + { + case 0: + desc5 += " Allocate_100%"; + config.BeginBytesToAllocate = config.MaxBytesToAllocate; + config.AdditionalOperationCount = 0; + break; + case 1: + desc5 += " Allocate_50%+Operations"; + config.BeginBytesToAllocate = config.MaxBytesToAllocate * 50 / 100; + config.AdditionalOperationCount = 1024; + break; + case 2: + desc5 += " Allocate_5%+Operations"; + config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100; + config.AdditionalOperationCount = 1024; + break; + case 3: + desc5 += " Allocate_95%+Operations"; + config.BeginBytesToAllocate = config.MaxBytesToAllocate * 95 / 100; + config.AdditionalOperationCount = 1024; + break; + default: + assert(0); + } + + const char* testDescription = desc5.c_str(); + + for(size_t repeat = 0; repeat < repeatCount; ++repeat) + { + printf("%s Repeat %u\n", testDescription, (uint32_t)repeat); + + Result result{}; + VkResult res = MainTest(result, config); + assert(res == VK_SUCCESS); + WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result); + } + } + } + } + } + } +} + +static void PerformPoolTests(FILE* file) +{ + const size_t AVG_RESOURCES_PER_POOL = 300; + + uint32_t repeatCount = 1; + if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3; + + PoolTestConfig config{}; + config.RandSeed = 2346343; + config.FrameCount = 200; + config.ItemsToMakeUnusedPercent = 2; + + size_t threadCountCount = 1; + switch(ConfigType) + { + case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break; + case CONFIG_TYPE_SMALL: threadCountCount = 2; break; + case CONFIG_TYPE_AVERAGE: threadCountCount = 2; break; + case CONFIG_TYPE_LARGE: threadCountCount = 3; break; + case CONFIG_TYPE_MAXIMUM: threadCountCount = 3; break; + default: assert(0); + } + for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex) + { + std::string desc1; + + switch(threadCountIndex) + { + case 0: + desc1 += "1_thread"; + config.ThreadCount = 1; + break; + case 1: + desc1 += "16_threads"; + config.ThreadCount = 16; + break; + case 2: + desc1 += "2_threads"; + config.ThreadCount = 2; + break; + default: + assert(0); + } + + // 0 = buffers, 1 = images, 2 = buffers and images + size_t buffersVsImagesCount = 2; + if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount; + for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex) + { + std::string desc2 = desc1; + switch(buffersVsImagesIndex) + { + case 0: desc2 += " Buffers"; break; + case 1: desc2 += " Images"; break; + case 2: desc2 += " Buffers+Images"; break; + default: assert(0); + } + + // 0 = small, 1 = large, 2 = small and large + size_t smallVsLargeCount = 2; + if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount; + for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex) + { + std::string desc3 = desc2; + switch(smallVsLargeIndex) + { + case 0: desc3 += " Small"; break; + case 1: desc3 += " Large"; break; + case 2: desc3 += " Small+Large"; break; + default: assert(0); + } + + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + config.PoolSize = 6ull * 1024 * 1024 * 1024; // 6 GB + else + config.PoolSize = 4ull * 1024 * 1024; + + // 0 = varying sizes min...max, 1 = set of constant sizes + size_t constantSizesCount = 1; + if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount; + for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex) + { + std::string desc4 = desc3; + switch(constantSizesIndex) + { + case 0: desc4 += " Varying_sizes"; break; + case 1: desc4 += " Constant_sizes"; break; + default: assert(0); + } + + config.AllocationSizes.clear(); + // Buffers present + if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2) + { + // Small + if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 16, 1024}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 16, 16}); + config.AllocationSizes.push_back({1, 64, 64}); + config.AllocationSizes.push_back({1, 256, 256}); + config.AllocationSizes.push_back({1, 1024, 1024}); + } + } + // Large + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0x10000, 0x10000}); + config.AllocationSizes.push_back({1, 0x80000, 0x80000}); + config.AllocationSizes.push_back({1, 0x200000, 0x200000}); + config.AllocationSizes.push_back({1, 0xA00000, 0xA00000}); + } + } + } + // Images present + if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2) + { + // Small + if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0, 0, 4, 32}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0, 0, 4, 4}); + config.AllocationSizes.push_back({1, 0, 0, 8, 8}); + config.AllocationSizes.push_back({1, 0, 0, 16, 16}); + config.AllocationSizes.push_back({1, 0, 0, 32, 32}); + } + } + // Large + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0, 0, 256, 2048}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0, 0, 256, 256}); + config.AllocationSizes.push_back({1, 0, 0, 512, 512}); + config.AllocationSizes.push_back({1, 0, 0, 1024, 1024}); + config.AllocationSizes.push_back({1, 0, 0, 2048, 2048}); + } + } + } + + const VkDeviceSize avgResourceSize = config.CalcAvgResourceSize(); + config.PoolSize = avgResourceSize * AVG_RESOURCES_PER_POOL; + + // 0 = 66%, 1 = 133%, 2 = 100%, 3 = 33%, 4 = 166% + size_t subscriptionModeCount; + switch(ConfigType) + { + case CONFIG_TYPE_MINIMUM: subscriptionModeCount = 2; break; + case CONFIG_TYPE_SMALL: subscriptionModeCount = 2; break; + case CONFIG_TYPE_AVERAGE: subscriptionModeCount = 3; break; + case CONFIG_TYPE_LARGE: subscriptionModeCount = 5; break; + case CONFIG_TYPE_MAXIMUM: subscriptionModeCount = 5; break; + default: assert(0); + } + for(size_t subscriptionModeIndex = 0; subscriptionModeIndex < subscriptionModeCount; ++subscriptionModeIndex) + { + std::string desc5 = desc4; + + switch(subscriptionModeIndex) + { + case 0: + desc5 += " Subscription_66%"; + config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 66 / 100; + break; + case 1: + desc5 += " Subscription_133%"; + config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 133 / 100; + break; + case 2: + desc5 += " Subscription_100%"; + config.UsedItemCountMax = AVG_RESOURCES_PER_POOL; + break; + case 3: + desc5 += " Subscription_33%"; + config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 33 / 100; + break; + case 4: + desc5 += " Subscription_166%"; + config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 166 / 100; + break; + default: + assert(0); + } + + config.TotalItemCount = config.UsedItemCountMax * 5; + config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100; + + const char* testDescription = desc5.c_str(); + + for(size_t repeat = 0; repeat < repeatCount; ++repeat) + { + printf("%s Repeat %u\n", testDescription, (uint32_t)repeat); + + PoolTestResult result{}; + g_MemoryAliasingWarningEnabled = false; + TestPool_Benchmark(result, config); + g_MemoryAliasingWarningEnabled = true; + WritePoolTestResult(file, CODE_DESCRIPTION, testDescription, config, result); + } + } + } + } + } + } +} + +void Test() +{ + wprintf(L"TESTING:\n"); + + // # Simple tests + + TestBasics(); + TestPool_SameSize(); + TestHeapSizeLimit(); + TestMapping(); + TestMappingMultithreaded(); + TestDefragmentationSimple(); + TestDefragmentationFull(); + + // # Detailed tests + FILE* file; + fopen_s(&file, "Results.csv", "w"); + assert(file != NULL); + + WriteMainTestResultHeader(file); + PerformMainTests(file); + //PerformCustomMainTest(file); + + WritePoolTestResultHeader(file); + PerformPoolTests(file); + //PerformCustomPoolTest(file); + + fclose(file); + + wprintf(L"Done.\n"); +} + #endif // #ifdef _WIN32 diff --git a/src/Tests.h b/src/Tests.h index 7e9fc4b..9da4f6f 100644 --- a/src/Tests.h +++ b/src/Tests.h @@ -3,6 +3,7 @@ #ifdef _WIN32 +void Test(); #endif // #ifdef _WIN32 diff --git a/src/VulkanSample.cpp b/src/VulkanSample.cpp index 6bdf418..dbe92fc 100644 --- a/src/VulkanSample.cpp +++ b/src/VulkanSample.cpp @@ -38,12 +38,16 @@ static const uint32_t COMMAND_BUFFER_COUNT = 2; static bool g_EnableValidationLayer = true; +VkPhysicalDevice g_hPhysicalDevice; +VkDevice g_hDevice; +VmaAllocator g_hAllocator; +bool g_MemoryAliasingWarningEnabled = true; + static HINSTANCE g_hAppInstance; static HWND g_hWnd; static LONG g_SizeX = 1280, g_SizeY = 720; static VkInstance g_hVulkanInstance; static VkSurfaceKHR g_hSurface; -static VkPhysicalDevice g_hPhysicalDevice; static VkQueue g_hPresentQueue; static VkSurfaceFormatKHR g_SurfaceFormat; static VkExtent2D g_Extent; @@ -77,8 +81,6 @@ static PFN_vkDebugReportMessageEXT g_pvkDebugReportMessageEXT; static PFN_vkDestroyDebugReportCallbackEXT g_pvkDestroyDebugReportCallbackEXT; static VkDebugReportCallbackEXT g_hCallback; -static VkDevice g_hDevice; -static VmaAllocator g_hAllocator; static VkQueue g_hGraphicsQueue; static VkCommandBuffer g_hTemporaryCommandBuffer; @@ -144,10 +146,43 @@ VKAPI_ATTR VkBool32 VKAPI_CALL MyDebugReportCallback( const char* pMessage, void* pUserData) { + // "Non-linear image 0xebc91 is aliased with linear buffer 0xeb8e4 which may indicate a bug." + if(!g_MemoryAliasingWarningEnabled && flags == VK_DEBUG_REPORT_WARNING_BIT_EXT && + (strstr(pMessage, " is aliased with non-linear ") || strstr(pMessage, " is aliased with linear "))) + { + return VK_FALSE; + } + + // Ignoring because when VK_KHR_dedicated_allocation extension is enabled, + // vkGetBufferMemoryRequirements2KHR function is used instead, while Validation + // Layer seems to be unaware of it. + if (strstr(pMessage, "but vkGetBufferMemoryRequirements() has not been called on that buffer") != nullptr) + { + return VK_FALSE; + } + if (strstr(pMessage, "but vkGetImageMemoryRequirements() has not been called on that image") != nullptr) + { + return VK_FALSE; + } + + switch(flags) + { + case VK_DEBUG_REPORT_WARNING_BIT_EXT: + SetConsoleColor(CONSOLE_COLOR::WARNING); + break; + case VK_DEBUG_REPORT_ERROR_BIT_EXT: + SetConsoleColor(CONSOLE_COLOR::ERROR_); + break; + default: + SetConsoleColor(CONSOLE_COLOR::INFO); + } + printf("%s \xBA %s\n", pLayerPrefix, pMessage); - if((flags == VK_DEBUG_REPORT_WARNING_BIT_EXT) || - (flags == VK_DEBUG_REPORT_ERROR_BIT_EXT)) + SetConsoleColor(CONSOLE_COLOR::NORMAL); + + if(flags == VK_DEBUG_REPORT_WARNING_BIT_EXT || + flags == VK_DEBUG_REPORT_ERROR_BIT_EXT) { OutputDebugStringA(pMessage); OutputDebugStringA("\n"); @@ -1602,8 +1637,15 @@ static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) return 0; case WM_KEYDOWN: - if(wParam == VK_ESCAPE) + switch(wParam) + { + case VK_ESCAPE: PostMessage(hWnd, WM_CLOSE, 0, 0); + break; + case 'T': + Test(); + break; + } return 0; default: