#include "Tests.h"
#include "VmaUsage.h"
#include "Common.h"
#include <atomic>
#include <thread>
#include <mutex>
#include <functional>
#ifdef _WIN32
static const char* CODE_DESCRIPTION = "Foo";
static constexpr VkDeviceSize KILOBYTE = 1024;
static constexpr VkDeviceSize MEGABYTE = 1024 * 1024;
extern VkCommandBuffer g_hTemporaryCommandBuffer;
extern const VkAllocationCallbacks* g_Allocs;
extern bool VK_KHR_buffer_device_address_enabled;
extern bool VK_EXT_memory_priority_enabled;
extern PFN_vkGetBufferDeviceAddressKHR g_vkGetBufferDeviceAddressKHR;
void BeginSingleTimeCommands();
void EndSingleTimeCommands();
void SetDebugUtilsObjectName(VkObjectType type, uint64_t handle, const char* name);
#ifndef VMA_DEBUG_MARGIN
#define VMA_DEBUG_MARGIN 0
#endif
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_AVERAGE;
enum class FREE_ORDER { FORWARD, BACKWARD, RANDOM, COUNT };
static const char* FREE_ORDER_NAMES[] =
{
"FORWARD",
"BACKWARD",
"RANDOM",
};
static const char* AlgorithmToStr(uint32_t algorithm)
{
switch(algorithm)
{
case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT:
return "Linear";
case 0:
return "TLSF";
default:
assert(0);
return "";
}
}
static const char* VirtualAlgorithmToStr(uint32_t algorithm)
{
switch (algorithm)
{
case VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT:
return "Linear";
case 0:
return "TLSF";
default:
assert(0);
return "";
}
}
static const wchar_t* DefragmentationAlgorithmToStr(uint32_t algorithm)
{
switch (algorithm)
{
case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT:
return L"Balanced";
case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT:
return L"Fast";
case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT:
return L"Full";
case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT:
return L"Extensive";
case 0:
return L"Default";
default:
assert(0);
return L"";
}
}
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]; std::vector<AllocationSize> AllocationSizes;
uint32_t ThreadCount;
uint32_t ThreadsUsingCommonAllocationsProbabilityPercent;
FREE_ORDER FreeOrder;
VmaAllocationCreateFlags AllocationStrategy; };
struct Result
{
duration TotalTime;
duration AllocationTimeMin, AllocationTimeAvg, AllocationTimeMax;
duration DeallocationTimeMin, DeallocationTimeAvg, DeallocationTimeMax;
VkDeviceSize TotalMemoryAllocated;
VkDeviceSize FreeRangeSizeAvg, FreeRangeSizeMax;
};
struct PoolTestConfig
{
uint32_t RandSeed;
uint32_t ThreadCount;
VkDeviceSize PoolSize;
uint32_t FrameCount;
uint32_t TotalItemCount;
uint32_t UsedItemCountMin, UsedItemCountMax;
uint32_t ItemsToMakeUnusedPercent;
std::vector<AllocationSize> 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 FailedAllocationCount, FailedAllocationTotalSize;
};
static const uint32_t IMAGE_BYTES_PER_PIXEL = 1;
uint32_t g_FrameIndex = 0;
struct BufferInfo
{
VkBuffer Buffer = VK_NULL_HANDLE;
VmaAllocation Allocation = VK_NULL_HANDLE;
};
static uint32_t MemoryTypeToHeap(uint32_t memoryTypeIndex)
{
const VkPhysicalDeviceMemoryProperties* props;
vmaGetMemoryProperties(g_hAllocator, &props);
return props->memoryTypes[memoryTypeIndex].heapIndex;
}
static uint32_t GetAllocationStrategyCount()
{
switch(ConfigType)
{
case CONFIG_TYPE_MINIMUM:
case CONFIG_TYPE_SMALL:
return 1;
default: assert(0);
case CONFIG_TYPE_AVERAGE:
case CONFIG_TYPE_LARGE:
case CONFIG_TYPE_MAXIMUM:
return 2;
}
}
static const char* GetAllocationStrategyName(VmaAllocationCreateFlags allocStrategy)
{
switch(allocStrategy)
{
case VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT: return "MIN_MEMORY"; break;
case VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT: return "MIN_TIME"; break;
case 0: return "Default"; break;
default: assert(0); return "";
}
}
static const char* GetVirtualAllocationStrategyName(VmaVirtualAllocationCreateFlags allocStrategy)
{
switch (allocStrategy)
{
case VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT: return "MIN_MEMORY"; break;
case VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT: return "MIN_TIME"; break;
case 0: return "Default"; break;
default: assert(0); return "";
}
}
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 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)
{
}
};
static void CurrentTimeToStr(std::string& out)
{
time_t rawTime; time(&rawTime);
struct tm timeInfo; localtime_s(&timeInfo, &rawTime);
char timeStr[128];
strftime(timeStr, _countof(timeStr), "%c", &timeInfo);
out = timeStr;
}
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<size_t> 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<Allocation> commonAllocations;
std::mutex commonAllocationsMutex;
auto Allocate = [&](
VkDeviceSize bufferSize,
const VkExtent2D imageExtent,
RandomNumberGenerator& localRand,
VkDeviceSize& totalAllocatedBytes,
std::vector<Allocation>& 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);
memReq.flags |= config.AllocationStrategy;
Allocation allocation = {};
VmaAllocationInfo allocationInfo;
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);
}
}
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<std::mutex> lock(commonAllocationsMutex);
commonAllocations.push_back(allocation);
}
else
allocations.push_back(allocation);
}
else
{
TEST(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<uint32_t> numThreadsReachedMaxAllocations = 0;
HANDLE threadsFinishEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
auto ThreadProc = [&](uint32_t randSeed) -> void
{
RandomNumberGenerator threadRand(randSeed);
VkDeviceSize threadTotalAllocatedBytes = 0;
std::vector<Allocation> threadAllocations;
VkDeviceSize threadBeginBytesToAllocate = config.BeginBytesToAllocate / config.ThreadCount;
VkDeviceSize threadMaxBytesToAllocate = config.MaxBytesToAllocate / config.ThreadCount;
uint32_t threadAdditionalOperationCount = config.AdditionalOperationCount / config.ThreadCount;
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;
}
for(size_t i = 0; i < threadAdditionalOperationCount; ++i)
{
VkDeviceSize bufferSize = 0;
VkExtent2D imageExtent = {};
GetNextAllocationSize(bufferSize, imageExtent, threadRand);
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<std::mutex> 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);
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<std::thread> bkgThreads;
for(size_t i = 0; i < config.ThreadCount; ++i)
{
bkgThreads.emplace_back(std::bind(ThreadProc, threadRandSeed + (uint32_t)i));
}
while(numThreadsReachedMaxAllocations < config.ThreadCount)
Sleep(0);
VmaTotalStatistics vmaStats = {};
vmaCalculateStatistics(g_hAllocator, &vmaStats);
outResult.TotalMemoryAllocated = vmaStats.total.statistics.blockBytes;
outResult.FreeRangeSizeMax = vmaStats.total.unusedRangeSizeMax;
outResult.FreeRangeSizeAvg = round_div<VkDeviceSize>(vmaStats.total.statistics.blockBytes - vmaStats.total.statistics.allocationBytes, vmaStats.total.unusedRangeCount);
SetEvent(threadsFinishEvent);
for(size_t i = 0; i < bkgThreads.size(); ++i)
bkgThreads[i].join();
bkgThreads.clear();
CloseHandle(threadsFinishEvent);
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;
}
void SaveAllocatorStatsToFile(const wchar_t* filePath)
{
#if !defined(VMA_STATS_STRING_ENABLED) || VMA_STATS_STRING_ENABLED
wprintf(L"Saving JSON dump to file \"%s\"\n", filePath);
char* stats;
vmaBuildStatsString(g_hAllocator, &stats, VK_TRUE);
SaveFile(filePath, stats, strlen(stats));
vmaFreeStatsString(g_hAllocator, stats);
#endif
}
struct AllocInfo
{
VmaAllocation m_Allocation = VK_NULL_HANDLE;
VkBuffer m_Buffer = VK_NULL_HANDLE;
VkImage m_Image = VK_NULL_HANDLE;
VkImageLayout m_ImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
uint32_t m_StartValue = 0;
union
{
VkBufferCreateInfo m_BufferInfo;
VkImageCreateInfo m_ImageInfo;
};
VkBuffer m_NewBuffer = VK_NULL_HANDLE;
VkImage m_NewImage = VK_NULL_HANDLE;
void CreateBuffer(
const VkBufferCreateInfo& bufCreateInfo,
const VmaAllocationCreateInfo& allocCreateInfo);
void CreateImage(
const VkImageCreateInfo& imageCreateInfo,
const VmaAllocationCreateInfo& allocCreateInfo,
VkImageLayout layout);
void Destroy();
};
void AllocInfo::CreateBuffer(
const VkBufferCreateInfo& bufCreateInfo,
const VmaAllocationCreateInfo& allocCreateInfo)
{
m_BufferInfo = bufCreateInfo;
VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &m_Buffer, &m_Allocation, nullptr);
TEST(res == VK_SUCCESS);
}
void AllocInfo::CreateImage(
const VkImageCreateInfo& imageCreateInfo,
const VmaAllocationCreateInfo& allocCreateInfo,
VkImageLayout layout)
{
m_ImageInfo = imageCreateInfo;
m_ImageLayout = layout;
VkResult res = vmaCreateImage(g_hAllocator, &imageCreateInfo, &allocCreateInfo, &m_Image, &m_Allocation, nullptr);
TEST(res == VK_SUCCESS);
}
void AllocInfo::Destroy()
{
if(m_Image)
{
assert(!m_Buffer);
vkDestroyImage(g_hDevice, m_Image, g_Allocs);
m_Image = VK_NULL_HANDLE;
}
if(m_Buffer)
{
assert(!m_Image);
vkDestroyBuffer(g_hDevice, m_Buffer, g_Allocs);
m_Buffer = VK_NULL_HANDLE;
}
if(m_Allocation)
{
vmaFreeMemory(g_hAllocator, m_Allocation);
m_Allocation = VK_NULL_HANDLE;
}
}
class StagingBufferCollection
{
public:
StagingBufferCollection() { }
~StagingBufferCollection();
bool AcquireBuffer(VkDeviceSize size, VkBuffer& outBuffer, void*& outMappedPtr);
void ReleaseAllBuffers();
private:
static const VkDeviceSize MAX_TOTAL_SIZE = 256ull * 1024 * 1024;
struct BufInfo
{
VmaAllocation Allocation = VK_NULL_HANDLE;
VkBuffer Buffer = VK_NULL_HANDLE;
VkDeviceSize Size = VK_WHOLE_SIZE;
void* MappedPtr = nullptr;
bool Used = false;
};
std::vector<BufInfo> m_Bufs;
VkDeviceSize m_TotalSize = 0;
};
StagingBufferCollection::~StagingBufferCollection()
{
for(size_t i = m_Bufs.size(); i--; )
{
vmaDestroyBuffer(g_hAllocator, m_Bufs[i].Buffer, m_Bufs[i].Allocation);
}
}
bool StagingBufferCollection::AcquireBuffer(VkDeviceSize size, VkBuffer& outBuffer, void*& outMappedPtr)
{
assert(size <= MAX_TOTAL_SIZE);
size_t bestIndex = SIZE_MAX;
for(size_t i = 0, count = m_Bufs.size(); i < count; ++i)
{
BufInfo& currBufInfo = m_Bufs[i];
if(!currBufInfo.Used && currBufInfo.Size >= size &&
(bestIndex == SIZE_MAX || currBufInfo.Size < m_Bufs[bestIndex].Size))
{
bestIndex = i;
}
}
if(bestIndex != SIZE_MAX)
{
m_Bufs[bestIndex].Used = true;
outBuffer = m_Bufs[bestIndex].Buffer;
outMappedPtr = m_Bufs[bestIndex].MappedPtr;
return true;
}
if(m_TotalSize + size <= MAX_TOTAL_SIZE)
{
BufInfo bufInfo;
bufInfo.Size = size;
bufInfo.Used = true;
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = size;
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
VmaAllocationInfo allocInfo;
VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo);
bufInfo.MappedPtr = allocInfo.pMappedData;
TEST(res == VK_SUCCESS && bufInfo.MappedPtr);
outBuffer = bufInfo.Buffer;
outMappedPtr = bufInfo.MappedPtr;
m_Bufs.push_back(std::move(bufInfo));
m_TotalSize += size;
return true;
}
bool hasUnused = false;
for(size_t i = 0, count = m_Bufs.size(); i < count; ++i)
{
if(!m_Bufs[i].Used)
{
hasUnused = true;
break;
}
}
if(hasUnused)
{
for(size_t i = m_Bufs.size(); i--; )
{
if(!m_Bufs[i].Used)
{
m_TotalSize -= m_Bufs[i].Size;
vmaDestroyBuffer(g_hAllocator, m_Bufs[i].Buffer, m_Bufs[i].Allocation);
m_Bufs.erase(m_Bufs.begin() + i);
}
}
return AcquireBuffer(size, outBuffer, outMappedPtr);
}
return false;
}
void StagingBufferCollection::ReleaseAllBuffers()
{
for(size_t i = 0, count = m_Bufs.size(); i < count; ++i)
{
m_Bufs[i].Used = false;
}
}
static void UploadGpuData(const AllocInfo* allocInfo, size_t allocInfoCount)
{
StagingBufferCollection stagingBufs;
bool cmdBufferStarted = false;
for(size_t allocInfoIndex = 0; allocInfoIndex < allocInfoCount; ++allocInfoIndex)
{
const AllocInfo& currAllocInfo = allocInfo[allocInfoIndex];
if(currAllocInfo.m_Buffer)
{
const VkDeviceSize size = currAllocInfo.m_BufferInfo.size;
VkBuffer stagingBuf = VK_NULL_HANDLE;
void* stagingBufMappedPtr = nullptr;
if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr))
{
TEST(cmdBufferStarted);
EndSingleTimeCommands();
stagingBufs.ReleaseAllBuffers();
cmdBufferStarted = false;
bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr);
TEST(ok);
}
{
assert(size % sizeof(uint32_t) == 0);
uint32_t* stagingValPtr = (uint32_t*)stagingBufMappedPtr;
uint32_t val = currAllocInfo.m_StartValue;
for(size_t i = 0; i < size / sizeof(uint32_t); ++i)
{
*stagingValPtr = val;
++stagingValPtr;
++val;
}
}
if(!cmdBufferStarted)
{
cmdBufferStarted = true;
BeginSingleTimeCommands();
}
VkBufferCopy copy = {};
copy.srcOffset = 0;
copy.dstOffset = 0;
copy.size = size;
vkCmdCopyBuffer(g_hTemporaryCommandBuffer, stagingBuf, currAllocInfo.m_Buffer, 1, ©);
}
else
{
TEST(currAllocInfo.m_ImageInfo.format == VK_FORMAT_R8G8B8A8_UNORM && "Only RGBA8 images are currently supported.");
TEST(currAllocInfo.m_ImageInfo.mipLevels == 1 && "Only single mip images are currently supported.");
const VkDeviceSize size = (VkDeviceSize)currAllocInfo.m_ImageInfo.extent.width * currAllocInfo.m_ImageInfo.extent.height * sizeof(uint32_t);
VkBuffer stagingBuf = VK_NULL_HANDLE;
void* stagingBufMappedPtr = nullptr;
if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr))
{
TEST(cmdBufferStarted);
EndSingleTimeCommands();
stagingBufs.ReleaseAllBuffers();
cmdBufferStarted = false;
bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr);
TEST(ok);
}
{
assert(size % sizeof(uint32_t) == 0);
uint32_t *stagingValPtr = (uint32_t *)stagingBufMappedPtr;
uint32_t val = currAllocInfo.m_StartValue;
for(size_t i = 0; i < size / sizeof(uint32_t); ++i)
{
*stagingValPtr = val;
++stagingValPtr;
++val;
}
}
if(!cmdBufferStarted)
{
cmdBufferStarted = true;
BeginSingleTimeCommands();
}
VkImageSubresourceRange subresourceRange = {
VK_IMAGE_ASPECT_COLOR_BIT,
0, VK_REMAINING_MIP_LEVELS,
0, VK_REMAINING_ARRAY_LAYERS
};
VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
barrier.srcAccessMask = 0;
barrier.dstAccessMask = 0;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = currAllocInfo.m_Image;
barrier.subresourceRange = subresourceRange;
vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0,
0, nullptr,
0, nullptr,
1, &barrier);
VkBufferImageCopy copy = {};
copy.bufferOffset = 0;
copy.bufferRowLength = 0;
copy.bufferImageHeight = 0;
copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy.imageSubresource.layerCount = 1;
copy.imageExtent = currAllocInfo.m_ImageInfo.extent;
vkCmdCopyBufferToImage(g_hTemporaryCommandBuffer, stagingBuf, currAllocInfo.m_Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©);
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = currAllocInfo.m_ImageLayout;
vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0,
0, nullptr,
0, nullptr,
1, &barrier);
}
}
if(cmdBufferStarted)
{
EndSingleTimeCommands();
stagingBufs.ReleaseAllBuffers();
}
}
static void ValidateGpuData(const AllocInfo* allocInfo, size_t allocInfoCount)
{
StagingBufferCollection stagingBufs;
bool cmdBufferStarted = false;
size_t validateAllocIndexOffset = 0;
std::vector<void*> validateStagingBuffers;
for(size_t allocInfoIndex = 0; allocInfoIndex < allocInfoCount; ++allocInfoIndex)
{
const AllocInfo& currAllocInfo = allocInfo[allocInfoIndex];
if(currAllocInfo.m_Buffer)
{
const VkDeviceSize size = currAllocInfo.m_BufferInfo.size;
VkBuffer stagingBuf = VK_NULL_HANDLE;
void* stagingBufMappedPtr = nullptr;
if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr))
{
TEST(cmdBufferStarted);
EndSingleTimeCommands();
cmdBufferStarted = false;
for(size_t validateIndex = 0;
validateIndex < validateStagingBuffers.size();
++validateIndex)
{
const size_t validateAllocIndex = validateIndex + validateAllocIndexOffset;
const VkDeviceSize validateSize = allocInfo[validateAllocIndex].m_BufferInfo.size;
TEST(validateSize % sizeof(uint32_t) == 0);
const uint32_t* stagingValPtr = (const uint32_t*)validateStagingBuffers[validateIndex];
uint32_t val = allocInfo[validateAllocIndex].m_StartValue;
bool valid = true;
for(size_t i = 0; i < validateSize / sizeof(uint32_t); ++i)
{
if(*stagingValPtr != val)
{
valid = false;
break;
}
++stagingValPtr;
++val;
}
TEST(valid);
}
stagingBufs.ReleaseAllBuffers();
validateAllocIndexOffset = allocInfoIndex;
validateStagingBuffers.clear();
bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr);
TEST(ok);
}
if(!cmdBufferStarted)
{
cmdBufferStarted = true;
BeginSingleTimeCommands();
}
VkBufferCopy copy = {};
copy.srcOffset = 0;
copy.dstOffset = 0;
copy.size = size;
vkCmdCopyBuffer(g_hTemporaryCommandBuffer, currAllocInfo.m_Buffer, stagingBuf, 1, ©);
validateStagingBuffers.push_back(stagingBufMappedPtr);
}
else
{
TEST(0 && "Images not currently supported.");
}
}
if(cmdBufferStarted)
{
EndSingleTimeCommands();
for(size_t validateIndex = 0;
validateIndex < validateStagingBuffers.size();
++validateIndex)
{
const size_t validateAllocIndex = validateIndex + validateAllocIndexOffset;
const VkDeviceSize validateSize = allocInfo[validateAllocIndex].m_BufferInfo.size;
TEST(validateSize % sizeof(uint32_t) == 0);
const uint32_t* stagingValPtr = (const uint32_t*)validateStagingBuffers[validateIndex];
uint32_t val = allocInfo[validateAllocIndex].m_StartValue;
bool valid = true;
for(size_t i = 0; i < validateSize / sizeof(uint32_t); ++i)
{
if(*stagingValPtr != val)
{
valid = false;
break;
}
++stagingValPtr;
++val;
}
TEST(valid);
}
stagingBufs.ReleaseAllBuffers();
}
}
static void GetMemReq(VmaAllocationCreateInfo& outMemReq)
{
outMemReq = {};
outMemReq.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
}
static void CreateBuffer(
VmaAllocationCreateInfo allocCreateInfo,
const VkBufferCreateInfo& bufCreateInfo,
bool persistentlyMapped,
AllocInfo& outAllocInfo)
{
outAllocInfo = {};
outAllocInfo.m_BufferInfo = bufCreateInfo;
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) );
{
outAllocInfo.m_StartValue = (uint32_t)rand();
uint32_t* data = (uint32_t*)vmaAllocInfo.pMappedData;
TEST((data != nullptr) == persistentlyMapped);
if(!persistentlyMapped)
{
ERR_GUARD_VULKAN( vmaMapMemory(g_hAllocator, outAllocInfo.m_Allocation, (void**)&data) );
}
uint32_t value = outAllocInfo.m_StartValue;
TEST(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);
}
}
void CreateImage(
VmaAllocationCreateInfo allocCreateInfo,
const VkImageCreateInfo& imgCreateInfo,
VkImageLayout dstLayout,
bool persistentlyMapped,
AllocInfo& outAllocInfo)
{
if (persistentlyMapped)
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
outAllocInfo.CreateImage(imgCreateInfo, allocCreateInfo, dstLayout);
if (dstLayout != imgCreateInfo.initialLayout)
{
BeginSingleTimeCommands();
VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.oldLayout = imgCreateInfo.initialLayout;
barrier.newLayout = dstLayout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = outAllocInfo.m_Image;
barrier.subresourceRange =
{
VK_IMAGE_ASPECT_COLOR_BIT,
0, VK_REMAINING_MIP_LEVELS,
0, VK_REMAINING_ARRAY_LAYERS
};
vkCmdPipelineBarrier(g_hTemporaryCommandBuffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
0, nullptr, 0, nullptr, 1, &barrier);
EndSingleTimeCommands();
}
}
static void CreateAllocation(AllocInfo& outAllocation)
{
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; const bool isLarge = (rand() % 16) == 0;
if(isBuffer)
{
const uint32_t bufferSize = isLarge ?
(rand() % 10 + 1) * (1024 * 1024) : (rand() % 1024 + 1) * 1024;
VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufferInfo.size = bufferSize;
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VkResult res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &vmaMemReq, &outAllocation.m_Buffer, &outAllocation.m_Allocation, &allocInfo);
outAllocation.m_BufferInfo = bufferInfo;
TEST(res == VK_SUCCESS);
}
else
{
const uint32_t imageSizeX = isLarge ?
1024 + rand() % (4096 - 1024) : rand() % 1024 + 1; const uint32_t imageSizeY = isLarge ?
1024 + rand() % (4096 - 1024) : rand() % 1024 + 1;
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 | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
VkResult res = vmaCreateImage(g_hAllocator, &imageInfo, &vmaMemReq, &outAllocation.m_Image, &outAllocation.m_Allocation, &allocInfo);
outAllocation.m_ImageInfo = imageInfo;
TEST(res == VK_SUCCESS);
}
uint32_t* data = (uint32_t*)allocInfo.pMappedData;
if(allocInfo.pMappedData == nullptr)
{
VkResult res = vmaMapMemory(g_hAllocator, outAllocation.m_Allocation, (void**)&data);
TEST(res == VK_SUCCESS);
}
uint32_t value = outAllocation.m_StartValue;
TEST(allocInfo.size % 4 == 0);
for(size_t i = 0; i < allocInfo.size / sizeof(uint32_t); ++i)
data[i] = value++;
if(allocInfo.pMappedData == nullptr)
vmaUnmapMemory(g_hAllocator, 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<AllocInfo>& 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);
TEST(res == VK_SUCCESS);
}
uint32_t value = allocation.m_StartValue;
bool ok = true;
if(allocation.m_Buffer)
{
TEST(allocInfo.size % 4 == 0);
for(size_t i = 0; i < allocInfo.size / sizeof(uint32_t); ++i)
{
if(data[i] != value++)
{
ok = false;
break;
}
}
}
else
{
TEST(allocation.m_Image);
}
TEST(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, g_Allocs);
VkResult res = vkCreateBuffer(g_hDevice, &allocation.m_BufferInfo, g_Allocs, &allocation.m_Buffer);
TEST(res == VK_SUCCESS);
VkMemoryRequirements vkMemReq;
vkGetBufferMemoryRequirements(g_hDevice, allocation.m_Buffer, &vkMemReq);
TEST(vkMemReq.size >= allocation.m_BufferInfo.size);
res = vmaBindBufferMemory(g_hAllocator, allocation.m_Allocation, allocation.m_Buffer);
TEST(res == VK_SUCCESS);
}
else
{
vkDestroyImage(g_hDevice, allocation.m_Image, g_Allocs);
VkResult res = vkCreateImage(g_hDevice, &allocation.m_ImageInfo, g_Allocs, &allocation.m_Image);
TEST(res == VK_SUCCESS);
VkMemoryRequirements vkMemReq;
vkGetImageMemoryRequirements(g_hDevice, allocation.m_Image, &vkMemReq);
res = vmaBindImageMemory(g_hAllocator, allocation.m_Allocation, allocation.m_Image);
TEST(res == VK_SUCCESS);
}
}
static void ProcessDefragmentationPass(VmaDefragmentationPassMoveInfo& stepInfo)
{
std::vector<VkImageMemoryBarrier> beginImageBarriers;
std::vector<VkImageMemoryBarrier> finalizeImageBarriers;
VkPipelineStageFlags beginSrcStageMask = 0;
VkPipelineStageFlags beginDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
VkPipelineStageFlags finalizeSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
VkPipelineStageFlags finalizeDstStageMask = 0;
bool wantsMemoryBarrier = false;
VkMemoryBarrier beginMemoryBarrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER };
VkMemoryBarrier finalizeMemoryBarrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER };
for (uint32_t i = 0; i < stepInfo.moveCount; ++i)
{
if (stepInfo.pMoves[i].operation == VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY)
{
VmaAllocationInfo info;
vmaGetAllocationInfo(g_hAllocator, stepInfo.pMoves[i].srcAllocation, &info);
AllocInfo* allocInfo = (AllocInfo*)info.pUserData;
if (allocInfo->m_Image)
{
VkImage newImage;
const VkResult result = vkCreateImage(g_hDevice, &allocInfo->m_ImageInfo, g_Allocs, &newImage);
TEST(result >= VK_SUCCESS);
vmaBindImageMemory(g_hAllocator, stepInfo.pMoves[i].dstTmpAllocation, newImage);
allocInfo->m_NewImage = newImage;
beginSrcStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
finalizeDstStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkImageSubresourceRange subresourceRange = {
VK_IMAGE_ASPECT_COLOR_BIT,
0, VK_REMAINING_MIP_LEVELS,
0, VK_REMAINING_ARRAY_LAYERS
};
VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = newImage;
barrier.subresourceRange = subresourceRange;
beginImageBarriers.push_back(barrier);
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.oldLayout = allocInfo->m_ImageLayout;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier.image = allocInfo->m_Image;
beginImageBarriers.push_back(barrier);
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = allocInfo->m_ImageLayout;
barrier.image = newImage;
finalizeImageBarriers.push_back(barrier);
}
else if (allocInfo->m_Buffer)
{
VkBuffer newBuffer;
const VkResult result = vkCreateBuffer(g_hDevice, &allocInfo->m_BufferInfo, g_Allocs, &newBuffer);
TEST(result >= VK_SUCCESS);
vmaBindBufferMemory(g_hAllocator, stepInfo.pMoves[i].dstTmpAllocation, newBuffer);
allocInfo->m_NewBuffer = newBuffer;
beginSrcStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
finalizeDstStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
beginMemoryBarrier.srcAccessMask |= VK_ACCESS_MEMORY_WRITE_BIT;
beginMemoryBarrier.dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT;
finalizeMemoryBarrier.srcAccessMask |= VK_ACCESS_TRANSFER_WRITE_BIT;
finalizeMemoryBarrier.dstAccessMask |= VK_ACCESS_MEMORY_READ_BIT;
wantsMemoryBarrier = true;
}
}
}
if (!beginImageBarriers.empty() || wantsMemoryBarrier)
{
const uint32_t memoryBarrierCount = wantsMemoryBarrier ? 1 : 0;
vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, beginSrcStageMask, beginDstStageMask, 0,
memoryBarrierCount, &beginMemoryBarrier,
0, nullptr,
(uint32_t)beginImageBarriers.size(), beginImageBarriers.data());
}
for (uint32_t i = 0; i < stepInfo.moveCount; ++i)
{
if (stepInfo.pMoves[i].operation == VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY)
{
VmaAllocationInfo info;
vmaGetAllocationInfo(g_hAllocator, stepInfo.pMoves[i].srcAllocation, &info);
AllocInfo* allocInfo = (AllocInfo*)info.pUserData;
if (allocInfo->m_Image)
{
std::vector<VkImageCopy> imageCopies;
VkOffset3D offset = { 0, 0, 0 };
VkExtent3D extent = allocInfo->m_ImageInfo.extent;
VkImageSubresourceLayers subresourceLayers = {
VK_IMAGE_ASPECT_COLOR_BIT,
0,
0, 1
};
for (uint32_t mip = 0; mip < allocInfo->m_ImageInfo.mipLevels; ++mip)
{
subresourceLayers.mipLevel = mip;
VkImageCopy imageCopy{
subresourceLayers,
offset,
subresourceLayers,
offset,
extent
};
imageCopies.push_back(imageCopy);
extent.width = std::max(uint32_t(1), extent.width >> 1);
extent.height = std::max(uint32_t(1), extent.height >> 1);
extent.depth = std::max(uint32_t(1), extent.depth >> 1);
}
vkCmdCopyImage(
g_hTemporaryCommandBuffer,
allocInfo->m_Image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
allocInfo->m_NewImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
(uint32_t)imageCopies.size(), imageCopies.data());
}
else if (allocInfo->m_Buffer)
{
VkBufferCopy region = {
0,
0,
allocInfo->m_BufferInfo.size };
vkCmdCopyBuffer(g_hTemporaryCommandBuffer,
allocInfo->m_Buffer, allocInfo->m_NewBuffer,
1, ®ion);
}
}
}
if (!finalizeImageBarriers.empty() || wantsMemoryBarrier)
{
const uint32_t memoryBarrierCount = wantsMemoryBarrier ? 1 : 0;
vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, finalizeSrcStageMask, finalizeDstStageMask, 0,
memoryBarrierCount, &finalizeMemoryBarrier,
0, nullptr,
(uint32_t)finalizeImageBarriers.size(), finalizeImageBarriers.data());
}
}
static void Defragment(VmaDefragmentationInfo& defragmentationInfo,
VmaDefragmentationStats* defragmentationStats = nullptr)
{
VmaDefragmentationContext defragCtx = nullptr;
VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragmentationInfo, &defragCtx);
TEST(res == VK_SUCCESS);
VmaDefragmentationPassMoveInfo pass = {};
while ((res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_INCOMPLETE)
{
BeginSingleTimeCommands();
ProcessDefragmentationPass(pass);
EndSingleTimeCommands();
for(size_t i = 0; i < pass.moveCount; ++i)
{
VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
VmaAllocationInfo vmaAllocInfo;
vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
if(allocInfo->m_Buffer)
{
assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
allocInfo->m_Buffer = allocInfo->m_NewBuffer;
allocInfo->m_NewBuffer = VK_NULL_HANDLE;
}
else if(allocInfo->m_Image)
{
assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer);
vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs);
allocInfo->m_Image = allocInfo->m_NewImage;
allocInfo->m_NewImage = VK_NULL_HANDLE;
}
else
assert(0);
}
if ((res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_SUCCESS)
break;
TEST(res == VK_INCOMPLETE);
}
TEST(res == VK_SUCCESS);
vmaEndDefragmentation(g_hAllocator, defragCtx, defragmentationStats);
}
static void ValidateAllocationsData(const AllocInfo* allocs, size_t allocCount)
{
std::for_each(allocs, allocs + allocCount, [](const AllocInfo& allocInfo) {
ValidateAllocationData(allocInfo);
});
}
static void TestJson()
{
wprintf(L"Test JSON\n");
std::vector<VmaPool> pools;
std::vector<VmaAllocation> allocs;
VmaAllocationCreateInfo allocCreateInfo = {};
VkBufferCreateInfo buffCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
buffCreateInfo.size = 1024;
buffCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imgCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imgCreateInfo.extent.depth = 1;
imgCreateInfo.mipLevels = 1;
imgCreateInfo.arrayLayers = 1;
imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
VkMemoryRequirements memReq = {};
{
VkBuffer dummyBuffer = VK_NULL_HANDLE;
TEST(vkCreateBuffer(g_hDevice, &buffCreateInfo, g_Allocs, &dummyBuffer) == VK_SUCCESS && dummyBuffer);
vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs);
}
for (uint8_t poolType = 0; poolType < 2; ++poolType)
{
for (uint8_t memType = 0; memType < 2; ++memType)
{
switch (memType)
{
case 0:
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DONT_BIND_BIT;
break;
case 1:
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DONT_BIND_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
break;
}
switch (poolType)
{
case 0:
allocCreateInfo.pool = nullptr;
break;
case 1:
{
VmaPoolCreateInfo poolCreateInfo = {};
TEST(vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &buffCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex) == VK_SUCCESS);
VmaPool pool;
TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
allocCreateInfo.pool = pool;
pools.emplace_back(pool);
break;
}
}
for (uint8_t allocFlag = 0; allocFlag < 2; ++allocFlag)
{
switch (allocFlag)
{
case 1:
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
break;
}
for (uint8_t allocType = 0; allocType < 4; ++allocType)
{
for (uint8_t data = 0; data < 4; ++data)
{
VmaAllocation alloc = nullptr;
switch (allocType)
{
case 0:
{
VmaAllocationCreateInfo localCreateInfo = allocCreateInfo;
switch (memType)
{
case 0:
localCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
break;
case 1:
localCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
break;
}
TEST(vmaAllocateMemory(g_hAllocator, &memReq, &localCreateInfo, &alloc, nullptr) == VK_SUCCESS);
break;
}
case 1:
{
VkBuffer buffer;
TEST(vmaCreateBuffer(g_hAllocator, &buffCreateInfo, &allocCreateInfo, &buffer, &alloc, nullptr) == VK_SUCCESS);
vkDestroyBuffer(g_hDevice, buffer, g_Allocs);
break;
}
case 2:
{
imgCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
imgCreateInfo.extent.width = 512;
imgCreateInfo.extent.height = 1;
VkImage image;
TEST(vmaCreateImage(g_hAllocator, &imgCreateInfo, &allocCreateInfo, &image, &alloc, nullptr) == VK_SUCCESS);
vkDestroyImage(g_hDevice, image, g_Allocs);
break;
}
case 3:
{
imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imgCreateInfo.extent.width = 1024;
imgCreateInfo.extent.height = 512;
VkImage image;
TEST(vmaCreateImage(g_hAllocator, &imgCreateInfo, &allocCreateInfo, &image, &alloc, nullptr) == VK_SUCCESS);
vkDestroyImage(g_hDevice, image, g_Allocs);
break;
}
}
switch (data)
{
case 1:
vmaSetAllocationUserData(g_hAllocator, alloc, (void*)16112007);
break;
case 2:
vmaSetAllocationName(g_hAllocator, alloc, "SHEPURD");
break;
case 3:
vmaSetAllocationUserData(g_hAllocator, alloc, (void*)26012010);
vmaSetAllocationName(g_hAllocator, alloc, "JOKER");
break;
}
allocs.emplace_back(alloc);
}
}
}
}
}
SaveAllocatorStatsToFile(L"JSON_VULKAN.json");
for (auto& alloc : allocs)
vmaFreeMemory(g_hAllocator, alloc);
for (auto& pool : pools)
vmaDestroyPool(g_hAllocator, pool);
}
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<VkDeviceSize>(rand.Generate() % (MAX_BUF_SIZE - MIN_BUF_SIZE + 1) + MIN_BUF_SIZE, 64);
};
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = BUF_SIZE;
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
uint32_t memTypeIndex = UINT32_MAX;
vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &memTypeIndex);
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.blockSize = BLOCK_SIZE;
poolCreateInfo.memoryTypeIndex = memTypeIndex;
VmaPool pool;
TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
allocCreateInfo.pool = pool;
VmaDefragmentationInfo defragInfo = {};
defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT;
defragInfo.pool = pool;
{
VmaDefragmentationContext defragCtx = nullptr;
VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &defragCtx);
TEST(res == VK_SUCCESS);
VmaDefragmentationPassMoveInfo pass = {};
res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass);
TEST(res == VK_SUCCESS);
VmaDefragmentationStats defragStats = {};
vmaEndDefragmentation(g_hAllocator, defragCtx, &defragStats);
TEST(defragStats.allocationsMoved == 0 && defragStats.bytesFreed == 0 &&
defragStats.bytesMoved == 0 && defragStats.deviceMemoryBlocksFreed == 0);
}
std::vector<AllocInfo> allocations;
for (uint32_t persistentlyMappedOption = 0; persistentlyMappedOption < 2; ++persistentlyMappedOption)
{
wprintf(L" Persistently mapped option = %u\n", persistentlyMappedOption);
const bool persistentlyMapped = persistentlyMappedOption != 0;
{
for (size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i)
{
AllocInfo allocInfo;
CreateBuffer(allocCreateInfo, bufCreateInfo, persistentlyMapped, allocInfo);
allocations.push_back(allocInfo);
}
for (size_t i = 1; i < allocations.size(); ++i)
{
DestroyAllocation(allocations[i]);
allocations.erase(allocations.begin() + i);
}
for (auto& alloc : allocations)
vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
VmaDefragmentationStats defragStats;
Defragment(defragInfo, &defragStats);
TEST(defragStats.allocationsMoved == 4 && defragStats.bytesMoved == 4 * BUF_SIZE);
ValidateAllocationsData(allocations.data(), allocations.size());
DestroyAllAllocations(allocations);
}
{
for (size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i)
{
AllocInfo allocInfo;
CreateBuffer(allocCreateInfo, bufCreateInfo, persistentlyMapped, allocInfo);
allocations.push_back(allocInfo);
}
for (size_t i = 1; i < allocations.size(); ++i)
{
DestroyAllocation(allocations[i]);
allocations.erase(allocations.begin() + i);
}
for (auto& alloc : allocations)
vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
defragInfo.maxAllocationsPerPass = 1;
defragInfo.maxBytesPerPass = BUF_SIZE;
VmaDefragmentationContext defragCtx = nullptr;
VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &defragCtx);
TEST(res == VK_SUCCESS);
for (size_t i = 0; i < BLOCK_SIZE / BUF_SIZE / 2; ++i)
{
VmaDefragmentationPassMoveInfo pass = {};
res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass);
TEST(res == VK_INCOMPLETE);
BeginSingleTimeCommands();
ProcessDefragmentationPass(pass);
EndSingleTimeCommands();
for (size_t i = 0; i < pass.moveCount; ++i)
{
VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
VmaAllocationInfo vmaAllocInfo;
vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
if (allocInfo->m_Buffer)
{
assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
allocInfo->m_Buffer = allocInfo->m_NewBuffer;
allocInfo->m_NewBuffer = VK_NULL_HANDLE;
}
else if (allocInfo->m_Image)
{
assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer);
vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs);
allocInfo->m_Image = allocInfo->m_NewImage;
allocInfo->m_NewImage = VK_NULL_HANDLE;
}
else
assert(0);
}
res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &pass);
TEST(res == VK_INCOMPLETE);
}
VmaDefragmentationStats defragStats = {};
vmaEndDefragmentation(g_hAllocator, defragCtx, &defragStats);
TEST(defragStats.allocationsMoved == 4 && defragStats.bytesMoved == 4 * BUF_SIZE);
ValidateAllocationsData(allocations.data(), allocations.size());
DestroyAllAllocations(allocations);
}
{
for (size_t i = 0; i < 100; ++i)
{
VkBufferCreateInfo localBufCreateInfo = bufCreateInfo;
localBufCreateInfo.size = RandomBufSize();
AllocInfo allocInfo;
CreateBuffer(allocCreateInfo, localBufCreateInfo, 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);
}
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]);
}
for (auto& alloc : allocations)
vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
defragInfo.maxAllocationsPerPass = 0;
defragInfo.maxBytesPerPass = 0;
VmaDefragmentationContext defragCtx = nullptr;
VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &defragCtx);
TEST(res == VK_SUCCESS);
VmaDefragmentationPassMoveInfo pass = {};
while ((res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_INCOMPLETE)
{
VmaDefragmentationMove* end = pass.pMoves + pass.moveCount;
for (uint32_t i = 0; i < numberNonMovable; ++i)
{
VmaDefragmentationMove* move = std::find_if(pass.pMoves, end, [&](VmaDefragmentationMove& move) { return move.srcAllocation == allocations[i].m_Allocation; });
if (move != end)
move->operation = VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE;
}
BeginSingleTimeCommands();
ProcessDefragmentationPass(pass);
EndSingleTimeCommands();
for (size_t i = 0; i < pass.moveCount; ++i)
{
if (pass.pMoves[i].operation != VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE)
{
VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
VmaAllocationInfo vmaAllocInfo;
vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
if (allocInfo->m_Buffer)
{
assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
allocInfo->m_Buffer = allocInfo->m_NewBuffer;
allocInfo->m_NewBuffer = VK_NULL_HANDLE;
}
else
assert(0);
}
}
if ((res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_SUCCESS)
break;
TEST(res == VK_INCOMPLETE);
}
TEST(res == VK_SUCCESS);
VmaDefragmentationStats defragStats;
vmaEndDefragmentation(g_hAllocator, defragCtx, &defragStats);
ValidateAllocationsData(allocations.data(), allocations.size());
DestroyAllAllocations(allocations);
}
}
vmaDestroyPool(g_hAllocator, pool);
}
void TestDefragmentationVsMapping()
{
wprintf(L"Test defragmentation vs mapping\n");
VkBufferCreateInfo bufCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
bufCreateInfo.size = 64 * KILOBYTE;
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo dummyAllocCreateInfo = {};
dummyAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
dummyAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.flags = VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT;
poolCreateInfo.blockSize = 1 * MEGABYTE;
TEST(vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &dummyAllocCreateInfo, &poolCreateInfo.memoryTypeIndex)
== VK_SUCCESS);
VmaPool pool = VK_NULL_HANDLE;
TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
RandomNumberGenerator rand{2355762};
constexpr uint32_t START_ALLOC_COUNT = 160;
std::vector<AllocInfo> allocs{START_ALLOC_COUNT};
constexpr uint32_t RAND_NUM_PERSISTENTLY_MAPPED_BIT = 0x1000;
constexpr uint32_t RAND_NUM_MANUAL_MAP_COUNT_MASK = 0x3;
{
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
for(size_t allocIndex = 0; allocIndex < START_ALLOC_COUNT; ++allocIndex)
{
const uint32_t randNum = rand.Generate();
if(randNum & RAND_NUM_PERSISTENTLY_MAPPED_BIT)
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
else
allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT;
allocs[allocIndex].CreateBuffer(bufCreateInfo, allocCreateInfo);
vmaSetAllocationUserData(g_hAllocator, allocs[allocIndex].m_Allocation, (void*)(uintptr_t)randNum);
}
}
for(uint32_t i = 0; i < START_ALLOC_COUNT * 2 / 3; ++i)
{
const uint32_t allocIndexToRemove = rand.Generate() % allocs.size();
allocs[allocIndexToRemove].Destroy();
allocs.erase(allocs.begin() + allocIndexToRemove);
}
for(size_t allocIndex = 0, allocCount = allocs.size(); allocIndex < allocCount; ++allocIndex)
{
VmaAllocationInfo allocInfo;
vmaGetAllocationInfo(g_hAllocator, allocs[allocIndex].m_Allocation, &allocInfo);
const uint32_t randNum = (uint32_t)(uintptr_t)allocInfo.pUserData;
const uint32_t mapCount = randNum & RAND_NUM_MANUAL_MAP_COUNT_MASK;
for(uint32_t mapIndex = 0; mapIndex < mapCount; ++mapIndex)
{
void* ptr;
TEST(vmaMapMemory(g_hAllocator, allocs[allocIndex].m_Allocation, &ptr) == VK_SUCCESS);
TEST(ptr != nullptr);
}
}
{
VmaDefragmentationInfo defragInfo = {};
defragInfo.pool = pool;
defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT;
VmaDefragmentationContext defragCtx;
TEST(vmaBeginDefragmentation(g_hAllocator, &defragInfo, &defragCtx) == VK_SUCCESS);
for(uint32_t passIndex = 0; ; ++passIndex)
{
VmaDefragmentationPassMoveInfo passInfo = {};
VkResult res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &passInfo);
if(res == VK_SUCCESS)
break;
TEST(res == VK_INCOMPLETE);
wprintf(L" Pass %u moving %u allocations\n", passIndex, passInfo.moveCount);
for(uint32_t moveIndex = 0; moveIndex < passInfo.moveCount; ++moveIndex)
{
if(rand.Generate() % 5 == 0)
passInfo.pMoves[moveIndex].operation = VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE;
}
res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &passInfo);
if(res == VK_SUCCESS)
break;
TEST(res == VK_INCOMPLETE);
}
VmaDefragmentationStats defragStats = {};
vmaEndDefragmentation(g_hAllocator, defragCtx, &defragStats);
wprintf(L" Defragmentation: moved %u allocations, %llu B, freed %u memory blocks, %llu B\n",
defragStats.allocationsMoved, defragStats.bytesMoved,
defragStats.deviceMemoryBlocksFreed, defragStats.bytesFreed);
TEST(defragStats.allocationsMoved > 0 && defragStats.bytesMoved > 0);
TEST(defragStats.deviceMemoryBlocksFreed > 0 && defragStats.bytesFreed > 0);
}
for(size_t allocIndex = allocs.size(); allocIndex--; )
{
VmaAllocationInfo allocInfo;
vmaGetAllocationInfo(g_hAllocator, allocs[allocIndex].m_Allocation, &allocInfo);
const uint32_t randNum = (uint32_t)(uintptr_t)allocInfo.pUserData;
const bool isMapped = (randNum & (RAND_NUM_PERSISTENTLY_MAPPED_BIT | RAND_NUM_MANUAL_MAP_COUNT_MASK)) != 0;
TEST(isMapped == (allocInfo.pMappedData != nullptr));
const uint32_t mapCount = randNum & RAND_NUM_MANUAL_MAP_COUNT_MASK;
for(uint32_t mapIndex = 0; mapIndex < mapCount; ++mapIndex)
vmaUnmapMemory(g_hAllocator, allocs[allocIndex].m_Allocation);
}
for(size_t i = allocs.size(); i--; )
allocs[i].Destroy();
vmaDestroyPool(g_hAllocator, pool);
}
void TestDefragmentationAlgorithms()
{
wprintf(L"Test defragmentation simple\n");
RandomNumberGenerator rand(669);
const VkDeviceSize BUF_SIZE = 0x10000;
const uint32_t TEX_SIZE = 256;
const VkDeviceSize BLOCK_SIZE = BUF_SIZE * 200 + TEX_SIZE * 200;
const VkDeviceSize MIN_BUF_SIZE = 2048;
const VkDeviceSize MAX_BUF_SIZE = BUF_SIZE * 4;
auto RandomBufSize = [&]() -> VkDeviceSize
{
return align_up<VkDeviceSize>(rand.Generate() % (MAX_BUF_SIZE - MIN_BUF_SIZE + 1) + MIN_BUF_SIZE, 64);
};
const uint32_t MIN_TEX_SIZE = 512;
const uint32_t MAX_TEX_SIZE = TEX_SIZE * 4;
auto RandomTexSize = [&]() -> uint32_t
{
return align_up<uint32_t>(rand.Generate() % (MAX_TEX_SIZE - MIN_TEX_SIZE + 1) + MIN_TEX_SIZE, 64);
};
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = BUF_SIZE;
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VkImageCreateInfo imageCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imageCreateInfo.extent.depth = 1;
imageCreateInfo.mipLevels = 1;
imageCreateInfo.arrayLayers = 1;
imageCreateInfo.format = VK_FORMAT_R8_UNORM;
imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
uint32_t memTypeIndex = UINT32_MAX;
vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &memTypeIndex);
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.blockSize = BLOCK_SIZE;
poolCreateInfo.memoryTypeIndex = memTypeIndex;
VmaPool pool;
TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
allocCreateInfo.pool = pool;
VmaDefragmentationInfo defragInfo = {};
defragInfo.pool = pool;
std::vector<AllocInfo> allocations;
for (uint8_t i = 0; i < 4; ++i)
{
switch (i)
{
case 0:
defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT;
break;
case 1:
defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT;
break;
case 2:
defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT;
break;
case 3:
defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT;
break;
}
wprintf(L" Algorithm = %s\n", DefragmentationAlgorithmToStr(defragInfo.flags));
for (uint8_t j = 0; j < 2; ++j)
{
for (size_t i = 0; i < 400; ++i)
{
bufCreateInfo.size = RandomBufSize();
AllocInfo allocInfo;
CreateBuffer(allocCreateInfo, bufCreateInfo, false, allocInfo);
allocations.push_back(allocInfo);
}
for (size_t i = 0; i < 100; ++i)
{
imageCreateInfo.extent.width = RandomTexSize();
imageCreateInfo.extent.height = RandomTexSize();
AllocInfo allocInfo;
CreateImage(allocCreateInfo, imageCreateInfo, VK_IMAGE_LAYOUT_GENERAL, false, allocInfo);
allocations.push_back(allocInfo);
}
const uint32_t percentToDelete = 55;
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);
}
const uint32_t percentNonMovable = 20;
const size_t numberNonMovable = j == 0 ? 0 : (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]);
}
for (auto& alloc : allocations)
vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
std::wstring output = DefragmentationAlgorithmToStr(defragInfo.flags);
if (j == 0)
output += L"_NoMove";
else
output += L"_Move";
SaveAllocatorStatsToFile((output + L"_Before.json").c_str());
VmaDefragmentationContext defragCtx = nullptr;
VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &defragCtx);
TEST(res == VK_SUCCESS);
VmaDefragmentationPassMoveInfo pass = {};
while ((res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_INCOMPLETE)
{
VmaDefragmentationMove* end = pass.pMoves + pass.moveCount;
for (uint32_t i = 0; i < numberNonMovable; ++i)
{
VmaDefragmentationMove* move = std::find_if(pass.pMoves, end, [&](VmaDefragmentationMove& move) { return move.srcAllocation == allocations[i].m_Allocation; });
if (move != end)
move->operation = VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE;
}
for (uint32_t i = 0; i < pass.moveCount; ++i)
{
auto it = std::find_if(allocations.begin(), allocations.end(), [&](const AllocInfo& info) { return pass.pMoves[i].srcAllocation == info.m_Allocation; });
assert(it != allocations.end());
}
BeginSingleTimeCommands();
ProcessDefragmentationPass(pass);
EndSingleTimeCommands();
for (size_t i = 0; i < pass.moveCount; ++i)
{
if (pass.pMoves[i].operation == VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY)
{
VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
VmaAllocationInfo vmaAllocInfo;
vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
if (allocInfo->m_Buffer)
{
assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
allocInfo->m_Buffer = allocInfo->m_NewBuffer;
allocInfo->m_NewBuffer = VK_NULL_HANDLE;
}
else if (allocInfo->m_Image)
{
assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer);
vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs);
allocInfo->m_Image = allocInfo->m_NewImage;
allocInfo->m_NewImage = VK_NULL_HANDLE;
}
else
assert(0);
}
}
if ((res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_SUCCESS)
break;
TEST(res == VK_INCOMPLETE);
}
TEST(res == VK_SUCCESS);
VmaDefragmentationStats defragStats;
vmaEndDefragmentation(g_hAllocator, defragCtx, &defragStats);
SaveAllocatorStatsToFile((output + L"_After.json").c_str());
ValidateAllocationsData(allocations.data(), allocations.size());
DestroyAllAllocations(allocations);
}
}
vmaDestroyPool(g_hAllocator, pool);
}
void TestDefragmentationFull()
{
std::vector<AllocInfo> allocations;
for(size_t i = 0; i < 400; ++i)
{
AllocInfo allocation;
CreateAllocation(allocation);
allocations.push_back(allocation);
}
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);
}
{
std::vector<VmaAllocation> 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);
}
for (auto& alloc : allocations)
vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
const uint32_t defragCount = 1;
for(uint32_t defragIndex = 0; defragIndex < defragCount; ++defragIndex)
{
std::vector<VkBool32> allocationsChanged(vmaAllocations.size());
VmaDefragmentationInfo defragmentationInfo = {};
defragmentationInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT;
wprintf(L"Defragmentation #%u\n", defragIndex);
time_point begTime = std::chrono::high_resolution_clock::now();
VmaDefragmentationStats stats;
Defragment(defragmentationInfo, &stats);
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]);
}
}
}
}
DestroyAllAllocations(allocations);
}
static void TestDefragmentationGpu()
{
wprintf(L"Test defragmentation GPU\n");
std::vector<AllocInfo> allocations;
const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024;
const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024;
const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024;
const size_t bufCount = (size_t)(totalSize / bufSizeMin);
const size_t percentToLeave = 30;
const size_t percentNonMovable = 3;
RandomNumberGenerator rand = { 234522 };
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
for(size_t i = 0; i < bufCount; ++i)
{
bufCreateInfo.size = align_up(rand.Generate() % (bufSizeMax - bufSizeMin) + bufSizeMin, 32ull);
if(rand.Generate() % 100 < percentNonMovable)
{
bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
allocCreateInfo.pUserData = (void*)(uintptr_t)2;
}
else
{
bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
allocCreateInfo.pUserData = (void*)(uintptr_t)1;
}
AllocInfo alloc;
alloc.CreateBuffer(bufCreateInfo, allocCreateInfo);
alloc.m_StartValue = rand.Generate();
allocations.push_back(alloc);
}
{
const size_t buffersToDestroy = round_div<size_t>(bufCount * (100 - percentToLeave), 100);
for(size_t i = 0; i < buffersToDestroy; ++i)
{
const size_t index = rand.Generate() % allocations.size();
allocations[index].Destroy();
allocations.erase(allocations.begin() + index);
}
}
for (auto& alloc : allocations)
vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
UploadGpuData(allocations.data(), allocations.size());
wchar_t fileName[MAX_PATH];
swprintf_s(fileName, L"GPU_defragmentation_A_before.json");
SaveAllocatorStatsToFile(fileName);
{
const size_t allocCount = allocations.size();
std::vector<VmaAllocation> allocationPtrs;
std::vector<VkBool32> allocationChanged;
std::vector<size_t> allocationOriginalIndex;
for(size_t i = 0; i < allocCount; ++i)
{
VmaAllocationInfo allocInfo = {};
vmaGetAllocationInfo(g_hAllocator, allocations[i].m_Allocation, &allocInfo);
if((uintptr_t)allocInfo.pUserData == 1) {
allocationPtrs.push_back(allocations[i].m_Allocation);
allocationChanged.push_back(VK_FALSE);
allocationOriginalIndex.push_back(i);
}
}
const size_t movableAllocCount = allocationPtrs.size();
VmaDefragmentationInfo defragInfo = {};
VmaDefragmentationStats stats;
Defragment(defragInfo, &stats);
for(size_t i = 0; i < movableAllocCount; ++i)
{
if(allocationChanged[i])
{
const size_t origAllocIndex = allocationOriginalIndex[i];
RecreateAllocationResource(allocations[origAllocIndex]);
}
}
#if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0
TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0);
TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0);
#endif
}
swprintf_s(fileName, L"GPU_defragmentation_B_after.json");
SaveAllocatorStatsToFile(fileName);
ValidateGpuData(allocations.data(), allocations.size());
for(size_t i = allocations.size(); i--; )
{
allocations[i].Destroy();
}
}
static void TestDefragmentationIncrementalBasic()
{
wprintf(L"Test defragmentation incremental basic\n");
std::vector<AllocInfo> allocations;
const std::array<uint32_t, 3> imageSizes = { 256, 512, 1024 };
const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024;
const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024;
const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024;
const size_t imageCount = totalSize / ((size_t)imageSizes[0] * imageSizes[0] * 4) / 2;
const size_t bufCount = (size_t)(totalSize / bufSizeMin) / 2;
const size_t percentToLeave = 30;
RandomNumberGenerator rand = { 234522 };
VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
for(size_t i = 0; i < imageCount; ++i)
{
const uint32_t size = imageSizes[rand.Generate() % 3];
imageInfo.extent.width = size;
imageInfo.extent.height = size;
AllocInfo alloc;
CreateImage(allocCreateInfo, imageInfo, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, alloc);
alloc.m_StartValue = 0;
allocations.push_back(alloc);
}
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
for(size_t i = 0; i < bufCount; ++i)
{
bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16);
bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
AllocInfo alloc;
alloc.CreateBuffer(bufCreateInfo, allocCreateInfo);
alloc.m_StartValue = 0;
allocations.push_back(alloc);
}
{
const size_t allocationsToDestroy = round_div<size_t>((imageCount + bufCount) * (100 - percentToLeave), 100);
for(size_t i = 0; i < allocationsToDestroy; ++i)
{
const size_t index = rand.Generate() % allocations.size();
allocations[index].Destroy();
allocations.erase(allocations.begin() + index);
}
}
{
const size_t allocationCount = allocations.size();
for(size_t i = 0; i < allocationCount; ++i)
{
AllocInfo &alloc = allocations[i];
vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
}
}
UploadGpuData(allocations.data(), allocations.size());
wchar_t fileName[MAX_PATH];
swprintf_s(fileName, L"GPU_defragmentation_incremental_basic_A_before.json");
SaveAllocatorStatsToFile(fileName);
{
VmaDefragmentationInfo defragInfo = {};
VmaDefragmentationContext ctx = VK_NULL_HANDLE;
VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &ctx);
TEST(res == VK_SUCCESS);
VmaDefragmentationPassMoveInfo pass = {};
while ((res = vmaBeginDefragmentationPass(g_hAllocator, ctx, &pass)) == VK_INCOMPLETE)
{
for (uint32_t i = 0; i < pass.moveCount; ++i)
{
auto it = std::find_if(allocations.begin(), allocations.end(), [&](const AllocInfo& info) { return pass.pMoves[i].srcAllocation == info.m_Allocation; });
if (it == allocations.end())
pass.pMoves[i].operation = VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE;
}
BeginSingleTimeCommands();
ProcessDefragmentationPass(pass);
EndSingleTimeCommands();
for (size_t i = 0; i < pass.moveCount; ++i)
{
if (pass.pMoves[i].operation != VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE)
{
VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
VmaAllocationInfo vmaAllocInfo;
vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
if (allocInfo->m_Buffer)
{
assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
allocInfo->m_Buffer = allocInfo->m_NewBuffer;
allocInfo->m_NewBuffer = VK_NULL_HANDLE;
}
else if (allocInfo->m_Image)
{
assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer);
vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs);
allocInfo->m_Image = allocInfo->m_NewImage;
allocInfo->m_NewImage = VK_NULL_HANDLE;
}
else
assert(0);
}
}
if ((res = vmaEndDefragmentationPass(g_hAllocator, ctx, &pass)) == VK_SUCCESS)
break;
TEST(res == VK_INCOMPLETE);
}
TEST(res == VK_SUCCESS);
VmaDefragmentationStats stats = {};
vmaEndDefragmentation(g_hAllocator, ctx, &stats);
#if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0
TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0);
TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0);
#endif
}
swprintf_s(fileName, L"GPU_defragmentation_incremental_basic_B_after.json");
SaveAllocatorStatsToFile(fileName);
for(size_t i = allocations.size(); i--; )
{
allocations[i].Destroy();
}
}
void TestDefragmentationIncrementalComplex()
{
wprintf(L"Test defragmentation incremental complex\n");
std::vector<AllocInfo> allocations;
const std::array<uint32_t, 3> imageSizes = { 256, 512, 1024 };
const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024;
const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024;
const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024;
const size_t imageCount = (size_t)(totalSize / (imageSizes[0] * imageSizes[0] * 4)) / 2;
const size_t bufCount = (size_t)(totalSize / bufSizeMin) / 2;
const size_t percentToLeave = 30;
RandomNumberGenerator rand = { 234522 };
VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
for(size_t i = 0; i < imageCount; ++i)
{
const uint32_t size = imageSizes[rand.Generate() % 3];
imageInfo.extent.width = size;
imageInfo.extent.height = size;
AllocInfo alloc;
CreateImage(allocCreateInfo, imageInfo, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, alloc);
alloc.m_StartValue = 0;
allocations.push_back(alloc);
}
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
for(size_t i = 0; i < bufCount; ++i)
{
bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16);
bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
AllocInfo alloc;
alloc.CreateBuffer(bufCreateInfo, allocCreateInfo);
alloc.m_StartValue = 0;
allocations.push_back(alloc);
}
{
const size_t allocationsToDestroy = round_div<size_t>((imageCount + bufCount) * (100 - percentToLeave), 100);
for(size_t i = 0; i < allocationsToDestroy; ++i)
{
const size_t index = rand.Generate() % allocations.size();
allocations[index].Destroy();
allocations.erase(allocations.begin() + index);
}
}
{
for (auto& alloc : allocations)
vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc);
}
UploadGpuData(allocations.data(), allocations.size());
wchar_t fileName[MAX_PATH];
swprintf_s(fileName, L"GPU_defragmentation_incremental_complex_A_before.json");
SaveAllocatorStatsToFile(fileName);
const size_t maxAdditionalAllocations = 100;
std::vector<AllocInfo> additionalAllocations;
additionalAllocations.reserve(maxAdditionalAllocations);
const auto makeAdditionalAllocation = [&]()
{
if (additionalAllocations.size() < maxAdditionalAllocations)
{
bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16);
bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
AllocInfo alloc;
alloc.CreateBuffer(bufCreateInfo, allocCreateInfo);
additionalAllocations.push_back(alloc);
vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &additionalAllocations.back());
}
};
{
VmaDefragmentationInfo defragInfo = {};
defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT;
VmaDefragmentationContext ctx = VK_NULL_HANDLE;
VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragInfo, &ctx);
TEST(res == VK_SUCCESS);
makeAdditionalAllocation();
VmaDefragmentationPassMoveInfo pass = {};
while((res = vmaBeginDefragmentationPass(g_hAllocator, ctx, &pass)) == VK_INCOMPLETE)
{
makeAdditionalAllocation();
for (uint32_t i = 0; i < pass.moveCount; ++i)
{
auto it = std::find_if(allocations.begin(), allocations.end(), [&](const AllocInfo& info) { return pass.pMoves[i].srcAllocation == info.m_Allocation; });
if (it == allocations.end())
{
auto it = std::find_if(additionalAllocations.begin(), additionalAllocations.end(), [&](const AllocInfo& info) { return pass.pMoves[i].srcAllocation == info.m_Allocation; });
if (it == additionalAllocations.end())
pass.pMoves[i].operation = VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE;
}
}
BeginSingleTimeCommands();
ProcessDefragmentationPass(pass);
EndSingleTimeCommands();
makeAdditionalAllocation();
for (size_t i = 0; i < pass.moveCount; ++i)
{
if (pass.pMoves[i].operation != VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE)
{
VmaAllocation const alloc = pass.pMoves[i].srcAllocation;
VmaAllocationInfo vmaAllocInfo;
vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo);
AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData;
if (allocInfo->m_Buffer)
{
assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage);
vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs);
allocInfo->m_Buffer = allocInfo->m_NewBuffer;
allocInfo->m_NewBuffer = VK_NULL_HANDLE;
}
else if (allocInfo->m_Image)
{
assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer);
vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs);
allocInfo->m_Image = allocInfo->m_NewImage;
allocInfo->m_NewImage = VK_NULL_HANDLE;
}
else
assert(0);
}
}
if ((res = vmaEndDefragmentationPass(g_hAllocator, ctx, &pass)) == VK_SUCCESS)
break;
TEST(res == VK_INCOMPLETE);
makeAdditionalAllocation();
}
TEST(res == VK_SUCCESS);
VmaDefragmentationStats stats = {};
vmaEndDefragmentation(g_hAllocator, ctx, &stats);
#if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0
TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0);
TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0);
#endif
}
swprintf_s(fileName, L"GPU_defragmentation_incremental_complex_B_after.json");
SaveAllocatorStatsToFile(fileName);
for(size_t i = allocations.size(); i--; )
{
allocations[i].Destroy();
}
for(size_t i = additionalAllocations.size(); i--; )
{
additionalAllocations[i].Destroy();
}
}
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)
{
{
void* numberAsPointer = (void*)(size_t)0xC2501FF3u;
void* pointerToSomething = &res;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
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);
TEST(res == VK_SUCCESS);
TEST(allocInfo.pUserData == numberAsPointer);
vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
TEST(allocInfo.pUserData == numberAsPointer);
vmaSetAllocationUserData(g_hAllocator, alloc, pointerToSomething);
vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
TEST(allocInfo.pUserData == pointerToSomething);
vmaDestroyBuffer(g_hAllocator, buf, alloc);
}
{
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_AUTO;
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);
TEST(res == VK_SUCCESS);
TEST(allocInfo.pName != nullptr && allocInfo.pName != name1Buf);
TEST(strcmp(name1, allocInfo.pName) == 0);
delete[] name1Buf;
vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
TEST(strcmp(name1, allocInfo.pName) == 0);
vmaSetAllocationName(g_hAllocator, alloc, name2);
vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
TEST(strcmp(name2, allocInfo.pName) == 0);
vmaSetAllocationName(g_hAllocator, alloc, nullptr);
vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo);
TEST(allocInfo.pName == nullptr);
vmaDestroyBuffer(g_hAllocator, buf, alloc);
}
}
}
static void TestInvalidAllocations()
{
VkResult res;
VmaAllocationCreateInfo allocCreateInfo = {};
{
VkMemoryRequirements memReq = {};
memReq.size = 0; memReq.alignment = 4;
memReq.memoryTypeBits = UINT32_MAX;
VmaAllocation alloc = VK_NULL_HANDLE;
res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr);
TEST(res == VK_ERROR_INITIALIZATION_FAILED && alloc == VK_NULL_HANDLE);
}
{
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufCreateInfo.size = 0; VkBuffer buf = VK_NULL_HANDLE;
VmaAllocation alloc = VK_NULL_HANDLE;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, nullptr);
TEST(res == VK_ERROR_INITIALIZATION_FAILED && buf == VK_NULL_HANDLE && alloc == VK_NULL_HANDLE);
}
{
VkImageCreateInfo imageCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imageCreateInfo.format = VK_FORMAT_B8G8R8A8_UNORM;
imageCreateInfo.extent.width = 128;
imageCreateInfo.extent.height = 0; imageCreateInfo.extent.depth = 1;
imageCreateInfo.mipLevels = 1;
imageCreateInfo.arrayLayers = 1;
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
VkImage image = VK_NULL_HANDLE;
VmaAllocation alloc = VK_NULL_HANDLE;
res = vmaCreateImage(g_hAllocator, &imageCreateInfo, &allocCreateInfo, &image, &alloc, nullptr);
TEST(res == VK_ERROR_INITIALIZATION_FAILED && image == VK_NULL_HANDLE && alloc == VK_NULL_HANDLE);
}
}
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 = {};
res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
TEST(res == VK_SUCCESS);
vmaDestroyBuffer(g_hAllocator, buf, alloc);
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
allocCreateInfo.requiredFlags = 0;
allocCreateInfo.preferredFlags = 0;
allocCreateInfo.memoryTypeBits = UINT32_MAX;
res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
TEST(res == VK_SUCCESS);
TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
vmaDestroyBuffer(g_hAllocator, buf, alloc);
allocCreateInfo.usage = VMA_MEMORY_USAGE_UNKNOWN;
allocCreateInfo.flags = 0;
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);
TEST(res == VK_SUCCESS);
TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
vmaDestroyBuffer(g_hAllocator, buf, alloc);
const uint32_t memType = allocInfo.memoryType;
allocCreateInfo.usage = VMA_MEMORY_USAGE_UNKNOWN;
allocCreateInfo.flags = 0;
allocCreateInfo.requiredFlags = 0;
allocCreateInfo.preferredFlags = 0;
allocCreateInfo.memoryTypeBits = 1u << memType;
res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
TEST(res == VK_SUCCESS);
TEST(allocInfo.memoryType == memType);
vmaDestroyBuffer(g_hAllocator, buf, alloc);
}
static void TestGetAllocatorInfo()
{
wprintf(L"Test vnaGetAllocatorInfo\n");
VmaAllocatorInfo allocInfo = {};
vmaGetAllocatorInfo(g_hAllocator, &allocInfo);
TEST(allocInfo.instance == g_hVulkanInstance);
TEST(allocInfo.physicalDevice == g_hPhysicalDevice);
TEST(allocInfo.device == g_hDevice);
}
static void TestBasics()
{
wprintf(L"Test basics\n");
VkResult res;
TestGetAllocatorInfo();
TestMemoryRequirements();
{
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_AUTO_PREFER_DEVICE;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
TEST(res == VK_SUCCESS);
vmaDestroyBuffer(g_hAllocator, buf, alloc);
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
TEST(res == VK_SUCCESS);
vmaDestroyBuffer(g_hAllocator, buf, alloc);
}
TestUserData();
TestInvalidAllocations();
}
static void TestVirtualBlocks()
{
wprintf(L"Test virtual blocks\n");
const VkDeviceSize blockSize = 16 * MEGABYTE;
const VkDeviceSize alignment = 256;
VkDeviceSize offset;
VmaVirtualBlockCreateInfo blockCreateInfo = {};
blockCreateInfo.pAllocationCallbacks = g_Allocs;
blockCreateInfo.size = blockSize;
VmaVirtualBlock block;
TEST(vmaCreateVirtualBlock(&blockCreateInfo, &block) == VK_SUCCESS && block);
VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.alignment = alignment;
allocCreateInfo.pUserData = (void*)(uintptr_t)1;
allocCreateInfo.size = 8 * MEGABYTE;
VmaVirtualAllocation allocation0 = VK_NULL_HANDLE;
TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation0, &offset) == VK_SUCCESS);
TEST(allocation0 != VK_NULL_HANDLE);
VmaVirtualAllocationInfo allocInfo0 = {};
vmaGetVirtualAllocationInfo(block, allocation0, &allocInfo0);
TEST(allocInfo0.offset < blockSize);
TEST(allocInfo0.offset == offset);
TEST(allocInfo0.size == allocCreateInfo.size);
TEST(allocInfo0.pUserData == allocCreateInfo.pUserData);
vmaSetVirtualAllocationUserData(block, allocation0, (void*)(uintptr_t)2);
vmaGetVirtualAllocationInfo(block, allocation0, &allocInfo0);
TEST(allocInfo0.pUserData == (void*)(uintptr_t)2);
allocCreateInfo.size = 4 * MEGABYTE;
VmaVirtualAllocation allocation1 = VK_NULL_HANDLE;
TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation1, nullptr) == VK_SUCCESS);
TEST(allocation1 != VK_NULL_HANDLE);
VmaVirtualAllocationInfo allocInfo1 = {};
vmaGetVirtualAllocationInfo(block, allocation1, &allocInfo1);
TEST(allocInfo1.offset < blockSize);
TEST(allocInfo1.offset + 4 * MEGABYTE <= allocInfo0.offset || allocInfo0.offset + 8 * MEGABYTE <= allocInfo1.offset);
allocCreateInfo.size = 8 * MEGABYTE;
VmaVirtualAllocation allocation2 = VK_NULL_HANDLE;
TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation2, &offset) < 0);
TEST(allocation2 == VK_NULL_HANDLE);
TEST(offset == UINT64_MAX);
vmaVirtualFree(block, allocation1);
TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation2, nullptr) == VK_SUCCESS);
TEST(allocation2 != VK_NULL_HANDLE);
VmaVirtualAllocationInfo allocInfo2 = {};
vmaGetVirtualAllocationInfo(block, allocation2, &allocInfo2);
TEST(allocInfo2.offset < blockSize);
TEST(allocInfo2.offset + 4 * MEGABYTE <= allocInfo0.offset || allocInfo0.offset + 8 * MEGABYTE <= allocInfo2.offset);
VmaDetailedStatistics statInfo = {};
vmaCalculateVirtualBlockStatistics(block, &statInfo);
TEST(statInfo.statistics.allocationCount == 2);
TEST(statInfo.statistics.blockCount == 1);
TEST(statInfo.statistics.allocationBytes == blockSize);
TEST(statInfo.statistics.blockBytes == blockSize);
#if !defined(VMA_STATS_STRING_ENABLED) || VMA_STATS_STRING_ENABLED
char* json = nullptr;
vmaBuildVirtualBlockStatsString(block, &json, VK_TRUE);
{
std::string str(json);
TEST( str.find("\"CustomData\": \"0000000000000001\"") != std::string::npos );
TEST( str.find("\"CustomData\": \"0000000000000002\"") != std::string::npos );
}
vmaFreeVirtualBlockStatsString(block, json);
#endif
vmaVirtualFree(block, allocation0);
vmaVirtualFree(block, VK_NULL_HANDLE);
{
constexpr size_t allocCount = 10;
VmaVirtualAllocation allocations[allocCount] = {};
for(size_t i = 0; i < allocCount; ++i)
{
const bool alignment0 = i == allocCount - 1;
allocCreateInfo.size = i * 3 + 15;
allocCreateInfo.alignment = alignment0 ? 0 : 8;
TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocations[i], nullptr) == VK_SUCCESS);
TEST(allocations[i] != VK_NULL_HANDLE);
if(!alignment0)
{
VmaVirtualAllocationInfo info;
vmaGetVirtualAllocationInfo(block, allocations[i], &info);
TEST(info.offset % allocCreateInfo.alignment == 0);
}
}
for(size_t i = allocCount; i--; )
{
vmaVirtualFree(block, allocations[i]);
}
}
vmaVirtualFree(block, allocation2);
vmaDestroyVirtualBlock(block);
{
TEST(vmaCreateVirtualBlock(&blockCreateInfo, &block) == VK_SUCCESS);
allocCreateInfo = VmaVirtualAllocationCreateInfo{};
allocCreateInfo.size = MEGABYTE;
for(size_t i = 0; i < 8; ++i)
{
VmaVirtualAllocation allocation;
TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation, nullptr) == VK_SUCCESS);
}
vmaClearVirtualBlock(block);
vmaDestroyVirtualBlock(block);
}
}
static void TestVirtualBlocksAlgorithms()
{
wprintf(L"Test virtual blocks algorithms\n");
RandomNumberGenerator rand{3454335};
auto calcRandomAllocSize = [&rand]() -> VkDeviceSize { return rand.Generate() % 20 + 5; };
for(size_t algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex)
{
VmaVirtualBlockCreateInfo blockCreateInfo = {};
blockCreateInfo.pAllocationCallbacks = g_Allocs;
blockCreateInfo.size = 10'000;
switch(algorithmIndex)
{
case 1: blockCreateInfo.flags = VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT; break;
}
VmaVirtualBlock block = nullptr;
VkResult res = vmaCreateVirtualBlock(&blockCreateInfo, &block);
TEST(res == VK_SUCCESS);
struct AllocData
{
VmaVirtualAllocation allocation;
VkDeviceSize allocOffset, requestedSize, allocationSize;
};
std::vector<AllocData> allocations;
for(size_t i = 0; i < 20; ++i)
{
VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.size = calcRandomAllocSize();
allocCreateInfo.pUserData = (void*)(uintptr_t)(allocCreateInfo.size * 10);
if(i < 10) { }
else if(i < 12) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT;
else if(i < 14) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT;
else if(i < 16) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT;
else if(i < 18 && algorithmIndex == 1) allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
AllocData alloc = {};
alloc.requestedSize = allocCreateInfo.size;
res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.allocation, nullptr);
TEST(res == VK_SUCCESS);
VmaVirtualAllocationInfo allocInfo;
vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
TEST(allocInfo.size >= allocCreateInfo.size);
alloc.allocOffset = allocInfo.offset;
alloc.allocationSize = allocInfo.size;
allocations.push_back(alloc);
}
for(size_t i = 0; i < 5; ++i)
{
const size_t index = rand.Generate() % allocations.size();
vmaVirtualFree(block, allocations[index].allocation);
allocations.erase(allocations.begin() + index);
}
for(size_t i = 0; i < 6; ++i)
{
VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.size = calcRandomAllocSize();
allocCreateInfo.pUserData = (void*)(uintptr_t)(allocCreateInfo.size * 10);
AllocData alloc = {};
alloc.requestedSize = allocCreateInfo.size;
res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.allocation, nullptr);
TEST(res == VK_SUCCESS);
VmaVirtualAllocationInfo allocInfo;
vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
TEST(allocInfo.size >= allocCreateInfo.size);
alloc.allocOffset = allocInfo.offset;
alloc.allocationSize = allocInfo.size;
allocations.push_back(alloc);
}
for(size_t i = 0; i < 3; ++i)
{
VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.size = calcRandomAllocSize();
allocCreateInfo.alignment = 16;
allocCreateInfo.pUserData = (void*)(uintptr_t)(allocCreateInfo.size * 10);
AllocData alloc = {};
alloc.requestedSize = allocCreateInfo.size;
res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.allocation, nullptr);
TEST(res == VK_SUCCESS);
VmaVirtualAllocationInfo allocInfo;
vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
TEST(allocInfo.offset % 16 == 0);
TEST(allocInfo.size >= allocCreateInfo.size);
alloc.allocOffset = allocInfo.offset;
alloc.allocationSize = allocInfo.size;
allocations.push_back(alloc);
}
std::sort(allocations.begin(), allocations.end(), [](const AllocData& lhs, const AllocData& rhs) {
return lhs.allocOffset < rhs.allocOffset; });
for(size_t i = 0; i < allocations.size() - 1; ++i)
{
TEST(allocations[i+1].allocOffset >= allocations[i].allocOffset + allocations[i].allocationSize);
}
{
const AllocData& alloc = allocations.back();
VmaVirtualAllocationInfo allocInfo = {};
vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
TEST((uintptr_t)allocInfo.pUserData == alloc.requestedSize * 10);
vmaSetVirtualAllocationUserData(block, alloc.allocation, (void*)(uintptr_t)666);
vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
TEST((uintptr_t)allocInfo.pUserData == 666);
}
{
VkDeviceSize actualAllocSizeMin = VK_WHOLE_SIZE, actualAllocSizeMax = 0, actualAllocSizeSum = 0;
std::for_each(allocations.begin(), allocations.end(), [&](const AllocData& a) {
actualAllocSizeMin = std::min(actualAllocSizeMin, a.allocationSize);
actualAllocSizeMax = std::max(actualAllocSizeMax, a.allocationSize);
actualAllocSizeSum += a.allocationSize;
});
VmaDetailedStatistics statInfo = {};
vmaCalculateVirtualBlockStatistics(block, &statInfo);
TEST(statInfo.statistics.allocationCount == allocations.size());
TEST(statInfo.statistics.blockCount == 1);
TEST(statInfo.statistics.blockBytes == blockCreateInfo.size);
TEST(statInfo.allocationSizeMax == actualAllocSizeMax);
TEST(statInfo.allocationSizeMin == actualAllocSizeMin);
TEST(statInfo.statistics.allocationBytes >= actualAllocSizeSum);
}
#if !defined(VMA_STATS_STRING_ENABLED) || VMA_STATS_STRING_ENABLED
{
char* json = nullptr;
vmaBuildVirtualBlockStatsString(block, &json, VK_TRUE);
int I = 0; vmaFreeVirtualBlockStatsString(block, json);
}
#endif
vmaClearVirtualBlock(block);
vmaDestroyVirtualBlock(block);
}
}
static void TestAllocationVersusResourceSize()
{
wprintf(L"Test allocation versus resource size\n");
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = 22921; bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
for(uint32_t i = 0; i < 2; ++i)
{
if(i == 1)
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
else
allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
AllocInfo info;
info.CreateBuffer(bufCreateInfo, allocCreateInfo);
VmaAllocationInfo allocInfo = {};
vmaGetAllocationInfo(g_hAllocator, info.m_Allocation, &allocInfo);
void* mappedPtr = nullptr;
VkResult res = vmaMapMemory(g_hAllocator, info.m_Allocation, &mappedPtr);
TEST(res == VK_SUCCESS);
memset(mappedPtr, 0xCC, (size_t)allocInfo.size);
vmaUnmapMemory(g_hAllocator, info.m_Allocation);
info.Destroy();
}
}
static void TestPool_MinBlockCount()
{
#if defined(VMA_DEBUG_MARGIN) && VMA_DEBUG_MARGIN > 0
return;
#endif
wprintf(L"Test Pool MinBlockCount\n");
VkResult res;
static const VkDeviceSize ALLOC_SIZE = 512ull * 1024;
static const VkDeviceSize BLOCK_SIZE = ALLOC_SIZE * 2;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufCreateInfo.size = ALLOC_SIZE;
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.blockSize = BLOCK_SIZE;
poolCreateInfo.minBlockCount = 2; res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex);
TEST(res == VK_SUCCESS);
VmaPool pool = VK_NULL_HANDLE;
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
TEST(res == VK_SUCCESS && pool != VK_NULL_HANDLE);
VmaDetailedStatistics begPoolStats = {};
vmaCalculatePoolStatistics(g_hAllocator, pool, &begPoolStats);
TEST(begPoolStats.statistics.blockCount == 2 &&
begPoolStats.statistics.allocationCount == 0 &&
begPoolStats.statistics.blockBytes == BLOCK_SIZE * 2);
static const uint32_t BUF_COUNT = 5;
allocCreateInfo.pool = pool;
std::vector<AllocInfo> allocs(BUF_COUNT);
for(uint32_t i = 0; i < BUF_COUNT; ++i)
{
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &allocs[i].m_Buffer, &allocs[i].m_Allocation, nullptr);
TEST(res == VK_SUCCESS && allocs[i].m_Buffer != VK_NULL_HANDLE && allocs[i].m_Allocation != VK_NULL_HANDLE);
}
VmaDetailedStatistics poolStats2 = {};
vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats2);
TEST(poolStats2.statistics.blockCount == 3 &&
poolStats2.statistics.allocationCount == BUF_COUNT &&
poolStats2.statistics.blockBytes == BLOCK_SIZE * 3);
allocs[0].Destroy();
allocs[1].Destroy();
VmaDetailedStatistics poolStats3 = {};
vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats3);
TEST(poolStats3.statistics.blockCount == 3 &&
poolStats3.statistics.allocationCount == BUF_COUNT - 2 &&
poolStats2.statistics.blockBytes == BLOCK_SIZE * 3);
allocs[BUF_COUNT - 1].Destroy();
VmaDetailedStatistics poolStats4 = {};
vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats4);
TEST(poolStats4.statistics.blockCount == 2 &&
poolStats4.statistics.allocationCount == BUF_COUNT - 3 &&
poolStats4.statistics.blockBytes == BLOCK_SIZE * 2);
for(size_t i = allocs.size(); i--; )
{
allocs[i].Destroy();
}
vmaDestroyPool(g_hAllocator, pool);
}
static void TestPool_MinAllocationAlignment()
{
wprintf(L"Test Pool MinAllocationAlignment\n");
VkResult res;
static const VkDeviceSize ALLOC_SIZE = 32;
static const VkDeviceSize BLOCK_SIZE = 1024 * 1024;
static const VkDeviceSize MIN_ALLOCATION_ALIGNMENT = 64 * 1024;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufCreateInfo.size = ALLOC_SIZE;
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.blockSize = BLOCK_SIZE;
poolCreateInfo.minAllocationAlignment = MIN_ALLOCATION_ALIGNMENT;
res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex);
TEST(res == VK_SUCCESS);
VmaPool pool = VK_NULL_HANDLE;
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
TEST(res == VK_SUCCESS && pool != VK_NULL_HANDLE);
static const uint32_t BUF_COUNT = 4;
allocCreateInfo = {};
allocCreateInfo.pool = pool;
std::vector<AllocInfo> allocs(BUF_COUNT);
for(uint32_t i = 0; i < BUF_COUNT; ++i)
{
VmaAllocationInfo allocInfo = {};
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &allocs[i].m_Buffer, &allocs[i].m_Allocation, &allocInfo);
TEST(res == VK_SUCCESS && allocs[i].m_Buffer != VK_NULL_HANDLE && allocs[i].m_Allocation != VK_NULL_HANDLE);
TEST(allocInfo.offset % MIN_ALLOCATION_ALIGNMENT == 0);
}
for(size_t i = allocs.size(); i--; )
{
allocs[i].Destroy();
}
vmaDestroyPool(g_hAllocator, pool);
}
static void TestPoolsAndAllocationParameters()
{
wprintf(L"Test pools and allocation parameters\n");
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = 1 * MEGABYTE;
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
uint32_t memTypeIndex = UINT32_MAX;
VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &memTypeIndex);
TEST(res == VK_SUCCESS);
VmaPool pool1 = nullptr, pool2 = nullptr;
std::vector<BufferInfo> bufs;
uint32_t totalNewAllocCount = 0, totalNewBlockCount = 0;
VmaTotalStatistics statsBeg, statsEnd;
vmaCalculateStatistics(g_hAllocator, &statsBeg);
for(size_t poolTypeI = 0; poolTypeI < 3; ++poolTypeI)
{
if(poolTypeI == 0)
{
allocCreateInfo.pool = nullptr;
}
else if(poolTypeI == 1)
{
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.memoryTypeIndex = memTypeIndex;
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool1);
TEST(res == VK_SUCCESS);
allocCreateInfo.pool = pool1;
}
else if(poolTypeI == 2)
{
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.memoryTypeIndex = memTypeIndex;
poolCreateInfo.maxBlockCount = 1;
poolCreateInfo.blockSize = 2 * MEGABYTE + MEGABYTE / 2; res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool2);
TEST(res == VK_SUCCESS);
allocCreateInfo.pool = pool2;
}
uint32_t poolAllocCount = 0, poolBlockCount = 0;
BufferInfo bufInfo = {};
VmaAllocationInfo allocInfo[4] = {};
allocCreateInfo.flags = 0;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo[0]);
TEST(res == VK_SUCCESS && bufInfo.Allocation && bufInfo.Buffer);
bufs.push_back(std::move(bufInfo));
++poolAllocCount;
if(poolTypeI != 2)
{
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo[1]);
TEST(res == VK_SUCCESS && bufInfo.Allocation && bufInfo.Buffer);
TEST(allocInfo[1].offset == 0); TEST(allocInfo[1].deviceMemory != allocInfo[0].deviceMemory); bufs.push_back(std::move(bufInfo));
++poolAllocCount;
}
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo[2]);
TEST(res == VK_SUCCESS && bufInfo.Allocation && bufInfo.Buffer);
TEST(allocInfo[2].deviceMemory == allocInfo[0].deviceMemory); TEST(allocInfo[2].offset != allocInfo[0].offset);
bufs.push_back(std::move(bufInfo));
++poolAllocCount;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo[3]);
if(poolTypeI == 2)
TEST(res < 0);
else
{
TEST(res == VK_SUCCESS && bufInfo.Allocation && bufInfo.Buffer);
bufs.push_back(std::move(bufInfo));
++poolAllocCount;
}
switch(poolTypeI)
{
case 0: poolBlockCount = 1; break; case 1: poolBlockCount = 2; break; case 2: poolBlockCount = 1; break; }
if(poolTypeI > 0)
{
VmaDetailedStatistics poolStats = {};
vmaCalculatePoolStatistics(g_hAllocator, poolTypeI == 2 ? pool2 : pool1, &poolStats);
TEST(poolStats.statistics.allocationCount == poolAllocCount);
const VkDeviceSize usedSize = poolStats.statistics.allocationBytes;
TEST(usedSize == poolAllocCount * MEGABYTE);
TEST(poolStats.statistics.blockCount == poolBlockCount);
}
totalNewAllocCount += poolAllocCount;
totalNewBlockCount += poolBlockCount;
}
vmaCalculateStatistics(g_hAllocator, &statsEnd);
TEST(statsEnd.total.statistics.allocationCount == statsBeg.total.statistics.allocationCount + totalNewAllocCount);
TEST(statsEnd.total.statistics.blockCount >= statsBeg.total.statistics.blockCount + totalNewBlockCount);
TEST(statsEnd.total.statistics.allocationBytes == statsBeg.total.statistics.allocationBytes + totalNewAllocCount * MEGABYTE);
for(auto& bufInfo : bufs)
vmaDestroyBuffer(g_hAllocator, bufInfo.Buffer, bufInfo.Allocation);
vmaDestroyPool(g_hAllocator, pool2);
vmaDestroyPool(g_hAllocator, pool1);
}
void TestHeapSizeLimit()
{
wprintf(L"Test heap size limit\n");
const VkDeviceSize HEAP_SIZE_LIMIT = 100ull * 1024 * 1024; const VkDeviceSize BLOCK_SIZE = 10ull * 1024 * 1024;
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.instance = g_hVulkanInstance;
allocatorCreateInfo.pHeapSizeLimit = heapSizeLimit;
#if VMA_DYNAMIC_VULKAN_FUNCTIONS
VmaVulkanFunctions vulkanFunctions = {};
vulkanFunctions.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
vulkanFunctions.vkGetDeviceProcAddr = vkGetDeviceProcAddr;
allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions;
#endif
VmaAllocator hAllocator;
VkResult res = vmaCreateAllocator(&allocatorCreateInfo, &hAllocator);
TEST(res == VK_SUCCESS);
struct Item
{
VkBuffer hBuf;
VmaAllocation hAlloc;
};
std::vector<Item> items;
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
VmaAllocationInfo dedicatedAllocInfo;
{
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
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, &dedicatedAllocInfo);
TEST(res == VK_SUCCESS);
items.push_back(item);
}
}
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.memoryTypeIndex = dedicatedAllocInfo.memoryType;
poolCreateInfo.blockSize = BLOCK_SIZE;
VmaPool hPool;
res = vmaCreatePool(hAllocator, &poolCreateInfo, &hPool);
TEST(res == VK_SUCCESS);
{
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);
TEST(res == VK_SUCCESS);
items.push_back(item);
}
}
{
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = hPool;
bufCreateInfo.size = 128;
VkBuffer hBuf;
VmaAllocation hAlloc;
res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &hBuf, &hAlloc, nullptr);
TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
}
for(size_t i = items.size(); i--; )
{
vmaDestroyBuffer(hAllocator, items[i].hBuf, items[i].hAlloc);
}
vmaDestroyPool(hAllocator, hPool);
vmaDestroyAllocator(hAllocator);
}
#ifndef VMA_DEBUG_MARGIN
#define VMA_DEBUG_MARGIN (0)
#endif
static void TestDebugMargin()
{
if(VMA_DEBUG_MARGIN == 0)
{
return;
}
wprintf(L"Test VMA_DEBUG_MARGIN = %u\n", (uint32_t)VMA_DEBUG_MARGIN);
VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufInfo.size = 256;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
VmaPoolCreateInfo poolCreateInfo = {};
TEST(vmaFindMemoryTypeIndexForBufferInfo(
g_hAllocator, &bufInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex) == VK_SUCCESS);
for(size_t algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex)
{
switch(algorithmIndex)
{
case 0: poolCreateInfo.flags = 0; break;
case 1: poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT; break;
default: assert(0);
}
VmaPool pool = VK_NULL_HANDLE;
TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS && pool);
allocCreateInfo.pool = pool;
const size_t BUF_COUNT = 10;
BufferInfo buffers[BUF_COUNT];
VmaAllocationInfo allocInfo[BUF_COUNT];
for(size_t allocIndex = 0; allocIndex < 10; ++allocIndex)
{
const bool isLast = allocIndex == BUF_COUNT - 1;
bufInfo.size = (VkDeviceSize)(allocIndex + 1) * 256;
allocCreateInfo.flags = isLast ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0;
VkResult res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buffers[allocIndex].Buffer, &buffers[allocIndex].Allocation, &allocInfo[allocIndex]);
TEST(res == VK_SUCCESS);
if(isLast)
{
TEST(allocInfo[allocIndex].pMappedData != nullptr);
memset(allocInfo[allocIndex].pMappedData, 0xFF, bufInfo.size );
}
}
std::sort(allocInfo, allocInfo + BUF_COUNT, [](const VmaAllocationInfo& lhs, const VmaAllocationInfo& rhs) -> bool
{
if(lhs.deviceMemory != rhs.deviceMemory)
{
return lhs.deviceMemory < rhs.deviceMemory;
}
return lhs.offset < rhs.offset;
});
for(size_t i = 1; i < BUF_COUNT; ++i)
{
if(allocInfo[i].deviceMemory == allocInfo[i - 1].deviceMemory)
{
TEST(allocInfo[i].offset >=
allocInfo[i - 1].offset + allocInfo[i - 1].size + VMA_DEBUG_MARGIN);
}
}
VkResult res = vmaCheckCorruption(g_hAllocator, UINT32_MAX);
TEST(res == VK_SUCCESS || res == VK_ERROR_FEATURE_NOT_PRESENT);
char* json = nullptr;
vmaBuildStatsString(g_hAllocator, &json, VK_TRUE);
int I = 1; vmaFreeStatsString(g_hAllocator, json);
for(size_t i = BUF_COUNT; i--; )
{
vmaDestroyBuffer(g_hAllocator, buffers[i].Buffer, buffers[i].Allocation);
}
vmaDestroyPool(g_hAllocator, pool);
}
}
static void TestDebugMarginNotInVirtualAllocator()
{
wprintf(L"Test VMA_DEBUG_MARGIN not applied to virtual allocator\n");
constexpr size_t ALLOCATION_COUNT = 10;
for(size_t algorithm = 0; algorithm < 2; ++algorithm)
{
VmaVirtualBlockCreateInfo blockCreateInfo = {};
blockCreateInfo.size = ALLOCATION_COUNT * MEGABYTE;
blockCreateInfo.flags = (algorithm == 1 ? VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT : 0);
VmaVirtualBlock block = VK_NULL_HANDLE;
TEST(vmaCreateVirtualBlock(&blockCreateInfo, &block) == VK_SUCCESS);
VmaVirtualAllocation allocs[ALLOCATION_COUNT];
for(size_t i = 0; i < ALLOCATION_COUNT; ++i)
{
VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.size = 1 * MEGABYTE;
TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocs[i], nullptr) == VK_SUCCESS);
}
vmaClearVirtualBlock(block);
vmaDestroyVirtualBlock(block);
}
}
static void TestLinearAllocator()
{
wprintf(L"Test linear allocator\n");
RandomNumberGenerator rand{645332};
VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
sampleBufCreateInfo.size = 1024; sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
VmaAllocationCreateInfo sampleAllocCreateInfo = {};
sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
VmaPoolCreateInfo poolCreateInfo = {};
VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
TEST(res == VK_SUCCESS);
poolCreateInfo.blockSize = 1024 * 300;
poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT;
poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
VmaPool pool = nullptr;
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
TEST(res == VK_SUCCESS);
VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
constexpr size_t maxBufCount = 100;
std::vector<BufferInfo> bufInfo;
constexpr VkDeviceSize bufSizeMin = 64;
constexpr VkDeviceSize bufSizeMax = 1024;
VmaAllocationInfo allocInfo;
VkDeviceSize prevOffset = 0;
for(size_t i = 0; i < 2; ++i)
{
VkDeviceSize bufSumSize = 0;
for(size_t i = 0; i < maxBufCount; ++i)
{
bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
TEST(i == 0 || allocInfo.offset > prevOffset);
bufInfo.push_back(newBufInfo);
prevOffset = allocInfo.offset;
bufSumSize += bufCreateInfo.size;
}
VmaDetailedStatistics stats;
vmaCalculatePoolStatistics(g_hAllocator, pool, &stats);
TEST(stats.statistics.blockBytes == poolCreateInfo.blockSize);
TEST(stats.statistics.blockBytes - stats.statistics.allocationBytes == poolCreateInfo.blockSize - bufSumSize);
TEST(stats.statistics.allocationCount == bufInfo.size());
while(!bufInfo.empty())
{
const size_t indexToDestroy = rand.Generate() % bufInfo.size();
const BufferInfo& currBufInfo = bufInfo[indexToDestroy];
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.erase(bufInfo.begin() + indexToDestroy);
}
}
{
for(size_t i = 0; i < maxBufCount; ++i)
{
bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
TEST(i == 0 || allocInfo.offset > prevOffset);
bufInfo.push_back(newBufInfo);
prevOffset = allocInfo.offset;
}
for(size_t i = 0; i < maxBufCount / 5; ++i)
{
const BufferInfo& currBufInfo = bufInfo.back();
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.pop_back();
}
for(size_t i = 0; i < maxBufCount / 5; ++i)
{
bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
TEST(i == 0 || allocInfo.offset > prevOffset);
bufInfo.push_back(newBufInfo);
prevOffset = allocInfo.offset;
}
while(!bufInfo.empty())
{
const BufferInfo& currBufInfo = bufInfo.back();
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.pop_back();
}
}
{
bufCreateInfo.size = bufSizeMax;
for(size_t i = 0; i < maxBufCount; ++i)
{
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
TEST(i == 0 || allocInfo.offset > prevOffset);
bufInfo.push_back(newBufInfo);
prevOffset = allocInfo.offset;
}
const size_t buffersPerIter = maxBufCount / 10 - 1;
const size_t iterCount = poolCreateInfo.blockSize / bufCreateInfo.size / buffersPerIter * 2;
for(size_t iter = 0; iter < iterCount; ++iter)
{
for(size_t bufPerIter = 0; bufPerIter < buffersPerIter; ++bufPerIter)
{
const BufferInfo& currBufInfo = bufInfo.front();
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.erase(bufInfo.begin());
}
for(size_t bufPerIter = 0; bufPerIter < buffersPerIter; ++bufPerIter)
{
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
}
}
uint32_t debugIndex = 0;
while(res == VK_SUCCESS)
{
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
if(res == VK_SUCCESS)
{
bufInfo.push_back(newBufInfo);
}
else
{
TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
}
++debugIndex;
}
while(!bufInfo.empty())
{
const size_t indexToDestroy = rand.Generate() % bufInfo.size();
const BufferInfo& currBufInfo = bufInfo[indexToDestroy];
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.erase(bufInfo.begin() + indexToDestroy);
}
}
{
VkDeviceSize prevOffsetLower = 0;
VkDeviceSize prevOffsetUpper = poolCreateInfo.blockSize;
for(size_t i = 0; i < maxBufCount; ++i)
{
const bool upperAddress = (i % 2) != 0;
if(upperAddress)
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
else
allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
if(upperAddress)
{
TEST(allocInfo.offset < prevOffsetUpper);
prevOffsetUpper = allocInfo.offset;
}
else
{
TEST(allocInfo.offset >= prevOffsetLower);
prevOffsetLower = allocInfo.offset;
}
TEST(prevOffsetLower < prevOffsetUpper);
bufInfo.push_back(newBufInfo);
}
for(size_t i = 0; i < maxBufCount / 5; ++i)
{
const BufferInfo& currBufInfo = bufInfo.back();
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.pop_back();
}
for(size_t i = 0; i < maxBufCount / 5; ++i)
{
const bool upperAddress = (i % 2) != 0;
if(upperAddress)
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
else
allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
}
while(!bufInfo.empty())
{
const BufferInfo& currBufInfo = bufInfo.back();
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.pop_back();
}
prevOffsetLower = 0;
prevOffsetUpper = poolCreateInfo.blockSize;
res = VK_SUCCESS;
for(size_t i = 0; res == VK_SUCCESS; ++i)
{
const bool upperAddress = (i % 2) != 0;
if(upperAddress)
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
else
allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 64);
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
if(res == VK_SUCCESS)
{
if(upperAddress)
{
TEST(allocInfo.offset < prevOffsetUpper);
prevOffsetUpper = allocInfo.offset;
}
else
{
TEST(allocInfo.offset >= prevOffsetLower);
prevOffsetLower = allocInfo.offset;
}
TEST(prevOffsetLower < prevOffsetUpper);
bufInfo.push_back(newBufInfo);
}
}
while(!bufInfo.empty())
{
const size_t indexToDestroy = rand.Generate() % bufInfo.size();
const BufferInfo& currBufInfo = bufInfo[indexToDestroy];
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.erase(bufInfo.begin() + indexToDestroy);
}
prevOffsetUpper = poolCreateInfo.blockSize;
res = VK_SUCCESS;
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
bufCreateInfo.size = bufSizeMax;
for(size_t i = 0; res == VK_SUCCESS; ++i)
{
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
if(res == VK_SUCCESS)
{
TEST(allocInfo.offset < prevOffsetUpper);
prevOffsetUpper = allocInfo.offset;
bufInfo.push_back(newBufInfo);
}
}
while(!bufInfo.empty())
{
const BufferInfo& currBufInfo = bufInfo.back();
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.pop_back();
}
}
vmaDestroyPool(g_hAllocator, pool);
}
static void TestLinearAllocatorMultiBlock()
{
wprintf(L"Test linear allocator multi block\n");
RandomNumberGenerator rand{345673};
VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
sampleBufCreateInfo.size = 1024 * 1024;
sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo sampleAllocCreateInfo = {};
sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT;
VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
TEST(res == VK_SUCCESS);
VmaPool pool = nullptr;
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
TEST(res == VK_SUCCESS);
VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
std::vector<BufferInfo> bufInfo;
VmaAllocationInfo allocInfo;
{
VkDeviceMemory lastMem = VK_NULL_HANDLE;
for(uint32_t i = 0; ; ++i)
{
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
if(lastMem && allocInfo.deviceMemory != lastMem)
{
break;
}
lastMem = allocInfo.deviceMemory;
}
TEST(bufInfo.size() > 2);
VmaDetailedStatistics poolStats = {};
vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
TEST(poolStats.statistics.blockCount == 2);
while(!bufInfo.empty())
{
const size_t indexToDestroy = rand.Generate() % bufInfo.size();
const BufferInfo& currBufInfo = bufInfo[indexToDestroy];
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.erase(bufInfo.begin() + indexToDestroy);
}
vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
TEST(poolStats.statistics.blockCount <= 1);
}
{
VkDeviceMemory lastMem = VK_NULL_HANDLE;
for(uint32_t i = 0; ; ++i)
{
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
if(lastMem && allocInfo.deviceMemory != lastMem)
{
break;
}
lastMem = allocInfo.deviceMemory;
}
TEST(bufInfo.size() > 2);
for(uint32_t i = 0; i < 5; ++i)
{
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
}
VmaDetailedStatistics poolStats = {};
vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
TEST(poolStats.statistics.blockCount == 2);
for(size_t i = 0, countToDelete = bufInfo.size() / 2; i < countToDelete; ++i)
{
const BufferInfo& currBufInfo = bufInfo.back();
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.pop_back();
}
BufferInfo newBufInfo;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
TEST(poolStats.statistics.blockCount == 1);
while(!bufInfo.empty())
{
const BufferInfo& currBufInfo = bufInfo.back();
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.pop_back();
}
}
vmaDestroyPool(g_hAllocator, pool);
}
static void TestAllocationAlgorithmsCorrectness()
{
wprintf(L"Test allocation algorithm correctness\n");
constexpr uint32_t LEVEL_COUNT = 12;
RandomNumberGenerator rand{2342435};
for(uint32_t isVirtual = 0; isVirtual < 3; ++isVirtual)
{
const VkDeviceSize sizeUnit = isVirtual == 2 ? 1 : 0x10000;
const VkDeviceSize blockSize = (1llu << (LEVEL_COUNT - 1)) * sizeUnit;
for(uint32_t algorithmIndex = 0; algorithmIndex < 1; ++algorithmIndex)
{
VmaPool pool = VK_NULL_HANDLE;
VmaVirtualBlock virtualBlock = VK_NULL_HANDLE;
uint32_t algorithm;
switch (algorithmIndex)
{
case 0:
algorithm = 0;
break;
default:
break;
}
if(isVirtual)
{
VmaVirtualBlockCreateInfo blockCreateInfo = {};
blockCreateInfo.pAllocationCallbacks = g_Allocs;
blockCreateInfo.flags = algorithm;
blockCreateInfo.size = blockSize;
TEST(vmaCreateVirtualBlock(&blockCreateInfo, &virtualBlock) == VK_SUCCESS);
}
else
{
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.blockSize = blockSize;
poolCreateInfo.flags = algorithm;
poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = 0x10000; bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
TEST(vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex) == VK_SUCCESS);
TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
}
for(uint32_t strategyIndex = 0; strategyIndex < 3; ++strategyIndex)
{
struct AllocData
{
VmaAllocation alloc = VK_NULL_HANDLE;
VkBuffer buf = VK_NULL_HANDLE;
VmaVirtualAllocation virtualAlloc = VK_NULL_HANDLE;
};
std::vector<AllocData> allocationsPerLevel[LEVEL_COUNT];
auto createAllocation = [&](uint32_t level) -> void
{
AllocData allocData;
const VkDeviceSize allocSize = (1llu << level) * sizeUnit;
if(isVirtual)
{
VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.size = allocSize;
switch(strategyIndex)
{
case 1: allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; break;
case 2: allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT; break;
}
TEST(vmaVirtualAllocate(virtualBlock, &allocCreateInfo, &allocData.virtualAlloc, nullptr) == VK_SUCCESS);
}
else
{
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
switch(strategyIndex)
{
case 1: allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; break;
case 2: allocCreateInfo.flags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT; break;
}
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = allocSize;
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &allocData.buf, &allocData.alloc, nullptr) == VK_SUCCESS);
}
allocationsPerLevel[level].push_back(allocData);
};
auto destroyAllocation = [&](uint32_t level, size_t index) -> void
{
const AllocData& allocData = allocationsPerLevel[level][index];
if(isVirtual)
vmaVirtualFree(virtualBlock, allocData.virtualAlloc);
else
vmaDestroyBuffer(g_hAllocator, allocData.buf, allocData.alloc);
allocationsPerLevel[level].erase(allocationsPerLevel[level].begin() + index);
};
createAllocation(LEVEL_COUNT - 1);
for(uint32_t level = LEVEL_COUNT; level-- > 1; )
{
size_t indexToDestroy = rand.Generate() % allocationsPerLevel[level].size();
destroyAllocation(level, indexToDestroy);
createAllocation(level - 1);
createAllocation(level - 1);
}
{
uint32_t actualAllocCount = 0, statAllocCount = 0;
VkDeviceSize actualAllocSize = 0, statAllocSize = 0;
for(uint32_t level = 0; level < LEVEL_COUNT; ++level)
{
for(size_t index = allocationsPerLevel[level].size(); index--; )
{
if(isVirtual)
{
VmaVirtualAllocationInfo allocInfo = {};
vmaGetVirtualAllocationInfo(virtualBlock, allocationsPerLevel[level][index].virtualAlloc, &allocInfo);
actualAllocSize += allocInfo.size;
}
else
{
VmaAllocationInfo allocInfo = {};
vmaGetAllocationInfo(g_hAllocator, allocationsPerLevel[level][index].alloc, &allocInfo);
actualAllocSize += allocInfo.size;
}
}
actualAllocCount += (uint32_t)allocationsPerLevel[level].size();
}
if(isVirtual)
{
VmaDetailedStatistics info = {};
vmaCalculateVirtualBlockStatistics(virtualBlock, &info);
statAllocCount = info.statistics.allocationCount;
statAllocSize = info.statistics.allocationBytes;
TEST(info.statistics.blockCount == 1);
TEST(info.statistics.blockBytes == blockSize);
}
else
{
VmaDetailedStatistics stats = {};
vmaCalculatePoolStatistics(g_hAllocator, pool, &stats);
statAllocCount = (uint32_t)stats.statistics.allocationCount;
statAllocSize = stats.statistics.allocationBytes;
TEST(stats.statistics.blockCount == 1);
TEST(stats.statistics.blockBytes == blockSize);
}
TEST(actualAllocCount == statAllocCount);
TEST(actualAllocSize == statAllocSize);
}
{
char* json = nullptr;
if(isVirtual)
{
vmaBuildVirtualBlockStatsString(virtualBlock, &json, VK_TRUE);
int I = 1; vmaFreeVirtualBlockStatsString(virtualBlock, json);
}
else
{
vmaBuildStatsString(g_hAllocator, &json, VK_TRUE);
int I = 1; vmaFreeStatsString(g_hAllocator, json);
}
}
for(uint32_t level = 0; level < LEVEL_COUNT; ++level)
for(size_t index = allocationsPerLevel[level].size(); index--; )
destroyAllocation(level, index);
}
vmaDestroyVirtualBlock(virtualBlock);
vmaDestroyPool(g_hAllocator, pool);
}
}
}
static void ManuallyTestLinearAllocator()
{
VmaTotalStatistics origStats;
vmaCalculateStatistics(g_hAllocator, &origStats);
wprintf(L"Manually test linear allocator\n");
RandomNumberGenerator rand{645332};
VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
sampleBufCreateInfo.size = 1024; sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
VmaAllocationCreateInfo sampleAllocCreateInfo = {};
sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
VmaPoolCreateInfo poolCreateInfo = {};
VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
TEST(res == VK_SUCCESS);
poolCreateInfo.blockSize = 10 * 1024;
poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT;
poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
VmaPool pool = nullptr;
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
TEST(res == VK_SUCCESS);
VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
std::vector<BufferInfo> bufInfo;
VmaAllocationInfo allocInfo;
BufferInfo newBufInfo;
{
bufCreateInfo.size = 32;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
bufCreateInfo.size = 1024;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
bufCreateInfo.size = 32;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT;
bufCreateInfo.size = 128;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
bufCreateInfo.size = 1024;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
bufCreateInfo.size = 16;
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
bufInfo.push_back(newBufInfo);
VmaTotalStatistics currStats;
vmaCalculateStatistics(g_hAllocator, &currStats);
VmaDetailedStatistics poolStats;
vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
#if !defined(VMA_STATS_STRING_ENABLED) || VMA_STATS_STRING_ENABLED
char* statsStr = nullptr;
vmaBuildStatsString(g_hAllocator, &statsStr, VK_TRUE);
int I = 0;
vmaFreeStatsString(g_hAllocator, statsStr);
#endif
while(!bufInfo.empty())
{
const BufferInfo& currBufInfo = bufInfo.back();
vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
bufInfo.pop_back();
}
}
vmaDestroyPool(g_hAllocator, pool);
}
static void BenchmarkAlgorithmsCase(FILE* file,
uint32_t algorithm,
bool empty,
VmaAllocationCreateFlags allocStrategy,
FREE_ORDER freeOrder)
{
RandomNumberGenerator rand{16223};
const VkDeviceSize bufSizeMin = 32;
const VkDeviceSize bufSizeMax = 1024;
const size_t maxBufCapacity = 10000;
const uint32_t iterationCount = 10;
VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
sampleBufCreateInfo.size = bufSizeMax;
sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
VmaAllocationCreateInfo sampleAllocCreateInfo = {};
sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
VmaPoolCreateInfo poolCreateInfo = {};
VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
TEST(res == VK_SUCCESS);
poolCreateInfo.blockSize = bufSizeMax * maxBufCapacity;
poolCreateInfo.flags = VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT; poolCreateInfo.flags |= algorithm;
poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
VmaPool pool = nullptr;
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
TEST(res == VK_SUCCESS);
VkBuffer dummyBuffer = VK_NULL_HANDLE;
res = vkCreateBuffer(g_hDevice, &sampleBufCreateInfo, g_Allocs, &dummyBuffer);
TEST(res == VK_SUCCESS && dummyBuffer);
VkMemoryRequirements memReq = {};
vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs);
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
allocCreateInfo.flags = allocStrategy;
VmaAllocation alloc;
std::vector<VmaAllocation> baseAllocations;
if(!empty)
{
VkDeviceSize totalSize = 0;
while(totalSize < poolCreateInfo.blockSize / 3)
{
memReq.size = bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin);
res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr);
TEST(res == VK_SUCCESS);
baseAllocations.push_back(alloc);
totalSize += memReq.size;
}
size_t allocsToDelete = baseAllocations.size() / 2;
for(size_t i = 0; i < allocsToDelete; ++i)
{
const size_t index = (size_t)rand.Generate() % baseAllocations.size();
vmaFreeMemory(g_hAllocator, baseAllocations[index]);
baseAllocations.erase(baseAllocations.begin() + index);
}
}
const size_t allocCount = maxBufCapacity / 3;
std::vector<VmaAllocation> testAllocations;
testAllocations.reserve(allocCount);
duration allocTotalDuration = duration::zero();
duration freeTotalDuration = duration::zero();
for(uint32_t iterationIndex = 0; iterationIndex < iterationCount; ++iterationIndex)
{
time_point allocTimeBeg = std::chrono::high_resolution_clock::now();
for(size_t i = 0; i < allocCount; ++i)
{
memReq.size = bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin);
res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr);
TEST(res == VK_SUCCESS);
testAllocations.push_back(alloc);
}
allocTotalDuration += std::chrono::high_resolution_clock::now() - allocTimeBeg;
switch(freeOrder)
{
case FREE_ORDER::FORWARD:
break;
case FREE_ORDER::BACKWARD:
std::reverse(testAllocations.begin(), testAllocations.end());
break;
case FREE_ORDER::RANDOM:
std::shuffle(testAllocations.begin(), testAllocations.end(), MyUniformRandomNumberGenerator(rand));
break;
default: assert(0);
}
time_point freeTimeBeg = std::chrono::high_resolution_clock::now();
for(size_t i = 0; i < allocCount; ++i)
vmaFreeMemory(g_hAllocator, testAllocations[i]);
freeTotalDuration += std::chrono::high_resolution_clock::now() - freeTimeBeg;
testAllocations.clear();
}
while(!baseAllocations.empty())
{
vmaFreeMemory(g_hAllocator, baseAllocations.back());
baseAllocations.pop_back();
}
vmaDestroyPool(g_hAllocator, pool);
const float allocTotalSeconds = ToFloatSeconds(allocTotalDuration);
const float freeTotalSeconds = ToFloatSeconds(freeTotalDuration);
printf(" Algorithm=%s %s Allocation=%s FreeOrder=%s: allocations %g s, free %g s\n",
AlgorithmToStr(algorithm),
empty ? "Empty" : "Not empty",
GetAllocationStrategyName(allocStrategy),
FREE_ORDER_NAMES[(size_t)freeOrder],
allocTotalSeconds,
freeTotalSeconds);
if(file)
{
std::string currTime;
CurrentTimeToStr(currTime);
fprintf(file, "%s,%s,%s,%u,%s,%s,%g,%g\n",
CODE_DESCRIPTION, currTime.c_str(),
AlgorithmToStr(algorithm),
empty ? 1 : 0,
GetAllocationStrategyName(allocStrategy),
FREE_ORDER_NAMES[(uint32_t)freeOrder],
allocTotalSeconds,
freeTotalSeconds);
}
}
static void TestBufferDeviceAddress()
{
wprintf(L"Test buffer device address\n");
assert(VK_KHR_buffer_device_address_enabled);
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = 0x10000;
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
{
if(testIndex == 1)
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
BufferInfo bufInfo = {};
VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&bufInfo.Buffer, &bufInfo.Allocation, nullptr);
TEST(res == VK_SUCCESS);
VkBufferDeviceAddressInfoEXT bufferDeviceAddressInfo = { VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_EXT };
bufferDeviceAddressInfo.buffer = bufInfo.Buffer;
TEST(g_vkGetBufferDeviceAddressKHR != nullptr);
VkDeviceAddress addr = g_vkGetBufferDeviceAddressKHR(g_hDevice, &bufferDeviceAddressInfo);
TEST(addr != 0);
vmaDestroyBuffer(g_hAllocator, bufInfo.Buffer, bufInfo.Allocation);
}
}
static void TestMemoryPriority()
{
wprintf(L"Test memory priority\n");
assert(VK_EXT_memory_priority_enabled);
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = 0x10000;
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
allocCreateInfo.priority = 1.f;
for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
{
if(testIndex == 1)
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
BufferInfo bufInfo = {};
VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
&bufInfo.Buffer, &bufInfo.Allocation, nullptr);
TEST(res == VK_SUCCESS);
vmaDestroyBuffer(g_hAllocator, bufInfo.Buffer, bufInfo.Allocation);
}
}
static void BenchmarkAlgorithms(FILE* file)
{
wprintf(L"Benchmark algorithms\n");
if(file)
{
fprintf(file,
"Code,Time,"
"Algorithm,Empty,Allocation strategy,Free order,"
"Allocation time (s),Deallocation time (s)\n");
}
uint32_t freeOrderCount = 1;
if(ConfigType >= CONFIG_TYPE::CONFIG_TYPE_LARGE)
freeOrderCount = 3;
else if(ConfigType >= CONFIG_TYPE::CONFIG_TYPE_SMALL)
freeOrderCount = 2;
const uint32_t emptyCount = ConfigType >= CONFIG_TYPE::CONFIG_TYPE_SMALL ? 2 : 1;
const uint32_t allocStrategyCount = GetAllocationStrategyCount();
for(uint32_t freeOrderIndex = 0; freeOrderIndex < freeOrderCount; ++freeOrderIndex)
{
FREE_ORDER freeOrder = FREE_ORDER::COUNT;
switch(freeOrderIndex)
{
case 0: freeOrder = FREE_ORDER::BACKWARD; break;
case 1: freeOrder = FREE_ORDER::FORWARD; break;
case 2: freeOrder = FREE_ORDER::RANDOM; break;
default: assert(0);
}
for(uint32_t emptyIndex = 0; emptyIndex < emptyCount; ++emptyIndex)
{
for(uint32_t algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex)
{
uint32_t algorithm = 0;
switch(algorithmIndex)
{
case 0:
break;
case 1:
algorithm = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT;
break;
default:
assert(0);
}
uint32_t currAllocStrategyCount = algorithm != 0 ? 1 : allocStrategyCount;
for(uint32_t allocStrategyIndex = 0; allocStrategyIndex < currAllocStrategyCount; ++allocStrategyIndex)
{
VmaAllocatorCreateFlags strategy = 0;
if(currAllocStrategyCount > 1)
{
switch(allocStrategyIndex)
{
case 0: strategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT; break;
case 1: strategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; break;
default: assert(0);
}
}
BenchmarkAlgorithmsCase(
file,
algorithm,
(emptyIndex == 0), strategy,
freeOrder); }
}
}
}
}
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, g_Allocs, &dummyBuffer);
TEST(res == VK_SUCCESS);
VkMemoryRequirements memReq;
vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
memoryTypeBits = memReq.memoryTypeBits;
vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs);
}
VmaAllocationCreateInfo poolAllocInfo = {};
poolAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
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;
VmaPool pool;
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
TEST(res == VK_SUCCESS);
{
static const char* const POOL_NAME = "Pool name";
vmaSetPoolName(g_hAllocator, pool, POOL_NAME);
const char* fetchedPoolName = nullptr;
vmaGetPoolName(g_hAllocator, pool, &fetchedPoolName);
TEST(strcmp(fetchedPoolName, POOL_NAME) == 0);
vmaSetPoolName(g_hAllocator, pool, nullptr);
}
vmaSetCurrentFrameIndex(g_hAllocator, 1);
VmaAllocationCreateInfo allocInfo = {};
allocInfo.pool = pool;
struct BufItem
{
VkBuffer Buf;
VmaAllocation Alloc;
};
std::vector<BufItem> items;
for(size_t i = 0; i < BUF_COUNT; ++i)
{
BufItem item;
res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
TEST(res == VK_SUCCESS);
items.push_back(item);
}
{
BufItem item;
res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY);
}
for(size_t i = 0; i < items.size(); ++i)
{
VmaAllocationInfo allocInfo;
vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo);
TEST(allocInfo.deviceMemory != VK_NULL_HANDLE);
TEST(allocInfo.pMappedData == nullptr);
}
{
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);
}
}
{
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);
if(res == VK_SUCCESS)
items.push_back(item);
}
}
else {
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);
}
}
}
}
while(items.size() < BUF_COUNT)
{
BufItem item;
res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
TEST(res == VK_SUCCESS);
items.push_back(item);
}
vmaDestroyBuffer(g_hAllocator, items.back().Buf, items.back().Alloc);
items.pop_back();
{
VmaDetailedStatistics poolStats = {};
vmaCalculatePoolStatistics(g_hAllocator, pool, &poolStats);
TEST(poolStats.statistics.allocationCount == items.size());
TEST(poolStats.statistics.blockBytes == BUF_COUNT * BUF_SIZE);
TEST(poolStats.unusedRangeCount == 1);
TEST(poolStats.statistics.blockBytes - poolStats.statistics.allocationBytes == BUF_SIZE);
}
for(size_t i = items.size(); i--; )
vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
items.clear();
for(size_t i = 0; i < BUF_COUNT; ++i)
{
BufItem item;
res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr);
TEST(res == VK_SUCCESS);
items.push_back(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);
}
{
VmaDefragmentationInfo defragmentationInfo = {};
defragmentationInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT;
defragmentationInfo.pool = pool;
VmaDefragmentationContext defragCtx = nullptr;
VkResult res = vmaBeginDefragmentation(g_hAllocator, &defragmentationInfo, &defragCtx);
TEST(res == VK_SUCCESS);
VmaDefragmentationPassMoveInfo pass = {};
while ((res = vmaBeginDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_INCOMPLETE)
{
if ((res = vmaEndDefragmentationPass(g_hAllocator, defragCtx, &pass)) == VK_SUCCESS)
break;
TEST(res == VK_INCOMPLETE);
}
TEST(res == VK_SUCCESS);
VmaDefragmentationStats defragmentationStats;
vmaEndDefragmentation(g_hAllocator, defragCtx, &defragmentationStats);
TEST(defragmentationStats.allocationsMoved == 24);
}
for(size_t i = items.size(); i--; )
vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc);
items.clear();
{
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
VkMemoryRequirements memReq;
memReq.memoryTypeBits = UINT32_MAX;
memReq.alignment = 1;
memReq.size = poolCreateInfo.blockSize + 4;
VmaAllocation alloc = nullptr;
res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr);
TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY && alloc == nullptr);
}
vmaDestroyPool(g_hAllocator, pool);
}
static bool ValidatePattern(const void* pMemory, size_t size, uint8_t pattern)
{
const uint8_t* pBytes = (const uint8_t*)pMemory;
for(size_t i = 0; i < size; ++i)
{
if(pBytes[i] != pattern)
{
return false;
}
}
return true;
}
static void TestAllocationsInitialization()
{
VkResult res;
const size_t BUF_SIZE = 1024;
VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufInfo.size = BUF_SIZE;
bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo dummyBufAllocCreateInfo = {};
dummyBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
dummyBufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.blockSize = BUF_SIZE * 10;
poolCreateInfo.minBlockCount = 1; poolCreateInfo.maxBlockCount = 1;
res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufInfo, &dummyBufAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
TEST(res == VK_SUCCESS);
VmaAllocationCreateInfo bufAllocCreateInfo = {};
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &bufAllocCreateInfo.pool);
TEST(res == VK_SUCCESS);
bufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
VkBuffer firstBuf;
VmaAllocation firstAlloc;
res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &firstBuf, &firstAlloc, nullptr);
TEST(res == VK_SUCCESS);
for(uint32_t i = 0; i < 2; ++i)
{
const bool persistentlyMapped = i == 0;
bufAllocCreateInfo.flags = persistentlyMapped ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0;
VkBuffer buf;
VmaAllocation alloc;
VmaAllocationInfo allocInfo;
res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &buf, &alloc, &allocInfo);
TEST(res == VK_SUCCESS);
void* pMappedData;
if(!persistentlyMapped)
{
res = vmaMapMemory(g_hAllocator, alloc, &pMappedData);
TEST(res == VK_SUCCESS);
}
else
{
pMappedData = allocInfo.pMappedData;
}
bool valid = ValidatePattern(pMappedData, BUF_SIZE, 0xDC);
TEST(valid);
if(!persistentlyMapped)
{
vmaUnmapMemory(g_hAllocator, alloc);
}
vmaDestroyBuffer(g_hAllocator, buf, alloc);
valid = ValidatePattern(pMappedData, BUF_SIZE, 0xEF);
TEST(valid);
}
vmaDestroyBuffer(g_hAllocator, firstBuf, firstAlloc);
vmaDestroyPool(g_hAllocator, bufAllocCreateInfo.pool);
}
static void TestPool_Benchmark(
PoolTestResult& outResult,
const PoolTestConfig& config)
{
TEST(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 bufferTemplateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufferTemplateInfo.size = 256; bufferTemplateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VkImageCreateInfo imageTemplateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imageTemplateInfo.imageType = VK_IMAGE_TYPE_2D;
imageTemplateInfo.extent.width = 256; imageTemplateInfo.extent.height = 256; imageTemplateInfo.extent.depth = 1;
imageTemplateInfo.mipLevels = 1;
imageTemplateInfo.arrayLayers = 1;
imageTemplateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
imageTemplateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageTemplateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
imageTemplateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; imageTemplateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
uint32_t bufferMemoryTypeBits = UINT32_MAX;
{
VkBuffer dummyBuffer;
VkResult res = vkCreateBuffer(g_hDevice, &bufferTemplateInfo, g_Allocs, &dummyBuffer);
TEST(res == VK_SUCCESS);
VkMemoryRequirements memReq;
vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq);
bufferMemoryTypeBits = memReq.memoryTypeBits;
vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs);
}
uint32_t imageMemoryTypeBits = UINT32_MAX;
{
VkImage dummyImage;
VkResult res = vkCreateImage(g_hDevice, &imageTemplateInfo, g_Allocs, &dummyImage);
TEST(res == VK_SUCCESS);
VkMemoryRequirements memReq;
vkGetImageMemoryRequirements(g_hDevice, dummyImage, &memReq);
imageMemoryTypeBits = memReq.memoryTypeBits;
vkDestroyImage(g_hDevice, dummyImage, g_Allocs);
}
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
TEST(0);
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.minBlockCount = 1;
poolCreateInfo.maxBlockCount = 1;
poolCreateInfo.blockSize = config.PoolSize;
const VkPhysicalDeviceMemoryProperties* memProps = nullptr;
vmaGetMemoryProperties(g_hAllocator, &memProps);
VmaPool pool = VK_NULL_HANDLE;
VkResult res;
while(memoryTypeBits)
{
VmaAllocationCreateInfo dummyAllocCreateInfo = {};
dummyAllocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
vmaFindMemoryTypeIndex(g_hAllocator, memoryTypeBits, &dummyAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
const uint32_t heapIndex = memProps->memoryTypes[poolCreateInfo.memoryTypeIndex].heapIndex;
if(poolCreateInfo.blockSize * poolCreateInfo.minBlockCount < memProps->memoryHeaps[heapIndex].size)
{
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
if(res == VK_SUCCESS)
break;
}
memoryTypeBits &= ~(1u << poolCreateInfo.memoryTypeIndex);
}
TEST(pool);
time_point timeBeg = std::chrono::high_resolution_clock::now();
auto ThreadProc = [&config, allocationSizeProbabilitySum, pool](
PoolTestThreadResult* outThreadResult,
uint32_t randSeed,
HANDLE frameStartEvent,
HANDLE frameEndEvent) -> void
{
RandomNumberGenerator threadRand{randSeed};
VkResult res = VK_SUCCESS;
VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufferInfo.size = 256; 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; imageInfo.extent.height = 256; imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
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->FailedAllocationCount = 0;
outThreadResult->FailedAllocationTotalSize = 0;
struct Item
{
VkDeviceSize BufferSize = 0;
VkExtent2D ImageSize = { 0, 0 };
VkBuffer Buf = VK_NULL_HANDLE;
VkImage Image = VK_NULL_HANDLE;
VmaAllocation Alloc = VK_NULL_HANDLE;
Item() { }
Item(Item&& src) :
BufferSize(src.BufferSize), ImageSize(src.ImageSize), Buf(src.Buf), Image(src.Image), Alloc(src.Alloc)
{
src.BufferSize = 0;
src.ImageSize = {0, 0};
src.Buf = VK_NULL_HANDLE;
src.Image = VK_NULL_HANDLE;
src.Alloc = VK_NULL_HANDLE;
}
Item(const Item& src) = delete;
~Item()
{
DestroyResources();
}
Item& operator=(Item&& src)
{
if(&src != this)
{
DestroyResources();
BufferSize = src.BufferSize; ImageSize = src.ImageSize;
Buf = src.Buf; Image = src.Image; Alloc = src.Alloc;
src.BufferSize = 0;
src.ImageSize = {0, 0};
src.Buf = VK_NULL_HANDLE;
src.Image = VK_NULL_HANDLE;
src.Alloc = VK_NULL_HANDLE;
}
return *this;
}
Item& operator=(const Item& src) = delete;
void DestroyResources()
{
if(Buf)
{
assert(Image == VK_NULL_HANDLE);
vmaDestroyBuffer(g_hAllocator, Buf, Alloc);
Buf = VK_NULL_HANDLE;
}
else
{
vmaDestroyImage(g_hAllocator, Image, Alloc);
Image = VK_NULL_HANDLE;
}
Alloc = VK_NULL_HANDLE;
}
VkDeviceSize CalcSizeBytes() const
{
return BufferSize +
4ull * ImageSize.width * ImageSize.height;
}
};
std::vector<Item> unusedItems, usedItems;
const size_t threadTotalItemCount = config.TotalItemCount / config.ThreadCount;
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)
{
TEST(allocSize.BufferSizeMin > 0);
TEST(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
{
TEST(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(std::move(item));
}
auto Allocate = [&](Item& item) -> VkResult
{
assert(item.Buf == VK_NULL_HANDLE && item.Image == VK_NULL_HANDLE && item.Alloc == VK_NULL_HANDLE);
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
if(item.BufferSize)
{
bufferInfo.size = item.BufferSize;
VkResult res = VK_SUCCESS;
{
PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocCreateInfo, &item.Buf, &item.Alloc, nullptr);
}
if(res == VK_SUCCESS)
SetDebugUtilsObjectName(VK_OBJECT_TYPE_BUFFER, (uint64_t)item.Buf, "TestPool_Benchmark_Buffer");
return res;
}
else
{
TEST(item.ImageSize.width && item.ImageSize.height);
imageInfo.extent.width = item.ImageSize.width;
imageInfo.extent.height = item.ImageSize.height;
VkResult res = VK_SUCCESS;
{
PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult);
res = vmaCreateImage(g_hAllocator, &imageInfo, &allocCreateInfo, &item.Image, &item.Alloc, nullptr);
}
if(res == VK_SUCCESS)
SetDebugUtilsObjectName(VK_OBJECT_TYPE_IMAGE, (uint64_t)item.Image, "TestPool_Benchmark_Image");
return res;
}
};
for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex)
{
WaitForSingleObject(frameStartEvent, INFINITE);
const size_t bufsToMakeUnused = usedItems.size() * config.ItemsToMakeUnusedPercent / 100;
for(size_t i = 0; i < bufsToMakeUnused; ++i)
{
size_t index = threadRand.Generate() % usedItems.size();
auto it = usedItems.begin() + index;
Item item = std::move(*it);
usedItems.erase(it);
unusedItems.push_back(std::move(item));
}
const size_t usedBufCount = (threadRand.Generate() % (config.UsedItemCountMax - config.UsedItemCountMin) + config.UsedItemCountMin)
/ config.ThreadCount;
TEST(usedBufCount < usedItems.size() + unusedItems.size());
while(usedBufCount < usedItems.size())
{
size_t index = threadRand.Generate() % usedItems.size();
auto it = usedItems.begin() + index;
Item item = std::move(*it);
usedItems.erase(it);
unusedItems.push_back(std::move(item));
}
while(usedBufCount > usedItems.size())
{
size_t index = threadRand.Generate() % unusedItems.size();
auto it = unusedItems.begin() + index;
Item item = std::move(*it);
unusedItems.erase(it);
usedItems.push_back(std::move(item));
}
uint32_t touchExistingCount = 0;
uint32_t touchLostCount = 0;
uint32_t createSucceededCount = 0;
uint32_t createFailedCount = 0;
for(size_t i = 0; i < usedItems.size(); ++i)
{
Item& item = usedItems[i];
if(item.Alloc == VK_NULL_HANDLE)
{
res = Allocate(item);
++outThreadResult->AllocationCount;
if(res != VK_SUCCESS)
{
assert(item.Alloc == VK_NULL_HANDLE && item.Buf == VK_NULL_HANDLE && item.Image == VK_NULL_HANDLE);
++outThreadResult->FailedAllocationCount;
outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes();
++createFailedCount;
}
else
++createSucceededCount;
}
else
{
VmaAllocationInfo allocInfo;
vmaGetAllocationInfo(g_hAllocator, item.Alloc, &allocInfo);
++touchExistingCount;
}
}
SetEvent(frameEndEvent);
}
for(size_t i = usedItems.size(); i--; )
{
PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult);
usedItems[i].DestroyResources();
++outThreadResult->DeallocationCount;
}
for(size_t i = unusedItems.size(); i--; )
{
PoolDeallocationTimeRegisterObj timeRegisterOb(*outThreadResult);
unusedItems[i].DestroyResources();
++outThreadResult->DeallocationCount;
}
};
uint32_t threadRandSeed = mainRand.Generate();
std::vector<HANDLE> frameStartEvents{config.ThreadCount};
std::vector<HANDLE> frameEndEvents{config.ThreadCount};
std::vector<std::thread> bkgThreads;
std::vector<PoolTestThreadResult> 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]));
}
TEST(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);
}
for(size_t i = 0; i < bkgThreads.size(); ++i)
{
bkgThreads[i].join();
CloseHandle(frameEndEvents[i]);
CloseHandle(frameStartEvents[i]);
}
bkgThreads.clear();
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.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;
}
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 TestMemoryUsage()
{
wprintf(L"Testing memory usage:\n");
static const VmaMemoryUsage lastUsage = VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED;
for(uint32_t usage = 0; usage <= lastUsage; ++usage)
{
switch(usage)
{
case VMA_MEMORY_USAGE_UNKNOWN: printf(" VMA_MEMORY_USAGE_UNKNOWN:\n"); break;
case VMA_MEMORY_USAGE_GPU_ONLY: printf(" VMA_MEMORY_USAGE_GPU_ONLY:\n"); break;
case VMA_MEMORY_USAGE_CPU_ONLY: printf(" VMA_MEMORY_USAGE_CPU_ONLY:\n"); break;
case VMA_MEMORY_USAGE_CPU_TO_GPU: printf(" VMA_MEMORY_USAGE_CPU_TO_GPU:\n"); break;
case VMA_MEMORY_USAGE_GPU_TO_CPU: printf(" VMA_MEMORY_USAGE_GPU_TO_CPU:\n"); break;
case VMA_MEMORY_USAGE_CPU_COPY: printf(" VMA_MEMORY_USAGE_CPU_COPY:\n"); break;
case VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED: printf(" VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED:\n"); break;
default: assert(0);
}
auto printResult = [](const char* testName, VkResult res, uint32_t memoryTypeBits, uint32_t memoryTypeIndex)
{
if(res == VK_SUCCESS)
printf(" %s: memoryTypeBits=0x%X, memoryTypeIndex=%u\n", testName, memoryTypeBits, memoryTypeIndex);
else
printf(" %s: memoryTypeBits=0x%X, FAILED with res=%d\n", testName, memoryTypeBits, (int32_t)res);
};
{
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = 65536;
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VkBuffer buf = VK_NULL_HANDLE;
VkResult res = vkCreateBuffer(g_hDevice, &bufCreateInfo, g_Allocs, &buf);
TEST(res == VK_SUCCESS && buf != VK_NULL_HANDLE);
VkMemoryRequirements memReq = {};
vkGetBufferMemoryRequirements(g_hDevice, buf, &memReq);
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = (VmaMemoryUsage)usage;
VmaAllocation alloc = VK_NULL_HANDLE;
VmaAllocationInfo allocInfo = {};
res = vmaAllocateMemoryForBuffer(g_hAllocator, buf, &allocCreateInfo, &alloc, &allocInfo);
if(res == VK_SUCCESS)
{
TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0);
res = vkBindBufferMemory(g_hDevice, buf, allocInfo.deviceMemory, allocInfo.offset);
TEST(res == VK_SUCCESS);
}
printResult("Buffer TRANSFER_DST + TRANSFER_SRC", res, memReq.memoryTypeBits, allocInfo.memoryType);
vmaDestroyBuffer(g_hAllocator, buf, alloc);
}
{
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = 65536;
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
VkBuffer buf = VK_NULL_HANDLE;
VkResult res = vkCreateBuffer(g_hDevice, &bufCreateInfo, g_Allocs, &buf);
TEST(res == VK_SUCCESS && buf != VK_NULL_HANDLE);
VkMemoryRequirements memReq = {};
vkGetBufferMemoryRequirements(g_hDevice, buf, &memReq);
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = (VmaMemoryUsage)usage;
VmaAllocation alloc = VK_NULL_HANDLE;
VmaAllocationInfo allocInfo = {};
res = vmaAllocateMemoryForBuffer(g_hAllocator, buf, &allocCreateInfo, &alloc, &allocInfo);
if(res == VK_SUCCESS)
{
TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0);
res = vkBindBufferMemory(g_hDevice, buf, allocInfo.deviceMemory, allocInfo.offset);
TEST(res == VK_SUCCESS);
}
printResult("Buffer TRANSFER_DST + VERTEX_BUFFER", res, memReq.memoryTypeBits, allocInfo.memoryType);
vmaDestroyBuffer(g_hAllocator, buf, alloc);
}
{
VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imgCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imgCreateInfo.extent.width = 256;
imgCreateInfo.extent.height = 256;
imgCreateInfo.extent.depth = 1;
imgCreateInfo.mipLevels = 1;
imgCreateInfo.arrayLayers = 1;
imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
VkImage img = VK_NULL_HANDLE;
VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img);
TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE);
VkMemoryRequirements memReq = {};
vkGetImageMemoryRequirements(g_hDevice, img, &memReq);
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = (VmaMemoryUsage)usage;
VmaAllocation alloc = VK_NULL_HANDLE;
VmaAllocationInfo allocInfo = {};
res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo);
if(res == VK_SUCCESS)
{
TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0);
res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset);
TEST(res == VK_SUCCESS);
}
printResult("Image OPTIMAL TRANSFER_DST + TRANSFER_SRC", res, memReq.memoryTypeBits, allocInfo.memoryType);
vmaDestroyImage(g_hAllocator, img, alloc);
}
{
VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imgCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imgCreateInfo.extent.width = 256;
imgCreateInfo.extent.height = 256;
imgCreateInfo.extent.depth = 1;
imgCreateInfo.mipLevels = 1;
imgCreateInfo.arrayLayers = 1;
imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
VkImage img = VK_NULL_HANDLE;
VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img);
TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE);
VkMemoryRequirements memReq = {};
vkGetImageMemoryRequirements(g_hDevice, img, &memReq);
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = (VmaMemoryUsage)usage;
VmaAllocation alloc = VK_NULL_HANDLE;
VmaAllocationInfo allocInfo = {};
res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo);
if(res == VK_SUCCESS)
{
TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0);
res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset);
TEST(res == VK_SUCCESS);
}
printResult("Image OPTIMAL TRANSFER_DST + SAMPLED", res, memReq.memoryTypeBits, allocInfo.memoryType);
vmaDestroyImage(g_hAllocator, img, alloc);
}
{
VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imgCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imgCreateInfo.extent.width = 256;
imgCreateInfo.extent.height = 256;
imgCreateInfo.extent.depth = 1;
imgCreateInfo.mipLevels = 1;
imgCreateInfo.arrayLayers = 1;
imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imgCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
VkImage img = VK_NULL_HANDLE;
VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img);
TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE);
VkMemoryRequirements memReq = {};
vkGetImageMemoryRequirements(g_hDevice, img, &memReq);
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = (VmaMemoryUsage)usage;
VmaAllocation alloc = VK_NULL_HANDLE;
VmaAllocationInfo allocInfo = {};
res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo);
if(res == VK_SUCCESS)
{
TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0);
res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset);
TEST(res == VK_SUCCESS);
}
printResult("Image OPTIMAL SAMPLED + COLOR_ATTACHMENT", res, memReq.memoryTypeBits, allocInfo.memoryType);
vmaDestroyImage(g_hAllocator, img, alloc);
}
}
}
static uint32_t FindDeviceCoherentMemoryTypeBits()
{
VkPhysicalDeviceMemoryProperties memProps;
vkGetPhysicalDeviceMemoryProperties(g_hPhysicalDevice, &memProps);
uint32_t memTypeBits = 0;
for(uint32_t i = 0; i < memProps.memoryTypeCount; ++i)
{
if(memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD)
memTypeBits |= 1u << i;
}
return memTypeBits;
}
static void TestDeviceCoherentMemory()
{
if(!VK_AMD_device_coherent_memory_enabled)
return;
uint32_t deviceCoherentMemoryTypeBits = FindDeviceCoherentMemoryTypeBits();
if(deviceCoherentMemoryTypeBits == 0)
return;
wprintf(L"Testing device coherent memory...\n");
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = 0x10000;
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD;
AllocInfo alloc = {};
VmaAllocationInfo allocInfo = {};
VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &alloc.m_Buffer, &alloc.m_Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
TEST((1u << allocInfo.memoryType) & deviceCoherentMemoryTypeBits);
alloc.Destroy();
{
VmaPoolCreateInfo poolCreateInfo = {};
res = vmaFindMemoryTypeIndex(g_hAllocator, UINT32_MAX, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex);
TEST(res == VK_SUCCESS);
TEST((1u << poolCreateInfo.memoryTypeIndex) & deviceCoherentMemoryTypeBits);
VmaPool pool = VK_NULL_HANDLE;
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
TEST(res == VK_SUCCESS);
vmaDestroyPool(g_hAllocator, pool);
}
VmaAllocatorCreateInfo allocatorCreateInfo = {};
SetAllocatorCreateInfo(allocatorCreateInfo);
allocatorCreateInfo.flags &= ~VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT;
VmaAllocator localAllocator = VK_NULL_HANDLE;
res = vmaCreateAllocator(&allocatorCreateInfo, &localAllocator);
TEST(res == VK_SUCCESS && localAllocator);
res = vmaCreateBuffer(localAllocator, &bufCreateInfo, &allocCreateInfo, &alloc.m_Buffer, &alloc.m_Allocation, &allocInfo);
TEST(res != VK_SUCCESS && !alloc.m_Buffer && !alloc.m_Allocation);
{
uint32_t memTypeIndex = UINT_MAX;
res = vmaFindMemoryTypeIndex(localAllocator, UINT32_MAX, &allocCreateInfo, &memTypeIndex);
TEST(res != VK_SUCCESS);
}
vmaDestroyAllocator(localAllocator);
}
static void TestBudget()
{
wprintf(L"Testing budget...\n");
static const VkDeviceSize BUF_SIZE = 10ull * 1024 * 1024;
static const uint32_t BUF_COUNT = 4;
const VkPhysicalDeviceMemoryProperties* memProps = {};
vmaGetMemoryProperties(g_hAllocator, &memProps);
for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
{
vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex);
VmaBudget budgetBeg[VK_MAX_MEMORY_HEAPS] = {};
vmaGetHeapBudgets(g_hAllocator, budgetBeg);
for(uint32_t i = 0; i < memProps->memoryHeapCount; ++i)
{
TEST(budgetBeg[i].budget > 0);
TEST(budgetBeg[i].budget <= memProps->memoryHeaps[i].size);
TEST(budgetBeg[i].statistics.allocationBytes <= budgetBeg[i].statistics.blockBytes);
}
VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufInfo.size = BUF_SIZE;
bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
if(testIndex == 0)
{
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
}
uint32_t heapIndex = 0;
BufferInfo bufInfos[BUF_COUNT] = {};
for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex)
{
VmaAllocationInfo allocInfo;
VkResult res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
&bufInfos[bufIndex].Buffer, &bufInfos[bufIndex].Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
if(bufIndex == 0)
{
heapIndex = MemoryTypeToHeap(allocInfo.memoryType);
}
else
{
TEST(MemoryTypeToHeap(allocInfo.memoryType) == heapIndex);
}
}
VmaBudget budgetWithBufs[VK_MAX_MEMORY_HEAPS] = {};
vmaGetHeapBudgets(g_hAllocator, budgetWithBufs);
for(size_t bufIndex = BUF_COUNT; bufIndex--; )
{
vmaDestroyBuffer(g_hAllocator, bufInfos[bufIndex].Buffer, bufInfos[bufIndex].Allocation);
}
VmaBudget budgetEnd[VK_MAX_MEMORY_HEAPS] = {};
vmaGetHeapBudgets(g_hAllocator, budgetEnd);
for(uint32_t i = 0; i < memProps->memoryHeapCount; ++i)
{
TEST(budgetEnd[i].statistics.allocationBytes <= budgetEnd[i].statistics.blockBytes);
if(i == heapIndex)
{
TEST(budgetEnd[i].statistics.allocationBytes == budgetBeg[i].statistics.allocationBytes);
TEST(budgetWithBufs[i].statistics.allocationBytes == budgetBeg[i].statistics.allocationBytes + BUF_SIZE * BUF_COUNT);
TEST(budgetWithBufs[i].statistics.blockBytes >= budgetEnd[i].statistics.blockBytes);
}
else
{
TEST(budgetEnd[i].statistics.allocationBytes == budgetEnd[i].statistics.allocationBytes &&
budgetEnd[i].statistics.allocationBytes == budgetWithBufs[i].statistics.allocationBytes);
TEST(budgetEnd[i].statistics.blockBytes == budgetEnd[i].statistics.blockBytes &&
budgetEnd[i].statistics.blockBytes == budgetWithBufs[i].statistics.blockBytes);
}
}
}
}
static void TestAliasing()
{
wprintf(L"Testing aliasing...\n");
VkImageCreateInfo img1CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
img1CreateInfo.imageType = VK_IMAGE_TYPE_2D;
img1CreateInfo.extent.width = 512;
img1CreateInfo.extent.height = 512;
img1CreateInfo.extent.depth = 1;
img1CreateInfo.mipLevels = 10;
img1CreateInfo.arrayLayers = 1;
img1CreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
img1CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
img1CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
img1CreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
img1CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
VkImageCreateInfo img2CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
img2CreateInfo.imageType = VK_IMAGE_TYPE_2D;
img2CreateInfo.extent.width = 1920;
img2CreateInfo.extent.height = 1080;
img2CreateInfo.extent.depth = 1;
img2CreateInfo.mipLevels = 1;
img2CreateInfo.arrayLayers = 1;
img2CreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
img2CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
img2CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
img2CreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
img2CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
VkImage img1 = VK_NULL_HANDLE;
ERR_GUARD_VULKAN(vkCreateImage(g_hDevice, &img1CreateInfo, g_Allocs, &img1));
VkImage img2 = VK_NULL_HANDLE;
ERR_GUARD_VULKAN(vkCreateImage(g_hDevice, &img2CreateInfo, g_Allocs, &img2));
VkMemoryRequirements img1MemReq = {};
vkGetImageMemoryRequirements(g_hDevice, img1, &img1MemReq);
VkMemoryRequirements img2MemReq = {};
vkGetImageMemoryRequirements(g_hDevice, img2, &img2MemReq);
VkMemoryRequirements finalMemReq = {};
finalMemReq.size = std::max(img1MemReq.size, img2MemReq.size);
finalMemReq.alignment = std::max(img1MemReq.alignment, img2MemReq.alignment);
finalMemReq.memoryTypeBits = img1MemReq.memoryTypeBits & img2MemReq.memoryTypeBits;
if(finalMemReq.memoryTypeBits != 0)
{
wprintf(L" size: max(%llu, %llu) = %llu\n",
img1MemReq.size, img2MemReq.size, finalMemReq.size);
wprintf(L" alignment: max(%llu, %llu) = %llu\n",
img1MemReq.alignment, img2MemReq.alignment, finalMemReq.alignment);
wprintf(L" memoryTypeBits: %u & %u = %u\n",
img1MemReq.memoryTypeBits, img2MemReq.memoryTypeBits, finalMemReq.memoryTypeBits);
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
VmaAllocation alloc = VK_NULL_HANDLE;
ERR_GUARD_VULKAN(vmaAllocateMemory(g_hAllocator, &finalMemReq, &allocCreateInfo, &alloc, nullptr));
ERR_GUARD_VULKAN(vmaBindImageMemory(g_hAllocator, alloc, img1));
ERR_GUARD_VULKAN(vmaBindImageMemory(g_hAllocator, alloc, img2));
vmaFreeMemory(g_hAllocator, alloc);
}
else
{
wprintf(L" Textures cannot alias!\n");
}
vkDestroyImage(g_hDevice, img2, g_Allocs);
vkDestroyImage(g_hDevice, img1, g_Allocs);
}
static void TestAllocationAliasing()
{
wprintf(L"Testing allocation aliasing...\n");
VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
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_SAMPLED_BIT;
VmaAllocationCreateInfo allocationCreateInfo = {};
allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
allocationCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocationCreateInfo.flags |= VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT;
{
VkImage originalImage;
VmaAllocation allocation;
imageInfo.extent.width = 640;
imageInfo.extent.height = 480;
VkResult res = vmaCreateImage(g_hAllocator, &imageInfo, &allocationCreateInfo, &originalImage, &allocation, nullptr);
TEST(res == VK_SUCCESS);
VkImage aliasingImage;
imageInfo.extent.width = 480;
imageInfo.extent.height = 256;
res = vkCreateImage(g_hDevice, &imageInfo, g_Allocs, &aliasingImage);
TEST(res == VK_SUCCESS);
res = vmaBindImageMemory(g_hAllocator, allocation, aliasingImage);
TEST(res == VK_SUCCESS);
vkDestroyImage(g_hDevice, aliasingImage, g_Allocs);
vmaDestroyImage(g_hAllocator, originalImage, allocation);
}
allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT;
VkBufferCreateInfo bufCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT;
bufCreateInfo.size = 300 * MEGABYTE;
{
VkBuffer origBuf;
VmaAllocation alloc;
VmaAllocationInfo allocInfo;
VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocationCreateInfo, &origBuf, &alloc, &allocInfo);
TEST(res == VK_SUCCESS && origBuf && alloc);
TEST(allocInfo.offset == 0);
VkBuffer aliasingBuf;
bufCreateInfo.size = 200 * MEGABYTE;
res = vmaCreateAliasingBuffer(g_hAllocator, alloc, &bufCreateInfo, &aliasingBuf);
TEST(res == VK_SUCCESS && aliasingBuf);
vkDestroyBuffer(g_hDevice, aliasingBuf, g_Allocs);
vmaDestroyBuffer(g_hAllocator, origBuf, alloc);
}
}
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)
{
TEST(memTypeIndex != UINT32_MAX);
VmaPoolCreateInfo poolInfo = {};
poolInfo.memoryTypeIndex = memTypeIndex;
res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
TEST(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_AUTO;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
allocCreateInfo.pool = pool;
if(testIndex == TEST_DEDICATED)
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
VmaAllocationInfo allocInfo;
BufferInfo bufferInfos[3];
for(size_t i = 0; i < 2; ++i)
{
res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
&bufferInfos[i].Buffer, &bufferInfos[i].Allocation, &allocInfo);
TEST(res == VK_SUCCESS);
TEST(allocInfo.pMappedData == nullptr);
memTypeIndex = allocInfo.memoryType;
}
char* data00 = nullptr;
res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data00);
TEST(res == VK_SUCCESS && data00 != nullptr);
data00[0xFFFF] = data00[0];
char* data01 = nullptr;
res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data01);
TEST(res == VK_SUCCESS && data01 == data00);
char* data1 = nullptr;
res = vmaMapMemory(g_hAllocator, bufferInfos[1].Allocation, (void**)&data1);
TEST(res == VK_SUCCESS && data1 != nullptr);
TEST(!MemoryRegionsOverlap(data00, (size_t)bufInfo.size, data1, (size_t)bufInfo.size));
data1[0xFFFF] = data1[0];
vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation);
vmaGetAllocationInfo(g_hAllocator, bufferInfos[0].Allocation, &allocInfo);
TEST(allocInfo.pMappedData == nullptr);
vmaUnmapMemory(g_hAllocator, bufferInfos[1].Allocation);
vmaGetAllocationInfo(g_hAllocator, bufferInfos[1].Allocation, &allocInfo);
TEST(allocInfo.pMappedData == nullptr);
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo,
&bufferInfos[2].Buffer, &bufferInfos[2].Allocation, &allocInfo);
TEST(res == VK_SUCCESS && allocInfo.pMappedData != nullptr);
char* data2 = nullptr;
res = vmaMapMemory(g_hAllocator, bufferInfos[2].Allocation, (void**)&data2);
TEST(res == VK_SUCCESS && data2 == allocInfo.pMappedData);
data2[0xFFFF] = data2[0];
vmaUnmapMemory(g_hAllocator, bufferInfos[2].Allocation);
vmaGetAllocationInfo(g_hAllocator, bufferInfos[2].Allocation, &allocInfo);
TEST(allocInfo.pMappedData == data2);
for(size_t i = 3; i--; )
vmaDestroyBuffer(g_hAllocator, bufferInfos[i].Buffer, bufferInfos[i].Allocation);
vmaDestroyPool(g_hAllocator, pool);
}
}
static void TestDeviceLocalMapped()
{
VkResult res;
for(uint32_t testIndex = 0; testIndex < 2; ++testIndex)
{
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
bufCreateInfo.size = 4096;
VmaPool pool = VK_NULL_HANDLE;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
if(testIndex == 1)
{
VmaPoolCreateInfo poolCreateInfo = {};
res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex);
TEST(res == VK_SUCCESS);
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
TEST(res == VK_SUCCESS);
allocCreateInfo.pool = pool;
}
VkBuffer buf = VK_NULL_HANDLE;
VmaAllocation alloc = VK_NULL_HANDLE;
VmaAllocationInfo allocInfo = {};
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo);
TEST(res == VK_SUCCESS && alloc);
VkMemoryPropertyFlags memTypeFlags = 0;
vmaGetMemoryTypeProperties(g_hAllocator, allocInfo.memoryType, &memTypeFlags);
const bool shouldBeMapped = (memTypeFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0;
TEST((allocInfo.pMappedData != nullptr) == shouldBeMapped);
vmaDestroyBuffer(g_hAllocator, buf, alloc);
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)
{
TEST(memTypeIndex != UINT32_MAX);
VmaPoolCreateInfo poolInfo = {};
poolInfo.memoryTypeIndex = memTypeIndex;
res = vmaCreatePool(g_hAllocator, &poolInfo, &pool);
TEST(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_AUTO;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
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](){
RandomNumberGenerator rand{threadIndex};
enum class MODE
{
DONT_MAP,
MAP_FOR_MOMENT,
MAP_FOR_LONGER,
MAP_TWO_TIMES,
PERSISTENTLY_MAPPED,
COUNT
};
std::vector<BufferInfo> bufInfos{threadBufferCount};
std::vector<MODE> 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);
TEST(res == VK_SUCCESS);
if(memTypeIndex == UINT32_MAX)
memTypeIndex = allocInfo.memoryType;
char* data = nullptr;
if(mode == MODE::PERSISTENTLY_MAPPED)
{
data = (char*)allocInfo.pMappedData;
TEST(data != nullptr);
}
else if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_FOR_LONGER ||
mode == MODE::MAP_TWO_TIMES)
{
TEST(data == nullptr);
res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data);
TEST(res == VK_SUCCESS && data != nullptr);
if(mode == MODE::MAP_TWO_TIMES)
{
char* data2 = nullptr;
res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data2);
TEST(res == VK_SUCCESS && data2 == data);
}
}
else if(mode == MODE::DONT_MAP)
{
TEST(allocInfo.pMappedData == nullptr);
}
else
TEST(0);
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)
TEST(allocInfo.pMappedData == nullptr);
else
TEST(allocInfo.pMappedData == data);
}
switch(rand.Generate() % 3)
{
case 0: Sleep(0); break; case 1: Sleep(10); break; }
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);
TEST(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,Time,"
"Threads,Buffers and images,Sizes,Operations,Allocation strategy,Free order,"
"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);
std::string currTime;
CurrentTimeToStr(currTime);
fprintf(file,
"%s,%s,%s,"
"%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n",
codeDescription,
currTime.c_str(),
testDescription,
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),"
"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);
std::string currTime;
CurrentTimeToStr(currTime);
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\n",
codeDescription,
testDescription,
currTime.c_str(),
config.ThreadCount,
(unsigned long long)config.PoolSize,
config.FrameCount,
config.TotalItemCount,
config.UsedItemCountMin,
config.UsedItemCountMax,
config.ItemsToMakeUnusedPercent,
totalTimeSeconds * 1e6f,
allocationTimeMinSeconds * 1e6f,
allocationTimeAvgSeconds * 1e6f,
allocationTimeMaxSeconds * 1e6f,
deallocationTimeMinSeconds * 1e6f,
deallocationTimeAvgSeconds * 1e6f,
deallocationTimeMaxSeconds * 1e6f,
result.FailedAllocationCount,
result.FailedAllocationTotalSize);
}
static void PerformCustomMainTest(FILE* file)
{
Config config{};
config.RandSeed = 65735476;
config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; config.MemUsageProbability[0] = 1; config.FreeOrder = FREE_ORDER::FORWARD;
config.ThreadCount = 16;
config.ThreadsUsingCommonAllocationsProbabilityPercent = 50;
config.AllocationStrategy = 0;
config.AllocationSizes.push_back({4, 0x10000, 0xA00000});
config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100;
config.AdditionalOperationCount = 1024;
Result result{};
VkResult res = MainTest(result, config);
TEST(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;
PoolTestResult result = {};
TestPool_Benchmark(result, config);
WritePoolTestResult(file, "Code desc", "Test desc", config, result);
}
static void PerformMainTests(FILE* file)
{
wprintf(L"MAIN TESTS:\n");
uint32_t repeatCount = 1;
if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3;
Config config{};
config.RandSeed = 65735476;
config.MemUsageProbability[0] = 1; 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);
}
const size_t strategyCount = GetAllocationStrategyCount();
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);
}
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);
}
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; else
config.MaxBytesToAllocate = 4ull * 1024 * 1024;
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();
if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
{
if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
{
if(constantSizesIndex == 0)
config.AllocationSizes.push_back({4, 16, 1024});
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});
}
}
if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
{
if(constantSizesIndex == 0)
config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); 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});
}
}
}
if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
{
if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
{
if(constantSizesIndex == 0)
config.AllocationSizes.push_back({4, 0, 0, 4, 32});
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});
}
}
if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
{
if(constantSizesIndex == 0)
config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
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});
}
}
}
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);
}
for(size_t strategyIndex = 0; strategyIndex < strategyCount; ++strategyIndex)
{
std::string desc6 = desc5;
switch(strategyIndex)
{
case 0:
desc6 += ",MinMemory";
config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT;
break;
case 1:
desc6 += ",MinTime";
config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT;
break;
default:
assert(0);
}
desc6 += ',';
desc6 += FREE_ORDER_NAMES[(uint32_t)config.FreeOrder];
const char* testDescription = desc6.c_str();
for(size_t repeat = 0; repeat < repeatCount; ++repeat)
{
printf("%s #%u\n", testDescription, (uint32_t)repeat);
Result result{};
VkResult res = MainTest(result, config);
TEST(res == VK_SUCCESS);
if(file)
{
WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
}
}
}
}
}
}
}
}
}
static void PerformPoolTests(FILE* file)
{
wprintf(L"POOL TESTS:\n");
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);
}
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);
}
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; else
config.PoolSize = 4ull * 1024 * 1024;
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();
if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2)
{
if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
{
if(constantSizesIndex == 0)
config.AllocationSizes.push_back({4, 16, 1024});
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});
}
}
if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
{
if(constantSizesIndex == 0)
config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); 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});
}
}
}
if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2)
{
if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2)
{
if(constantSizesIndex == 0)
config.AllocationSizes.push_back({4, 0, 0, 4, 32});
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});
}
}
if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2)
{
if(constantSizesIndex == 0)
config.AllocationSizes.push_back({4, 0, 0, 256, 2048});
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;
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 #%u\n", testDescription, (uint32_t)repeat);
PoolTestResult result{};
TestPool_Benchmark(result, config);
WritePoolTestResult(file, CODE_DESCRIPTION, testDescription, config, result);
}
}
}
}
}
}
}
static void BasicTestTLSF()
{
wprintf(L"Basic test TLSF\n");
VmaVirtualBlock block;
VmaVirtualBlockCreateInfo blockInfo = {};
blockInfo.flags = 0;
blockInfo.size = 50331648;
vmaCreateVirtualBlock(&blockInfo, &block);
VmaVirtualAllocationCreateInfo info = {};
info.alignment = 2;
VmaVirtualAllocation allocation[3] = {};
info.size = 576;
vmaVirtualAllocate(block, &info, allocation + 0, nullptr);
info.size = 648;
vmaVirtualAllocate(block, &info, allocation + 1, nullptr);
vmaVirtualFree(block, allocation[0]);
info.size = 720;
vmaVirtualAllocate(block, &info, allocation + 2, nullptr);
vmaVirtualFree(block, allocation[1]);
vmaVirtualFree(block, allocation[2]);
vmaDestroyVirtualBlock(block);
}
#if 0#endif
static void BasicTestAllocatePages()
{
wprintf(L"Basic test allocate pages\n");
RandomNumberGenerator rand{765461};
VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
sampleBufCreateInfo.size = 1024; sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo sampleAllocCreateInfo = {};
sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
sampleAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
VmaPoolCreateInfo poolCreateInfo = {};
VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
TEST(res == VK_SUCCESS);
poolCreateInfo.blockSize = 1024 * 1024;
poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
VmaPool pool = nullptr;
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
TEST(res == VK_SUCCESS);
VkMemoryRequirements memReq;
memReq.memoryTypeBits = UINT32_MAX;
memReq.alignment = 4 * 1024;
memReq.size = 4 * 1024;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
allocCreateInfo.pool = pool;
constexpr uint32_t allocCount = 100;
std::vector<VmaAllocation> alloc{allocCount};
std::vector<VmaAllocationInfo> allocInfo{allocCount};
res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), allocInfo.data());
TEST(res == VK_SUCCESS);
for(uint32_t i = 0; i < allocCount; ++i)
{
TEST(alloc[i] != VK_NULL_HANDLE &&
allocInfo[i].pMappedData != nullptr &&
allocInfo[i].deviceMemory == allocInfo[0].deviceMemory &&
allocInfo[i].memoryType == allocInfo[0].memoryType);
}
vmaFreeMemoryPages(g_hAllocator, allocCount, alloc.data());
std::fill(alloc.begin(), alloc.end(), nullptr);
std::fill(allocInfo.begin(), allocInfo.end(), VmaAllocationInfo{});
memReq.size = 100 * 1024;
res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), nullptr);
TEST(res != VK_SUCCESS);
TEST(std::find_if(alloc.begin(), alloc.end(), [](VmaAllocation alloc){ return alloc != VK_NULL_HANDLE; }) == alloc.end());
memReq.size = 4 * 1024;
memReq.alignment = 128 * 1024;
res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), allocInfo.data());
TEST(res != VK_SUCCESS);
memReq.alignment = 4 * 1024;
memReq.size = 4 * 1024;
VmaAllocationCreateInfo dedicatedAllocCreateInfo = {};
dedicatedAllocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
dedicatedAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &dedicatedAllocCreateInfo, allocCount, alloc.data(), allocInfo.data());
TEST(res == VK_SUCCESS);
for(uint32_t i = 0; i < allocCount; ++i)
{
TEST(alloc[i] != VK_NULL_HANDLE &&
allocInfo[i].pMappedData != nullptr &&
allocInfo[i].memoryType == allocInfo[0].memoryType &&
allocInfo[i].offset == 0);
if(i > 0)
{
TEST(allocInfo[i].deviceMemory != allocInfo[0].deviceMemory);
}
}
vmaFreeMemoryPages(g_hAllocator, allocCount, alloc.data());
std::fill(alloc.begin(), alloc.end(), nullptr);
std::fill(allocInfo.begin(), allocInfo.end(), VmaAllocationInfo{});
vmaDestroyPool(g_hAllocator, pool);
}
static void TestGpuData()
{
RandomNumberGenerator rand = { 53434 };
std::vector<AllocInfo> allocInfo;
for(size_t i = 0; i < 100; ++i)
{
AllocInfo info = {};
info.m_BufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
info.m_BufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
info.m_BufferInfo.size = 1024 * 1024 * (rand.Generate() % 9 + 1);
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
VkResult res = vmaCreateBuffer(g_hAllocator, &info.m_BufferInfo, &allocCreateInfo, &info.m_Buffer, &info.m_Allocation, nullptr);
TEST(res == VK_SUCCESS);
info.m_StartValue = rand.Generate();
allocInfo.push_back(std::move(info));
}
UploadGpuData(allocInfo.data(), allocInfo.size());
ValidateGpuData(allocInfo.data(), allocInfo.size());
DestroyAllAllocations(allocInfo);
}
static void TestVirtualBlocksAlgorithmsBenchmark()
{
wprintf(L"Benchmark virtual blocks algorithms\n");
wprintf(L"Alignment,Algorithm,Strategy,Alloc time ms,Random operation time ms,Free time ms\n");
const size_t ALLOCATION_COUNT = 7200;
const uint32_t MAX_ALLOC_SIZE = 2056;
const size_t RANDOM_OPERATION_COUNT = ALLOCATION_COUNT * 2;
VmaVirtualBlockCreateInfo blockCreateInfo = {};
blockCreateInfo.pAllocationCallbacks = g_Allocs;
blockCreateInfo.size = 0;
RandomNumberGenerator rand{ 20092010 };
uint32_t allocSizes[ALLOCATION_COUNT];
for (size_t i = 0; i < ALLOCATION_COUNT; ++i)
{
allocSizes[i] = rand.Generate() % MAX_ALLOC_SIZE + 1;
blockCreateInfo.size += allocSizes[i];
}
blockCreateInfo.size = static_cast<VkDeviceSize>(blockCreateInfo.size * 2.5);
for (uint8_t alignmentIndex = 0; alignmentIndex < 4; ++alignmentIndex)
{
VkDeviceSize alignment;
switch (alignmentIndex)
{
case 0: alignment = 1; break;
case 1: alignment = 16; break;
case 2: alignment = 64; break;
case 3: alignment = 256; break;
default: assert(0); break;
}
for (uint8_t allocStrategyIndex = 0; allocStrategyIndex < 3; ++allocStrategyIndex)
{
VmaVirtualAllocationCreateFlags allocFlags;
switch (allocStrategyIndex)
{
case 0: allocFlags = 0; break;
case 1: allocFlags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT; break;
case 2: allocFlags = VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; break;
default: assert(0);
}
for (uint8_t algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex)
{
switch (algorithmIndex)
{
case 0:
blockCreateInfo.flags = (VmaVirtualBlockCreateFlagBits)0;
break;
case 1:
blockCreateInfo.flags = VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT;
break;
default:
assert(0);
}
std::vector<VmaVirtualAllocation> allocs;
allocs.reserve(ALLOCATION_COUNT + RANDOM_OPERATION_COUNT);
allocs.resize(ALLOCATION_COUNT);
VmaVirtualBlock block;
TEST(vmaCreateVirtualBlock(&blockCreateInfo, &block) == VK_SUCCESS && block);
time_point timeBegin = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < ALLOCATION_COUNT; ++i)
{
VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.size = allocSizes[i];
allocCreateInfo.alignment = alignment;
allocCreateInfo.flags = allocFlags;
TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocs[i], nullptr) == VK_SUCCESS);
TEST(allocs[i] != VK_NULL_HANDLE);
}
duration allocDuration = std::chrono::high_resolution_clock::now() - timeBegin;
timeBegin = std::chrono::high_resolution_clock::now();
for (size_t opIndex = 0; opIndex < RANDOM_OPERATION_COUNT; ++opIndex)
{
if(rand.Generate() % 2)
{
VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.size = rand.Generate() % MAX_ALLOC_SIZE + 1;
allocCreateInfo.alignment = alignment;
allocCreateInfo.flags = allocFlags;
VmaVirtualAllocation alloc;
TEST(vmaVirtualAllocate(block, &allocCreateInfo, &alloc, nullptr) == VK_SUCCESS);
TEST(alloc != VK_NULL_HANDLE);
allocs.push_back(alloc);
}
else
{
size_t index = rand.Generate() % allocs.size();
vmaVirtualFree(block, allocs[index]);
if(index < allocs.size())
allocs[index] = allocs.back();
allocs.pop_back();
}
}
duration randomDuration = std::chrono::high_resolution_clock::now() - timeBegin;
timeBegin = std::chrono::high_resolution_clock::now();
for (size_t i = ALLOCATION_COUNT; i;)
vmaVirtualFree(block, allocs[--i]);
duration freeDuration = std::chrono::high_resolution_clock::now() - timeBegin;
vmaDestroyVirtualBlock(block);
printf("%llu,%s,%s,%g,%g,%g\n",
alignment,
VirtualAlgorithmToStr(blockCreateInfo.flags),
GetVirtualAllocationStrategyName(allocFlags),
ToFloatSeconds(allocDuration) * 1000.f,
ToFloatSeconds(randomDuration) * 1000.f,
ToFloatSeconds(freeDuration) * 1000.f);
}
}
}
}
static void TestMappingHysteresis()
{
wprintf(L"Test mapping hysteresis\n");
VkBufferCreateInfo bufCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufCreateInfo.size = 0x10000;
VmaAllocationCreateInfo templateAllocCreateInfo = {};
templateAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
templateAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.blockSize = 10 * MEGABYTE;
poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
TEST(vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator,
&bufCreateInfo, &templateAllocCreateInfo, &poolCreateInfo.memoryTypeIndex) == VK_SUCCESS);
constexpr uint32_t BUF_COUNT = 30;
bool endOfScenarios = false;
for(uint32_t scenarioIndex = 0; !endOfScenarios; ++scenarioIndex)
{
VmaPool pool;
TEST(vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) == VK_SUCCESS);
BufferInfo buf;
VmaAllocationInfo allocInfo;
std::vector<BufferInfo> bufs;
if(scenarioIndex == 0)
{
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex)
{
TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
TEST(allocInfo.pMappedData == nullptr);
vmaDestroyBuffer(g_hAllocator, buf.Buffer, buf.Allocation);
}
}
else if(scenarioIndex == 1)
{
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
TEST(allocInfo.pMappedData != nullptr);
bufs.push_back(buf);
for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex)
{
TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
TEST(allocInfo.pMappedData != nullptr);
vmaDestroyBuffer(g_hAllocator, buf.Buffer, buf.Allocation);
}
}
else if(scenarioIndex == 2)
{
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex)
{
TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
TEST(allocInfo.pMappedData != nullptr);
vmaDestroyBuffer(g_hAllocator, buf.Buffer, buf.Allocation);
}
}
else if(scenarioIndex == 3)
{
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
for(uint32_t i = 0; i < BUF_COUNT; ++i)
{
void* mappedData = nullptr;
TEST(vmaMapMemory(g_hAllocator, buf.Allocation, &mappedData) == VK_SUCCESS);
TEST(mappedData != nullptr);
vmaUnmapMemory(g_hAllocator, buf.Allocation);
}
vmaDestroyBuffer(g_hAllocator, buf.Buffer, buf.Allocation);
}
else if(scenarioIndex == 4)
{
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex)
{
TEST(vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf.Buffer, &buf.Allocation, &allocInfo) == VK_SUCCESS);
TEST(allocInfo.pMappedData == nullptr);
bufs.push_back(buf);
}
for(uint32_t i = 0; i < BUF_COUNT; ++i)
{
void* mappedData = nullptr;
TEST(vmaMapMemory(g_hAllocator, buf.Allocation, &mappedData) == VK_SUCCESS);
TEST(mappedData != nullptr);
vmaUnmapMemory(g_hAllocator, buf.Allocation);
}
}
else
endOfScenarios = true;
for(size_t i = bufs.size(); i--; )
vmaDestroyBuffer(g_hAllocator, bufs[i].Buffer, bufs[i].Allocation);
vmaDestroyPool(g_hAllocator, pool);
}
}
void Test()
{
wprintf(L"TESTING:\n");
if(false)
{
return;
}
#if VMA_DEBUG_MARGIN
TestDebugMargin();
TestDebugMarginNotInVirtualAllocator();
#else
TestJson();
TestBasics();
TestVirtualBlocks();
TestVirtualBlocksAlgorithms();
TestVirtualBlocksAlgorithmsBenchmark();
TestAllocationVersusResourceSize();
TestPool_SameSize();
TestPool_MinBlockCount();
TestPool_MinAllocationAlignment();
TestPoolsAndAllocationParameters();
TestHeapSizeLimit();
#if VMA_DEBUG_INITIALIZE_ALLOCATIONS
TestAllocationsInitialization();
#endif
TestMemoryUsage();
TestDeviceCoherentMemory();
TestBudget();
TestAliasing();
TestAllocationAliasing();
TestMapping();
TestMappingHysteresis();
TestDeviceLocalMapped();
TestMappingMultithreaded();
TestLinearAllocator();
ManuallyTestLinearAllocator();
TestLinearAllocatorMultiBlock();
TestAllocationAlgorithmsCorrectness();
BasicTestTLSF();
BasicTestAllocatePages();
if (VK_KHR_buffer_device_address_enabled)
TestBufferDeviceAddress();
if (VK_EXT_memory_priority_enabled)
TestMemoryPriority();
{
FILE* file;
fopen_s(&file, "Algorithms.csv", "w");
assert(file != NULL);
BenchmarkAlgorithms(file);
fclose(file);
}
TestDefragmentationSimple();
TestDefragmentationVsMapping();
if (ConfigType >= CONFIG_TYPE_AVERAGE)
{
TestDefragmentationAlgorithms();
TestDefragmentationFull();
TestDefragmentationGpu();
TestDefragmentationIncrementalBasic();
TestDefragmentationIncrementalComplex();
}
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);
#endif
wprintf(L"Done, all PASSED.\n");
}
#endif