blob: 774f070259d699949751e27ff1c8849dc64c070e [file] [log] [blame]
//
// Copyright (c) 2018-2020 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#include "VmaUsage.h"
#include "Common.h"
#include "Constants.h"
#include <unordered_map>
#include <map>
#include <algorithm>
static VERBOSITY g_Verbosity = VERBOSITY::DEFAULT;
static const uint32_t VULKAN_API_VERSION = VK_API_VERSION_1_1;
namespace DetailedStats
{
struct Flag
{
uint32_t setCount = 0;
void PostValue(bool v)
{
if(v)
{
++setCount;
}
}
void Print(uint32_t totalCount) const
{
if(setCount)
{
printf(" %u (%.2f%%)\n", setCount, (double)setCount * 100.0 / (double)totalCount);
}
else
{
printf(" 0\n");
}
}
};
struct Enum
{
Enum(size_t itemCount, const char* const* itemNames, const uint32_t* itemValues = nullptr) :
m_ItemCount(itemCount),
m_ItemNames(itemNames),
m_ItemValues(itemValues)
{
}
void PostValue(uint32_t v)
{
if(v < _countof(m_BaseCount))
{
++m_BaseCount[v];
}
else
{
auto it = m_ExtendedCount.find(v);
if(it != m_ExtendedCount.end())
{
++it->second;
}
else
{
m_ExtendedCount.insert(std::make_pair(v, 1u));
}
}
}
void Print(uint32_t totalCount) const
{
if(totalCount &&
(!m_ExtendedCount.empty() || std::count_if(m_BaseCount, m_BaseCount + _countof(m_BaseCount), [](uint32_t v) { return v > 0; })))
{
printf("\n");
for(size_t i = 0; i < _countof(m_BaseCount); ++i)
{
const uint32_t currCount = m_BaseCount[i];
if(currCount)
{
PrintItem((uint32_t)i, currCount, totalCount);
}
}
for(const auto& it : m_ExtendedCount)
{
PrintItem(it.first, it.second, totalCount);
}
}
else
{
printf(" 0\n");
}
}
private:
const size_t m_ItemCount;
const char* const* const m_ItemNames;
const uint32_t* const m_ItemValues;
uint32_t m_BaseCount[32] = {};
std::map<uint32_t, uint32_t> m_ExtendedCount;
void PrintItem(uint32_t value, uint32_t count, uint32_t totalCount) const
{
size_t itemIndex = m_ItemCount;
if(m_ItemValues)
{
for(itemIndex = 0; itemIndex < m_ItemCount; ++itemIndex)
{
if(m_ItemValues[itemIndex] == value)
{
break;
}
}
}
else
{
if(value < m_ItemCount)
{
itemIndex = value;
}
}
if(itemIndex < m_ItemCount)
{
printf(" %s: ", m_ItemNames[itemIndex]);
}
else
{
printf(" 0x%X: ", value);
}
printf("%u (%.2f%%)\n", count, (double)count * 100.0 / (double)totalCount);
}
};
struct FlagSet
{
uint32_t count[32] = {};
FlagSet(size_t count, const char* const* names, const uint32_t* values = nullptr) :
m_Count(count),
m_Names(names),
m_Values(values)
{
}
void PostValue(uint32_t v)
{
for(size_t i = 0; i < 32; ++i)
{
if((v & (1u << i)) != 0)
{
++count[i];
}
}
}
void Print(uint32_t totalCount) const
{
if(totalCount &&
std::count_if(count, count + _countof(count), [](uint32_t v) { return v > 0; }))
{
printf("\n");
for(uint32_t bitIndex = 0; bitIndex < 32; ++bitIndex)
{
const uint32_t currCount = count[bitIndex];
if(currCount)
{
size_t itemIndex = m_Count;
if(m_Values)
{
for(itemIndex = 0; itemIndex < m_Count; ++itemIndex)
{
if(m_Values[itemIndex] == (1u << bitIndex))
{
break;
}
}
}
else
{
if(bitIndex < m_Count)
{
itemIndex = bitIndex;
}
}
if(itemIndex < m_Count)
{
printf(" %s: ", m_Names[itemIndex]);
}
else
{
printf(" 0x%X: ", 1u << bitIndex);
}
printf("%u (%.2f%%)\n", currCount, (double)currCount * 100.0 / (double)totalCount);
}
}
}
else
{
printf(" 0\n");
}
}
private:
const size_t m_Count;
const char* const* const m_Names;
const uint32_t* const m_Values;
};
// T should be unsigned int
template<typename T>
struct MinMaxAvg
{
T min = std::numeric_limits<T>::max();
T max = 0;
T sum = T();
void PostValue(T v)
{
this->min = std::min(this->min, v);
this->max = std::max(this->max, v);
sum += v;
}
void Print(uint32_t totalCount) const
{
if(totalCount && sum > T())
{
if(this->min == this->max)
{
printf(" %llu\n", (uint64_t)this->max);
}
else
{
printf("\n Min: %llu\n Max: %llu\n Avg: %llu\n",
(uint64_t)this->min,
(uint64_t)this->max,
round_div<uint64_t>(this->sum, totalCount));
}
}
else
{
printf(" 0\n");
}
}
};
template<typename T>
struct BitMask
{
uint32_t zeroCount = 0;
uint32_t maxCount = 0;
void PostValue(T v)
{
if(v)
{
if(v == std::numeric_limits<T>::max())
{
++maxCount;
}
}
else
{
++zeroCount;
}
}
void Print(uint32_t totalCount) const
{
if(totalCount > 0 && zeroCount < totalCount)
{
const uint32_t otherCount = totalCount - (zeroCount + maxCount);
printf("\n 0: %u (%.2f%%)\n Max: %u (%.2f%%)\n Other: %u (%.2f%%)\n",
zeroCount, (double)zeroCount * 100.0 / (double)totalCount,
maxCount, (double)maxCount * 100.0 / (double)totalCount,
otherCount, (double)otherCount * 100.0 / (double)totalCount);
}
else
{
printf(" 0\n");
}
}
};
struct CountPerMemType
{
uint32_t count[VK_MAX_MEMORY_TYPES] = {};
void PostValue(uint32_t v)
{
for(uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i)
{
if((v & (1u << i)) != 0)
{
++count[i];
}
}
}
void Print(uint32_t totalCount) const
{
if(totalCount)
{
printf("\n");
for(uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i)
{
if(count[i])
{
printf(" %u: %u (%.2f%%)\n", i, count[i],
(double)count[i] * 100.0 / (double)totalCount);
}
}
}
else
{
printf(" 0\n");
}
}
};
struct StructureStats
{
uint32_t totalCount = 0;
};
#define PRINT_FIELD(name) \
printf(" " #name ":"); \
(name).Print(totalCount);
#define PRINT_FIELD_NAMED(name, nameStr) \
printf(" " nameStr ":"); \
(name).Print(totalCount);
struct VmaPoolCreateInfoStats : public StructureStats
{
CountPerMemType memoryTypeIndex;
FlagSet flags;
MinMaxAvg<VkDeviceSize> blockSize;
MinMaxAvg<size_t> minBlockCount;
MinMaxAvg<size_t> maxBlockCount;
Flag minMaxBlockCountEqual;
MinMaxAvg<uint32_t> frameInUseCount;
VmaPoolCreateInfoStats() :
flags(VMA_POOL_CREATE_FLAG_COUNT, VMA_POOL_CREATE_FLAG_NAMES, VMA_POOL_CREATE_FLAG_VALUES)
{
}
void PostValue(const VmaPoolCreateInfo& v)
{
++totalCount;
memoryTypeIndex.PostValue(v.memoryTypeIndex);
flags.PostValue(v.flags);
blockSize.PostValue(v.blockSize);
minBlockCount.PostValue(v.minBlockCount);
maxBlockCount.PostValue(v.maxBlockCount);
minMaxBlockCountEqual.PostValue(v.minBlockCount == v.maxBlockCount);
frameInUseCount.PostValue(v.frameInUseCount);
}
void Print() const
{
if(totalCount == 0)
{
return;
}
printf("VmaPoolCreateInfo (%u):\n", totalCount);
PRINT_FIELD(memoryTypeIndex);
PRINT_FIELD(flags);
PRINT_FIELD(blockSize);
PRINT_FIELD(minBlockCount);
PRINT_FIELD(maxBlockCount);
PRINT_FIELD_NAMED(minMaxBlockCountEqual, "minBlockCount == maxBlockCount");
PRINT_FIELD(frameInUseCount);
}
};
struct VkBufferCreateInfoStats : public StructureStats
{
FlagSet flags;
MinMaxAvg<VkDeviceSize> size;
FlagSet usage;
Enum sharingMode;
VkBufferCreateInfoStats() :
flags(VK_BUFFER_CREATE_FLAG_COUNT, VK_BUFFER_CREATE_FLAG_NAMES, VK_BUFFER_CREATE_FLAG_VALUES),
usage(VK_BUFFER_USAGE_FLAG_COUNT, VK_BUFFER_USAGE_FLAG_NAMES, VK_BUFFER_USAGE_FLAG_VALUES),
sharingMode(VK_SHARING_MODE_COUNT, VK_SHARING_MODE_NAMES)
{
}
void PostValue(const VkBufferCreateInfo& v)
{
++totalCount;
flags.PostValue(v.flags);
size.PostValue(v.size);
usage.PostValue(v.usage);
sharingMode.PostValue(v.sharingMode);
}
void Print() const
{
if(totalCount == 0)
{
return;
}
printf("VkBufferCreateInfo (%u):\n", totalCount);
PRINT_FIELD(flags);
PRINT_FIELD(size);
PRINT_FIELD(usage);
PRINT_FIELD(sharingMode);
}
};
struct VkImageCreateInfoStats : public StructureStats
{
FlagSet flags;
Enum imageType;
Enum format;
MinMaxAvg<uint32_t> width, height, depth, mipLevels, arrayLayers;
Flag depthGreaterThanOne, mipLevelsGreaterThanOne, arrayLayersGreaterThanOne;
Enum samples;
Enum tiling;
FlagSet usage;
Enum sharingMode;
Enum initialLayout;
VkImageCreateInfoStats() :
flags(VK_IMAGE_CREATE_FLAG_COUNT, VK_IMAGE_CREATE_FLAG_NAMES, VK_IMAGE_CREATE_FLAG_VALUES),
imageType(VK_IMAGE_TYPE_COUNT, VK_IMAGE_TYPE_NAMES),
format(VK_FORMAT_COUNT, VK_FORMAT_NAMES, VK_FORMAT_VALUES),
samples(VK_SAMPLE_COUNT_COUNT, VK_SAMPLE_COUNT_NAMES, VK_SAMPLE_COUNT_VALUES),
tiling(VK_IMAGE_TILING_COUNT, VK_IMAGE_TILING_NAMES),
usage(VK_IMAGE_USAGE_FLAG_COUNT, VK_IMAGE_USAGE_FLAG_NAMES, VK_IMAGE_USAGE_FLAG_VALUES),
sharingMode(VK_SHARING_MODE_COUNT, VK_SHARING_MODE_NAMES),
initialLayout(VK_IMAGE_LAYOUT_COUNT, VK_IMAGE_LAYOUT_NAMES, VK_IMAGE_LAYOUT_VALUES)
{
}
void PostValue(const VkImageCreateInfo& v)
{
++totalCount;
flags.PostValue(v.flags);
imageType.PostValue(v.imageType);
format.PostValue(v.format);
width.PostValue(v.extent.width);
height.PostValue(v.extent.height);
depth.PostValue(v.extent.depth);
mipLevels.PostValue(v.mipLevels);
arrayLayers.PostValue(v.arrayLayers);
depthGreaterThanOne.PostValue(v.extent.depth > 1);
mipLevelsGreaterThanOne.PostValue(v.mipLevels > 1);
arrayLayersGreaterThanOne.PostValue(v.arrayLayers > 1);
samples.PostValue(v.samples);
tiling.PostValue(v.tiling);
usage.PostValue(v.usage);
sharingMode.PostValue(v.sharingMode);
initialLayout.PostValue(v.initialLayout);
}
void Print() const
{
if(totalCount == 0)
{
return;
}
printf("VkImageCreateInfo (%u):\n", totalCount);
PRINT_FIELD(flags);
PRINT_FIELD(imageType);
PRINT_FIELD(format);
PRINT_FIELD(width);
PRINT_FIELD(height);
PRINT_FIELD(depth);
PRINT_FIELD(mipLevels);
PRINT_FIELD(arrayLayers);
PRINT_FIELD_NAMED(depthGreaterThanOne, "depth > 1");
PRINT_FIELD_NAMED(mipLevelsGreaterThanOne, "mipLevels > 1");
PRINT_FIELD_NAMED(arrayLayersGreaterThanOne, "arrayLayers > 1");
PRINT_FIELD(samples);
PRINT_FIELD(tiling);
PRINT_FIELD(usage);
PRINT_FIELD(sharingMode);
PRINT_FIELD(initialLayout);
}
};
struct VmaAllocationCreateInfoStats : public StructureStats
{
FlagSet flags;
Enum usage;
FlagSet requiredFlags, preferredFlags;
Flag requiredFlagsNotZero, preferredFlagsNotZero;
BitMask<uint32_t> memoryTypeBits;
Flag poolNotNull;
Flag userDataNotNull;
VmaAllocationCreateInfoStats() :
flags(VMA_ALLOCATION_CREATE_FLAG_COUNT, VMA_ALLOCATION_CREATE_FLAG_NAMES, VMA_ALLOCATION_CREATE_FLAG_VALUES),
usage(VMA_MEMORY_USAGE_COUNT, VMA_MEMORY_USAGE_NAMES),
requiredFlags(VK_MEMORY_PROPERTY_FLAG_COUNT, VK_MEMORY_PROPERTY_FLAG_NAMES, VK_MEMORY_PROPERTY_FLAG_VALUES),
preferredFlags(VK_MEMORY_PROPERTY_FLAG_COUNT, VK_MEMORY_PROPERTY_FLAG_NAMES, VK_MEMORY_PROPERTY_FLAG_VALUES)
{
}
void PostValue(const VmaAllocationCreateInfo& v, size_t count = 1)
{
totalCount += (uint32_t)count;
for(size_t i = 0; i < count; ++i)
{
flags.PostValue(v.flags);
usage.PostValue(v.usage);
requiredFlags.PostValue(v.requiredFlags);
preferredFlags.PostValue(v.preferredFlags);
requiredFlagsNotZero.PostValue(v.requiredFlags != 0);
preferredFlagsNotZero.PostValue(v.preferredFlags != 0);
memoryTypeBits.PostValue(v.memoryTypeBits);
poolNotNull.PostValue(v.pool != VK_NULL_HANDLE);
userDataNotNull.PostValue(v.pUserData != nullptr);
}
}
void Print() const
{
if(totalCount == 0)
{
return;
}
printf("VmaAllocationCreateInfo (%u):\n", totalCount);
PRINT_FIELD(flags);
PRINT_FIELD(usage);
PRINT_FIELD(requiredFlags);
PRINT_FIELD(preferredFlags);
PRINT_FIELD_NAMED(requiredFlagsNotZero, "requiredFlags != 0");
PRINT_FIELD_NAMED(preferredFlagsNotZero, "preferredFlags != 0");
PRINT_FIELD(memoryTypeBits);
PRINT_FIELD_NAMED(poolNotNull, "pool != VK_NULL_HANDLE");
PRINT_FIELD_NAMED(userDataNotNull, "pUserData != nullptr");
}
};
struct VmaAllocateMemoryPagesStats : public StructureStats
{
MinMaxAvg<size_t> allocationCount;
void PostValue(size_t allocationCount)
{
this->allocationCount.PostValue(allocationCount);
}
void Print() const
{
if(totalCount == 0)
{
return;
}
printf("vmaAllocateMemoryPages (%u):\n", totalCount);
PRINT_FIELD(allocationCount);
}
};
struct VmaDefragmentationInfo2Stats : public StructureStats
{
BitMask<VkDeviceSize> maxCpuBytesToMove;
BitMask<uint32_t> maxCpuAllocationsToMove;
BitMask<VkDeviceSize> maxGpuBytesToMove;
BitMask<uint32_t> maxGpuAllocationsToMove;
Flag commandBufferNotNull;
MinMaxAvg<uint32_t> allocationCount;
Flag allocationCountNotZero;
MinMaxAvg<uint32_t> poolCount;
Flag poolCountNotZero;
void PostValue(const VmaDefragmentationInfo2& info)
{
++totalCount;
maxCpuBytesToMove.PostValue(info.maxCpuBytesToMove);
maxCpuAllocationsToMove.PostValue(info.maxCpuAllocationsToMove);
maxGpuBytesToMove.PostValue(info.maxGpuBytesToMove);
maxGpuAllocationsToMove.PostValue(info.maxGpuAllocationsToMove);
commandBufferNotNull.PostValue(info.commandBuffer != VK_NULL_HANDLE);
allocationCount.PostValue(info.allocationCount);
allocationCountNotZero.PostValue(info.allocationCount != 0);
poolCount.PostValue(info.poolCount);
poolCountNotZero.PostValue(info.poolCount != 0);
}
void Print() const
{
if(totalCount == 0)
{
return;
}
printf("VmaDefragmentationInfo2 (%u):\n", totalCount);
PRINT_FIELD(maxCpuBytesToMove);
PRINT_FIELD(maxCpuAllocationsToMove);
PRINT_FIELD(maxGpuBytesToMove);
PRINT_FIELD(maxGpuAllocationsToMove);
PRINT_FIELD_NAMED(commandBufferNotNull, "commandBuffer != VK_NULL_HANDLE");
PRINT_FIELD(allocationCount);
PRINT_FIELD_NAMED(allocationCountNotZero, "allocationCount > 0");
PRINT_FIELD(poolCount);
PRINT_FIELD_NAMED(poolCountNotZero, "poolCount > 0");
}
};
#undef PRINT_FIELD_NAMED
#undef PRINT_FIELD
} // namespace DetailedStats
// Set this to false to disable deleting leaked VmaAllocation, VmaPool objects
// and let VMA report asserts about them.
static const bool CLEANUP_LEAKED_OBJECTS = true;
static std::string g_FilePath;
// Most significant 16 bits are major version, least significant 16 bits are minor version.
static uint32_t g_FileVersion;
inline uint32_t MakeVersion(uint32_t major, uint32_t minor) { return (major << 16) | minor; }
inline uint32_t GetVersionMajor(uint32_t version) { return version >> 16; }
inline uint32_t GetVersionMinor(uint32_t version) { return version & 0xFFFF; }
static size_t g_IterationCount = 1;
static uint32_t g_PhysicalDeviceIndex = 0;
static RangeSequence<size_t> g_LineRanges;
static bool g_UserDataEnabled = true;
static bool g_MemStatsEnabled = false;
VULKAN_EXTENSION_REQUEST g_VK_LAYER_KHRONOS_validation = VULKAN_EXTENSION_REQUEST::DEFAULT;
VULKAN_EXTENSION_REQUEST g_VK_EXT_memory_budget_request = VULKAN_EXTENSION_REQUEST::DEFAULT;
VULKAN_EXTENSION_REQUEST g_VK_AMD_device_coherent_memory_request = VULKAN_EXTENSION_REQUEST::DEFAULT;
struct StatsAfterLineEntry
{
size_t line;
bool detailed;
bool operator<(const StatsAfterLineEntry& rhs) const { return line < rhs.line; }
bool operator==(const StatsAfterLineEntry& rhs) const { return line == rhs.line; }
};
static std::vector<StatsAfterLineEntry> g_DumpStatsAfterLine;
static std::vector<size_t> g_DefragmentAfterLine;
static uint32_t g_DefragmentationFlags = 0;
static size_t g_DumpStatsAfterLineNextIndex = 0;
static size_t g_DefragmentAfterLineNextIndex = 0;
static bool ValidateFileVersion()
{
if(GetVersionMajor(g_FileVersion) == 1 &&
GetVersionMinor(g_FileVersion) <= 8)
{
return true;
}
return false;
}
static bool ParseFileVersion(const StrRange& s)
{
CsvSplit csvSplit;
csvSplit.Set(s, 2);
uint32_t major, minor;
if(csvSplit.GetCount() == 2 &&
StrRangeToUint(csvSplit.GetRange(0), major) &&
StrRangeToUint(csvSplit.GetRange(1), minor))
{
g_FileVersion = (major << 16) | minor;
return true;
}
else
{
return false;
}
}
////////////////////////////////////////////////////////////////////////////////
// class Statistics
class Statistics
{
public:
static uint32_t BufferUsageToClass(uint32_t usage);
static uint32_t ImageUsageToClass(uint32_t usage);
Statistics();
~Statistics();
void Init(uint32_t memHeapCount, uint32_t memTypeCount);
void PrintDeviceMemStats() const;
void PrintMemStats() const;
void PrintDetailedStats() const;
const size_t* GetFunctionCallCount() const { return m_FunctionCallCount; }
size_t GetImageCreationCount(uint32_t imgClass) const { return m_ImageCreationCount[imgClass]; }
size_t GetLinearImageCreationCount() const { return m_LinearImageCreationCount; }
size_t GetBufferCreationCount(uint32_t bufClass) const { return m_BufferCreationCount[bufClass]; }
size_t GetAllocationCreationCount() const { return (size_t)m_VmaAllocationCreateInfo.totalCount + m_CreateLostAllocationCount; }
size_t GetPoolCreationCount() const { return m_VmaPoolCreateInfo.totalCount; }
size_t GetBufferCreationCount() const { return (size_t)m_VkBufferCreateInfo.totalCount; }
void RegisterFunctionCall(VMA_FUNCTION func);
void RegisterCreateImage(const VkImageCreateInfo& info);
void RegisterCreateBuffer(const VkBufferCreateInfo& info);
void RegisterCreatePool(const VmaPoolCreateInfo& info);
void RegisterCreateAllocation(const VmaAllocationCreateInfo& info, size_t allocCount = 1);
void RegisterCreateLostAllocation() { ++m_CreateLostAllocationCount; }
void RegisterAllocateMemoryPages(size_t allocCount) { m_VmaAllocateMemoryPages.PostValue(allocCount); }
void RegisterDefragmentation(const VmaDefragmentationInfo2& info);
void RegisterDeviceMemoryAllocation(uint32_t memoryType, VkDeviceSize size);
void UpdateMemStats(const VmaStats& currStats);
private:
uint32_t m_MemHeapCount = 0;
uint32_t m_MemTypeCount = 0;
size_t m_FunctionCallCount[(size_t)VMA_FUNCTION::Count] = {};
size_t m_ImageCreationCount[4] = { };
size_t m_LinearImageCreationCount = 0;
size_t m_BufferCreationCount[4] = { };
struct DeviceMemStatInfo
{
size_t allocationCount;
VkDeviceSize allocationTotalSize;
};
struct DeviceMemStats
{
DeviceMemStatInfo memoryType[VK_MAX_MEMORY_TYPES];
DeviceMemStatInfo total;
} m_DeviceMemStats;
// Structure similar to VmaStatInfo, but not the same.
struct MemStatInfo
{
uint32_t blockCount;
uint32_t allocationCount;
uint32_t unusedRangeCount;
VkDeviceSize usedBytes;
VkDeviceSize unusedBytes;
VkDeviceSize totalBytes;
};
struct MemStats
{
MemStatInfo memoryType[VK_MAX_MEMORY_TYPES];
MemStatInfo memoryHeap[VK_MAX_MEMORY_HEAPS];
MemStatInfo total;
} m_PeakMemStats;
DetailedStats::VmaPoolCreateInfoStats m_VmaPoolCreateInfo;
DetailedStats::VkBufferCreateInfoStats m_VkBufferCreateInfo;
DetailedStats::VkImageCreateInfoStats m_VkImageCreateInfo;
DetailedStats::VmaAllocationCreateInfoStats m_VmaAllocationCreateInfo;
size_t m_CreateLostAllocationCount = 0;
DetailedStats::VmaAllocateMemoryPagesStats m_VmaAllocateMemoryPages;
DetailedStats::VmaDefragmentationInfo2Stats m_VmaDefragmentationInfo2;
void UpdateMemStatInfo(MemStatInfo& inoutPeakInfo, const VmaStatInfo& currInfo);
static void PrintMemStatInfo(const MemStatInfo& info);
};
// Hack for global AllocateDeviceMemoryCallback.
static Statistics* g_Statistics;
static void VKAPI_CALL AllocateDeviceMemoryCallback(
VmaAllocator allocator,
uint32_t memoryType,
VkDeviceMemory memory,
VkDeviceSize size,
void* pUserData)
{
g_Statistics->RegisterDeviceMemoryAllocation(memoryType, size);
}
/// Callback function called before vkFreeMemory.
static void VKAPI_CALL FreeDeviceMemoryCallback(
VmaAllocator allocator,
uint32_t memoryType,
VkDeviceMemory memory,
VkDeviceSize size,
void* pUserData)
{
// Nothing.
}
uint32_t Statistics::BufferUsageToClass(uint32_t usage)
{
// Buffer is used as source of data for fixed-function stage of graphics pipeline.
// It's indirect, vertex, or index buffer.
if ((usage & (VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT)) != 0)
{
return 0;
}
// Buffer is accessed by shaders for load/store/atomic.
// Aka "UAV"
else if ((usage & (VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT)) != 0)
{
return 1;
}
// Buffer is accessed by shaders for reading uniform data.
// Aka "constant buffer"
else if ((usage & (VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT)) != 0)
{
return 2;
}
// Any other type of buffer.
// Notice that VK_BUFFER_USAGE_TRANSFER_SRC_BIT and VK_BUFFER_USAGE_TRANSFER_DST_BIT
// flags are intentionally ignored.
else
{
return 3;
}
}
uint32_t Statistics::ImageUsageToClass(uint32_t usage)
{
// Image is used as depth/stencil "texture/surface".
if ((usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0)
{
return 0;
}
// Image is used as other type of attachment.
// Aka "render target"
else if ((usage & (VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT |
VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT |
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) != 0)
{
return 1;
}
// Image is accessed by shaders for sampling.
// Aka "texture"
else if ((usage & VK_IMAGE_USAGE_SAMPLED_BIT) != 0)
{
return 2;
}
// Any other type of image.
// Notice that VK_IMAGE_USAGE_TRANSFER_SRC_BIT and VK_IMAGE_USAGE_TRANSFER_DST_BIT
// flags are intentionally ignored.
else
{
return 3;
}
}
Statistics::Statistics()
{
ZeroMemory(&m_DeviceMemStats, sizeof(m_DeviceMemStats));
ZeroMemory(&m_PeakMemStats, sizeof(m_PeakMemStats));
assert(g_Statistics == nullptr);
g_Statistics = this;
}
Statistics::~Statistics()
{
assert(g_Statistics == this);
g_Statistics = nullptr;
}
void Statistics::Init(uint32_t memHeapCount, uint32_t memTypeCount)
{
m_MemHeapCount = memHeapCount;
m_MemTypeCount = memTypeCount;
}
void Statistics::PrintDeviceMemStats() const
{
printf("Successful device memory allocations:\n");
printf(" Total: count = %zu, total size = %llu\n",
m_DeviceMemStats.total.allocationCount, m_DeviceMemStats.total.allocationTotalSize);
for(uint32_t i = 0; i < m_MemTypeCount; ++i)
{
printf(" Memory type %u: count = %zu, total size = %llu\n",
i, m_DeviceMemStats.memoryType[i].allocationCount, m_DeviceMemStats.memoryType[i].allocationTotalSize);
}
}
void Statistics::PrintMemStats() const
{
printf("Memory statistics:\n");
printf(" Total:\n");
PrintMemStatInfo(m_PeakMemStats.total);
for(uint32_t i = 0; i < m_MemHeapCount; ++i)
{
const MemStatInfo& info = m_PeakMemStats.memoryHeap[i];
if(info.blockCount > 0 || info.totalBytes > 0)
{
printf(" Heap %u:\n", i);
PrintMemStatInfo(info);
}
}
for(uint32_t i = 0; i < m_MemTypeCount; ++i)
{
const MemStatInfo& info = m_PeakMemStats.memoryType[i];
if(info.blockCount > 0 || info.totalBytes > 0)
{
printf(" Type %u:\n", i);
PrintMemStatInfo(info);
}
}
}
void Statistics::PrintDetailedStats() const
{
m_VmaPoolCreateInfo.Print();
m_VmaAllocationCreateInfo.Print();
m_VmaAllocateMemoryPages.Print();
m_VkBufferCreateInfo.Print();
m_VkImageCreateInfo.Print();
m_VmaDefragmentationInfo2.Print();
}
void Statistics::RegisterFunctionCall(VMA_FUNCTION func)
{
++m_FunctionCallCount[(size_t)func];
}
void Statistics::RegisterCreateImage(const VkImageCreateInfo& info)
{
if(info.tiling == VK_IMAGE_TILING_LINEAR)
++m_LinearImageCreationCount;
else
{
const uint32_t imgClass = ImageUsageToClass(info.usage);
++m_ImageCreationCount[imgClass];
}
m_VkImageCreateInfo.PostValue(info);
}
void Statistics::RegisterCreateBuffer(const VkBufferCreateInfo& info)
{
const uint32_t bufClass = BufferUsageToClass(info.usage);
++m_BufferCreationCount[bufClass];
m_VkBufferCreateInfo.PostValue(info);
}
void Statistics::RegisterCreatePool(const VmaPoolCreateInfo& info)
{
m_VmaPoolCreateInfo.PostValue(info);
}
void Statistics::RegisterCreateAllocation(const VmaAllocationCreateInfo& info, size_t allocCount)
{
m_VmaAllocationCreateInfo.PostValue(info, allocCount);
}
void Statistics::RegisterDefragmentation(const VmaDefragmentationInfo2& info)
{
m_VmaDefragmentationInfo2.PostValue(info);
}
void Statistics::UpdateMemStats(const VmaStats& currStats)
{
UpdateMemStatInfo(m_PeakMemStats.total, currStats.total);
for(uint32_t i = 0; i < m_MemHeapCount; ++i)
{
UpdateMemStatInfo(m_PeakMemStats.memoryHeap[i], currStats.memoryHeap[i]);
}
for(uint32_t i = 0; i < m_MemTypeCount; ++i)
{
UpdateMemStatInfo(m_PeakMemStats.memoryType[i], currStats.memoryType[i]);
}
}
void Statistics::RegisterDeviceMemoryAllocation(uint32_t memoryType, VkDeviceSize size)
{
++m_DeviceMemStats.total.allocationCount;
m_DeviceMemStats.total.allocationTotalSize += size;
++m_DeviceMemStats.memoryType[memoryType].allocationCount;
m_DeviceMemStats.memoryType[memoryType].allocationTotalSize += size;
}
void Statistics::UpdateMemStatInfo(MemStatInfo& inoutPeakInfo, const VmaStatInfo& currInfo)
{
#define SET_PEAK(inoutDst, src) \
if((src) > (inoutDst)) \
{ \
(inoutDst) = (src); \
}
SET_PEAK(inoutPeakInfo.blockCount, currInfo.blockCount);
SET_PEAK(inoutPeakInfo.allocationCount, currInfo.allocationCount);
SET_PEAK(inoutPeakInfo.unusedRangeCount, currInfo.unusedRangeCount);
SET_PEAK(inoutPeakInfo.usedBytes, currInfo.usedBytes);
SET_PEAK(inoutPeakInfo.unusedBytes, currInfo.unusedBytes);
SET_PEAK(inoutPeakInfo.totalBytes, currInfo.usedBytes + currInfo.unusedBytes);
#undef SET_PEAK
}
void Statistics::PrintMemStatInfo(const MemStatInfo& info)
{
printf(" Peak blocks %u, allocations %u, unused ranges %u\n",
info.blockCount,
info.allocationCount,
info.unusedRangeCount);
printf(" Peak total bytes %llu, used bytes %llu, unused bytes %llu\n",
info.totalBytes,
info.usedBytes,
info.unusedBytes);
}
////////////////////////////////////////////////////////////////////////////////
// class ConfigurationParser
class ConfigurationParser
{
public:
ConfigurationParser();
bool Parse(LineSplit& lineSplit);
void Compare(
const VkPhysicalDeviceProperties& currDevProps,
const VkPhysicalDeviceMemoryProperties& currMemProps,
uint32_t vulkanApiVersion,
bool currMemoryBudgetEnabled);
private:
enum class OPTION
{
VulkanApiVersion,
PhysicalDevice_apiVersion,
PhysicalDevice_driverVersion,
PhysicalDevice_vendorID,
PhysicalDevice_deviceID,
PhysicalDevice_deviceType,
PhysicalDevice_deviceName,
PhysicalDeviceLimits_maxMemoryAllocationCount,
PhysicalDeviceLimits_bufferImageGranularity,
PhysicalDeviceLimits_nonCoherentAtomSize,
Extension_VK_KHR_dedicated_allocation,
Extension_VK_KHR_bind_memory2,
Extension_VK_EXT_memory_budget,
Extension_VK_AMD_device_coherent_memory,
Macro_VMA_DEBUG_ALWAYS_DEDICATED_MEMORY,
Macro_VMA_DEBUG_ALIGNMENT,
Macro_VMA_DEBUG_MARGIN,
Macro_VMA_DEBUG_INITIALIZE_ALLOCATIONS,
Macro_VMA_DEBUG_DETECT_CORRUPTION,
Macro_VMA_DEBUG_GLOBAL_MUTEX,
Macro_VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY,
Macro_VMA_SMALL_HEAP_MAX_SIZE,
Macro_VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE,
Count
};
std::vector<bool> m_OptionSet;
std::vector<std::string> m_OptionValue;
VkPhysicalDeviceMemoryProperties m_MemProps;
bool m_WarningHeaderPrinted = false;
void SetOption(
size_t lineNumber,
OPTION option,
const StrRange& str);
void EnsureWarningHeader();
void CompareOption(VERBOSITY minVerbosity, const char* name,
OPTION option, uint32_t currValue);
void CompareOption(VERBOSITY minVerbosity, const char* name,
OPTION option, uint64_t currValue);
void CompareOption(VERBOSITY minVerbosity, const char* name,
OPTION option, bool currValue);
void CompareOption(VERBOSITY minVerbosity, const char* name,
OPTION option, const char* currValue);
void CompareMemProps(
const VkPhysicalDeviceMemoryProperties& currMemProps);
};
ConfigurationParser::ConfigurationParser() :
m_OptionSet((size_t)OPTION::Count),
m_OptionValue((size_t)OPTION::Count)
{
ZeroMemory(&m_MemProps, sizeof(m_MemProps));
}
bool ConfigurationParser::Parse(LineSplit& lineSplit)
{
for(auto& it : m_OptionSet)
{
it = false;
}
for(auto& it : m_OptionValue)
{
it.clear();
}
StrRange line;
if(!lineSplit.GetNextLine(line) && !StrRangeEq(line, "Config,Begin"))
{
return false;
}
CsvSplit csvSplit;
while(lineSplit.GetNextLine(line))
{
if(StrRangeEq(line, "Config,End"))
{
break;
}
const size_t currLineNumber = lineSplit.GetNextLineIndex();
csvSplit.Set(line);
if(csvSplit.GetCount() == 0)
{
return false;
}
const StrRange optionName = csvSplit.GetRange(0);
if(StrRangeEq(optionName, "VulkanApiVersion"))
{
SetOption(currLineNumber, OPTION::VulkanApiVersion, StrRange{csvSplit.GetRange(1).beg, csvSplit.GetRange(2).end});
}
else if(StrRangeEq(optionName, "PhysicalDevice"))
{
if(csvSplit.GetCount() >= 3)
{
const StrRange subOptionName = csvSplit.GetRange(1);
if(StrRangeEq(subOptionName, "apiVersion"))
SetOption(currLineNumber, OPTION::PhysicalDevice_apiVersion, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "driverVersion"))
SetOption(currLineNumber, OPTION::PhysicalDevice_driverVersion, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "vendorID"))
SetOption(currLineNumber, OPTION::PhysicalDevice_vendorID, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "deviceID"))
SetOption(currLineNumber, OPTION::PhysicalDevice_deviceID, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "deviceType"))
SetOption(currLineNumber, OPTION::PhysicalDevice_deviceType, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "deviceName"))
SetOption(currLineNumber, OPTION::PhysicalDevice_deviceName, StrRange(csvSplit.GetRange(2).beg, line.end));
else
printf("Line %zu: Unrecognized configuration option.\n", currLineNumber);
}
else
printf("Line %zu: Too few columns.\n", currLineNumber);
}
else if(StrRangeEq(optionName, "PhysicalDeviceLimits"))
{
if(csvSplit.GetCount() >= 3)
{
const StrRange subOptionName = csvSplit.GetRange(1);
if(StrRangeEq(subOptionName, "maxMemoryAllocationCount"))
SetOption(currLineNumber, OPTION::PhysicalDeviceLimits_maxMemoryAllocationCount, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "bufferImageGranularity"))
SetOption(currLineNumber, OPTION::PhysicalDeviceLimits_bufferImageGranularity, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "nonCoherentAtomSize"))
SetOption(currLineNumber, OPTION::PhysicalDeviceLimits_nonCoherentAtomSize, csvSplit.GetRange(2));
else
printf("Line %zu: Unrecognized configuration option.\n", currLineNumber);
}
else
printf("Line %zu: Too few columns.\n", currLineNumber);
}
else if(StrRangeEq(optionName, "Extension"))
{
if(csvSplit.GetCount() >= 3)
{
const StrRange subOptionName = csvSplit.GetRange(1);
if(StrRangeEq(subOptionName, "VK_KHR_dedicated_allocation"))
{
// Ignore because this extension is promoted to Vulkan 1.1.
}
else if(StrRangeEq(subOptionName, "VK_KHR_bind_memory2"))
SetOption(currLineNumber, OPTION::Extension_VK_KHR_bind_memory2, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "VK_EXT_memory_budget"))
SetOption(currLineNumber, OPTION::Extension_VK_EXT_memory_budget, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "VK_AMD_device_coherent_memory"))
SetOption(currLineNumber, OPTION::Extension_VK_AMD_device_coherent_memory, csvSplit.GetRange(2));
else
printf("Line %zu: Unrecognized configuration option.\n", currLineNumber);
}
else
printf("Line %zu: Too few columns.\n", currLineNumber);
}
else if(StrRangeEq(optionName, "Macro"))
{
if(csvSplit.GetCount() >= 3)
{
const StrRange subOptionName = csvSplit.GetRange(1);
if(StrRangeEq(subOptionName, "VMA_DEBUG_ALWAYS_DEDICATED_MEMORY"))
SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_ALWAYS_DEDICATED_MEMORY, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "VMA_DEBUG_ALIGNMENT"))
SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_ALIGNMENT, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "VMA_DEBUG_MARGIN"))
SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_MARGIN, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "VMA_DEBUG_INITIALIZE_ALLOCATIONS"))
SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_INITIALIZE_ALLOCATIONS, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "VMA_DEBUG_DETECT_CORRUPTION"))
SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_DETECT_CORRUPTION, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "VMA_DEBUG_GLOBAL_MUTEX"))
SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_GLOBAL_MUTEX, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY"))
SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "VMA_SMALL_HEAP_MAX_SIZE"))
SetOption(currLineNumber, OPTION::Macro_VMA_SMALL_HEAP_MAX_SIZE, csvSplit.GetRange(2));
else if(StrRangeEq(subOptionName, "VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE"))
SetOption(currLineNumber, OPTION::Macro_VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE, csvSplit.GetRange(2));
else
printf("Line %zu: Unrecognized configuration option.\n", currLineNumber);
}
else
printf("Line %zu: Too few columns.\n", currLineNumber);
}
else if(StrRangeEq(optionName, "PhysicalDeviceMemory"))
{
uint32_t value = 0;
if(csvSplit.GetCount() == 3 && StrRangeEq(csvSplit.GetRange(1), "HeapCount") &&
StrRangeToUint(csvSplit.GetRange(2), value))
{
m_MemProps.memoryHeapCount = value;
}
else if(csvSplit.GetCount() == 3 && StrRangeEq(csvSplit.GetRange(1), "TypeCount") &&
StrRangeToUint(csvSplit.GetRange(2), value))
{
m_MemProps.memoryTypeCount = value;
}
else if(csvSplit.GetCount() == 5 && StrRangeEq(csvSplit.GetRange(1), "Heap") &&
StrRangeToUint(csvSplit.GetRange(2), value) &&
value < m_MemProps.memoryHeapCount)
{
if(StrRangeEq(csvSplit.GetRange(3), "size") &&
StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryHeaps[value].size))
{
// Parsed.
}
else if(StrRangeEq(csvSplit.GetRange(3), "flags") &&
StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryHeaps[value].flags))
{
// Parsed.
}
else
printf("Line %zu: Invalid configuration option.\n", currLineNumber);
}
else if(csvSplit.GetCount() == 5 && StrRangeEq(csvSplit.GetRange(1), "Type") &&
StrRangeToUint(csvSplit.GetRange(2), value) &&
value < m_MemProps.memoryTypeCount)
{
if(StrRangeEq(csvSplit.GetRange(3), "heapIndex") &&
StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryTypes[value].heapIndex))
{
// Parsed.
}
else if(StrRangeEq(csvSplit.GetRange(3), "propertyFlags") &&
StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryTypes[value].propertyFlags))
{
// Parsed.
}
else
printf("Line %zu: Invalid configuration option.\n", currLineNumber);
}
else
printf("Line %zu: Invalid configuration option.\n", currLineNumber);
}
else
printf("Line %zu: Unrecognized configuration option.\n", currLineNumber);
}
return true;
}
void ConfigurationParser::Compare(
const VkPhysicalDeviceProperties& currDevProps,
const VkPhysicalDeviceMemoryProperties& currMemProps,
uint32_t vulkanApiVersion,
bool currMemoryBudgetEnabled)
{
char vulkanApiVersionStr[32];
sprintf_s(vulkanApiVersionStr, "%u,%u", VK_VERSION_MAJOR(vulkanApiVersion), VK_VERSION_MINOR(vulkanApiVersion));
CompareOption(VERBOSITY::DEFAULT, "VulkanApiVersion",
OPTION::VulkanApiVersion, vulkanApiVersionStr);
CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice apiVersion",
OPTION::PhysicalDevice_apiVersion, currDevProps.apiVersion);
CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice driverVersion",
OPTION::PhysicalDevice_driverVersion, currDevProps.driverVersion);
CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice vendorID",
OPTION::PhysicalDevice_vendorID, currDevProps.vendorID);
CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice deviceID",
OPTION::PhysicalDevice_deviceID, currDevProps.deviceID);
CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice deviceType",
OPTION::PhysicalDevice_deviceType, (uint32_t)currDevProps.deviceType);
CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice deviceName",
OPTION::PhysicalDevice_deviceName, currDevProps.deviceName);
CompareOption(VERBOSITY::DEFAULT, "PhysicalDeviceLimits maxMemoryAllocationCount",
OPTION::PhysicalDeviceLimits_maxMemoryAllocationCount, currDevProps.limits.maxMemoryAllocationCount);
CompareOption(VERBOSITY::DEFAULT, "PhysicalDeviceLimits bufferImageGranularity",
OPTION::PhysicalDeviceLimits_bufferImageGranularity, currDevProps.limits.bufferImageGranularity);
CompareOption(VERBOSITY::DEFAULT, "PhysicalDeviceLimits nonCoherentAtomSize",
OPTION::PhysicalDeviceLimits_nonCoherentAtomSize, currDevProps.limits.nonCoherentAtomSize);
CompareMemProps(currMemProps);
}
void ConfigurationParser::SetOption(
size_t lineNumber,
OPTION option,
const StrRange& str)
{
if(m_OptionSet[(size_t)option])
{
printf("Line %zu: Option already specified.\n" ,lineNumber);
}
m_OptionSet[(size_t)option] = true;
std::string val;
str.to_str(val);
m_OptionValue[(size_t)option] = std::move(val);
}
void ConfigurationParser::EnsureWarningHeader()
{
if(!m_WarningHeaderPrinted)
{
printf("WARNING: Following configuration parameters don't match:\n");
m_WarningHeaderPrinted = true;
}
}
void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name,
OPTION option, uint32_t currValue)
{
if(m_OptionSet[(size_t)option] &&
g_Verbosity >= minVerbosity)
{
uint32_t origValue;
if(StrRangeToUint(StrRange(m_OptionValue[(size_t)option]), origValue))
{
if(origValue != currValue)
{
EnsureWarningHeader();
printf(" %s: original %u, current %u\n", name, origValue, currValue);
}
}
}
}
void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name,
OPTION option, uint64_t currValue)
{
if(m_OptionSet[(size_t)option] &&
g_Verbosity >= minVerbosity)
{
uint64_t origValue;
if(StrRangeToUint(StrRange(m_OptionValue[(size_t)option]), origValue))
{
if(origValue != currValue)
{
EnsureWarningHeader();
printf(" %s: original %llu, current %llu\n", name, origValue, currValue);
}
}
}
}
void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name,
OPTION option, bool currValue)
{
if(m_OptionSet[(size_t)option] &&
g_Verbosity >= minVerbosity)
{
bool origValue;
if(StrRangeToBool(StrRange(m_OptionValue[(size_t)option]), origValue))
{
if(origValue != currValue)
{
EnsureWarningHeader();
printf(" %s: original %u, current %u\n", name,
origValue ? 1 : 0,
currValue ? 1 : 0);
}
}
}
}
void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name,
OPTION option, const char* currValue)
{
if(m_OptionSet[(size_t)option] &&
g_Verbosity >= minVerbosity)
{
const std::string& origValue = m_OptionValue[(size_t)option];
if(origValue != currValue)
{
EnsureWarningHeader();
printf(" %s: original \"%s\", current \"%s\"\n", name, origValue.c_str(), currValue);
}
}
}
void ConfigurationParser::CompareMemProps(
const VkPhysicalDeviceMemoryProperties& currMemProps)
{
if(g_Verbosity < VERBOSITY::DEFAULT)
{
return;
}
bool memoryMatch =
currMemProps.memoryHeapCount == m_MemProps.memoryHeapCount &&
currMemProps.memoryTypeCount == m_MemProps.memoryTypeCount;
for(uint32_t i = 0; memoryMatch && i < currMemProps.memoryHeapCount; ++i)
{
memoryMatch =
currMemProps.memoryHeaps[i].flags == m_MemProps.memoryHeaps[i].flags;
}
for(uint32_t i = 0; memoryMatch && i < currMemProps.memoryTypeCount; ++i)
{
memoryMatch =
currMemProps.memoryTypes[i].heapIndex == m_MemProps.memoryTypes[i].heapIndex &&
currMemProps.memoryTypes[i].propertyFlags == m_MemProps.memoryTypes[i].propertyFlags;
}
if(memoryMatch && g_Verbosity == VERBOSITY::MAXIMUM)
{
bool memorySizeMatch = true;
for(uint32_t i = 0; memorySizeMatch && i < currMemProps.memoryHeapCount; ++i)
{
memorySizeMatch =
currMemProps.memoryHeaps[i].size == m_MemProps.memoryHeaps[i].size;
}
if(!memorySizeMatch)
{
printf("WARNING: Sizes of original memory heaps are different from current ones.\n");
}
}
else
{
printf("WARNING: Layout of original memory heaps and types is different from current one.\n");
}
}
////////////////////////////////////////////////////////////////////////////////
// class Player
static const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation";
static VkBool32 VKAPI_PTR MyDebugReportCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageTypes,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData)
{
assert(pCallbackData && pCallbackData->pMessageIdName && pCallbackData->pMessage);
printf("%s \xBA %s\n", pCallbackData->pMessageIdName, pCallbackData->pMessage);
return VK_FALSE;
}
static bool IsLayerSupported(const VkLayerProperties* pProps, size_t propCount, const char* pLayerName)
{
const VkLayerProperties* propsEnd = pProps + propCount;
return std::find_if(
pProps,
propsEnd,
[pLayerName](const VkLayerProperties& prop) -> bool {
return strcmp(pLayerName, prop.layerName) == 0;
}) != propsEnd;
}
static const size_t FIRST_PARAM_INDEX = 4;
static void InitVulkanFeatures(
VkPhysicalDeviceFeatures& outFeatures,
const VkPhysicalDeviceFeatures& supportedFeatures)
{
ZeroMemory(&outFeatures, sizeof(outFeatures));
// Enable something what may interact with memory/buffer/image support.
outFeatures.fullDrawIndexUint32 = supportedFeatures.fullDrawIndexUint32;
outFeatures.imageCubeArray = supportedFeatures.imageCubeArray;
outFeatures.geometryShader = supportedFeatures.geometryShader;
outFeatures.tessellationShader = supportedFeatures.tessellationShader;
outFeatures.multiDrawIndirect = supportedFeatures.multiDrawIndirect;
outFeatures.textureCompressionETC2 = supportedFeatures.textureCompressionETC2;
outFeatures.textureCompressionASTC_LDR = supportedFeatures.textureCompressionASTC_LDR;
outFeatures.textureCompressionBC = supportedFeatures.textureCompressionBC;
}
class Player
{
public:
Player();
int Init();
~Player();
void ApplyConfig(ConfigurationParser& configParser);
void ExecuteLine(size_t lineNumber, const StrRange& line);
void DumpStats(const char* fileNameFormat, size_t lineNumber, bool detailed);
void Defragment();
void PrintStats();
private:
static const size_t MAX_WARNINGS_TO_SHOW = 64;
size_t m_WarningCount = 0;
bool m_AllocateForBufferImageWarningIssued = false;
VkInstance m_VulkanInstance = VK_NULL_HANDLE;
VkPhysicalDevice m_PhysicalDevice = VK_NULL_HANDLE;
uint32_t m_GraphicsQueueFamilyIndex = UINT32_MAX;
uint32_t m_TransferQueueFamilyIndex = UINT32_MAX;
VkDevice m_Device = VK_NULL_HANDLE;
VkQueue m_GraphicsQueue = VK_NULL_HANDLE;
VkQueue m_TransferQueue = VK_NULL_HANDLE;
VmaAllocator m_Allocator = VK_NULL_HANDLE;
VkCommandPool m_CommandPool = VK_NULL_HANDLE;
VkCommandBuffer m_CommandBuffer = VK_NULL_HANDLE;
bool m_MemoryBudgetEnabled = false;
const VkPhysicalDeviceProperties* m_DevProps = nullptr;
const VkPhysicalDeviceMemoryProperties* m_MemProps = nullptr;
PFN_vkCreateDebugUtilsMessengerEXT m_vkCreateDebugUtilsMessengerEXT = nullptr;
PFN_vkDestroyDebugUtilsMessengerEXT m_vkDestroyDebugUtilsMessengerEXT = nullptr;
VkDebugUtilsMessengerEXT m_DebugUtilsMessenger = VK_NULL_HANDLE;
uint32_t m_VmaFrameIndex = 0;
// Any of these handles null can mean it was created in original but couldn't be created now.
struct Pool
{
VmaPool pool;
};
struct Allocation
{
uint32_t allocationFlags = 0;
VmaAllocation allocation = VK_NULL_HANDLE;
VkBuffer buffer = VK_NULL_HANDLE;
VkImage image = VK_NULL_HANDLE;
};
std::unordered_map<uint64_t, Pool> m_Pools;
std::unordered_map<uint64_t, Allocation> m_Allocations;
std::unordered_map<uint64_t, VmaDefragmentationContext> m_DefragmentationContexts;
struct Thread
{
uint32_t callCount;
};
std::unordered_map<uint32_t, Thread> m_Threads;
// Copy of column [1] from previously parsed line.
std::string m_LastLineTimeStr;
Statistics m_Stats;
std::vector<char> m_UserDataTmpStr;
void Destroy(const Allocation& alloc);
// Finds VmaPool bu original pointer.
// If origPool = null, returns true and outPool = null.
// If failed, prints warning, returns false and outPool = null.
bool FindPool(size_t lineNumber, uint64_t origPool, VmaPool& outPool);
// If allocation with that origPtr already exists, prints warning and replaces it.
void AddAllocation(size_t lineNumber, uint64_t origPtr, VkResult res, const char* functionName, Allocation&& allocDesc);
// Increments warning counter. Returns true if warning message should be printed.
bool IssueWarning();
int InitVulkan();
void FinalizeVulkan();
void RegisterDebugCallbacks();
void UnregisterDebugCallbacks();
// If parmeter count doesn't match, issues warning and returns false.
bool ValidateFunctionParameterCount(size_t lineNumber, const CsvSplit& csvSplit, size_t expectedParamCount, bool lastUnbound);
// If failed, prints warning, returns false, and sets allocCreateInfo.pUserData to null.
bool PrepareUserData(size_t lineNumber, uint32_t allocCreateFlags, const StrRange& userDataColumn, const StrRange& wholeLine, void*& outUserData);
void UpdateMemStats();
void ExecuteCreatePool(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteDestroyPool(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteSetAllocationUserData(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteCreateBuffer(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteDestroyBuffer(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::DestroyBuffer); DestroyAllocation(lineNumber, csvSplit, "vmaDestroyBuffer"); }
void ExecuteCreateImage(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteDestroyImage(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::DestroyImage); DestroyAllocation(lineNumber, csvSplit, "vmaDestroyImage"); }
void ExecuteFreeMemory(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::FreeMemory); DestroyAllocation(lineNumber, csvSplit, "vmaFreeMemory"); }
void ExecuteFreeMemoryPages(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteCreateLostAllocation(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteAllocateMemory(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteAllocateMemoryPages(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteAllocateMemoryForBufferOrImage(size_t lineNumber, const CsvSplit& csvSplit, OBJECT_TYPE objType);
void ExecuteMapMemory(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteUnmapMemory(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteFlushAllocation(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteInvalidateAllocation(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteTouchAllocation(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteGetAllocationInfo(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteMakePoolAllocationsLost(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteResizeAllocation(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteDefragmentationBegin(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteDefragmentationEnd(size_t lineNumber, const CsvSplit& csvSplit);
void ExecuteSetPoolName(size_t lineNumber, const CsvSplit& csvSplit);
void DestroyAllocation(size_t lineNumber, const CsvSplit& csvSplit, const char* functionName);
void PrintStats(const VmaStats& stats, const char* suffix);
void PrintStatInfo(const VmaStatInfo& info);
};
Player::Player()
{
}
int Player::Init()
{
int result = InitVulkan();
if(result == 0)
{
m_Stats.Init(m_MemProps->memoryHeapCount, m_MemProps->memoryTypeCount);
UpdateMemStats();
}
return result;
}
Player::~Player()
{
FinalizeVulkan();
if(g_Verbosity < VERBOSITY::MAXIMUM && m_WarningCount > MAX_WARNINGS_TO_SHOW)
printf("WARNING: %zu more warnings not shown.\n", m_WarningCount - MAX_WARNINGS_TO_SHOW);
}
void Player::ApplyConfig(ConfigurationParser& configParser)
{
configParser.Compare(*m_DevProps, *m_MemProps,
VULKAN_API_VERSION,
m_MemoryBudgetEnabled);
}
void Player::ExecuteLine(size_t lineNumber, const StrRange& line)
{
CsvSplit csvSplit;
csvSplit.Set(line);
if(csvSplit.GetCount() >= FIRST_PARAM_INDEX)
{
// Check thread ID.
uint32_t threadId;
if(StrRangeToUint(csvSplit.GetRange(0), threadId))
{
const auto it = m_Threads.find(threadId);
if(it != m_Threads.end())
{
++it->second.callCount;
}
else
{
Thread threadInfo{};
threadInfo.callCount = 1;
m_Threads[threadId] = threadInfo;
}
}
else
{
if(IssueWarning())
{
printf("Line %zu: Incorrect thread ID.\n", lineNumber);
}
}
// Save time.
csvSplit.GetRange(1).to_str(m_LastLineTimeStr);
// Update VMA current frame index.
StrRange frameIndexStr = csvSplit.GetRange(2);
uint32_t frameIndex;
if(StrRangeToUint(frameIndexStr, frameIndex))
{
if(frameIndex != m_VmaFrameIndex)
{
vmaSetCurrentFrameIndex(m_Allocator, frameIndex);
m_VmaFrameIndex = frameIndex;
}
}
else
{
if(IssueWarning())
{
printf("Line %zu: Incorrect frame index.\n", lineNumber);
}
}
StrRange functionName = csvSplit.GetRange(3);
if(StrRangeEq(functionName, "vmaCreateAllocator"))
{
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 0, false))
{
// Nothing.
}
}
else if(StrRangeEq(functionName, "vmaDestroyAllocator"))
{
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 0, false))
{
// Nothing.
}
}
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreatePool]))
ExecuteCreatePool(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DestroyPool]))
ExecuteDestroyPool(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::SetAllocationUserData]))
ExecuteSetAllocationUserData(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreateBuffer]))
ExecuteCreateBuffer(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DestroyBuffer]))
ExecuteDestroyBuffer(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreateImage]))
ExecuteCreateImage(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DestroyImage]))
ExecuteDestroyImage(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::FreeMemory]))
ExecuteFreeMemory(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::FreeMemoryPages]))
ExecuteFreeMemoryPages(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreateLostAllocation]))
ExecuteCreateLostAllocation(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemory]))
ExecuteAllocateMemory(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryPages]))
ExecuteAllocateMemoryPages(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryForBuffer]))
ExecuteAllocateMemoryForBufferOrImage(lineNumber, csvSplit, OBJECT_TYPE::BUFFER);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryForImage]))
ExecuteAllocateMemoryForBufferOrImage(lineNumber, csvSplit, OBJECT_TYPE::IMAGE);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::MapMemory]))
ExecuteMapMemory(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::UnmapMemory]))
ExecuteUnmapMemory(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::FlushAllocation]))
ExecuteFlushAllocation(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::InvalidateAllocation]))
ExecuteInvalidateAllocation(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::TouchAllocation]))
ExecuteTouchAllocation(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::GetAllocationInfo]))
ExecuteGetAllocationInfo(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::MakePoolAllocationsLost]))
ExecuteMakePoolAllocationsLost(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::ResizeAllocation]))
ExecuteResizeAllocation(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DefragmentationBegin]))
ExecuteDefragmentationBegin(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DefragmentationEnd]))
ExecuteDefragmentationEnd(lineNumber, csvSplit);
else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::SetPoolName]))
ExecuteSetPoolName(lineNumber, csvSplit);
else
{
if(IssueWarning())
{
printf("Line %zu: Unknown function.\n", lineNumber);
}
}
}
else
{
if(IssueWarning())
{
printf("Line %zu: Too few columns.\n", lineNumber);
}
}
}
void Player::DumpStats(const char* fileNameFormat, size_t lineNumber, bool detailed)
{
char* pStatsString = nullptr;
vmaBuildStatsString(m_Allocator, &pStatsString, detailed ? VK_TRUE : VK_FALSE);
char fileName[MAX_PATH];
sprintf_s(fileName, fileNameFormat, lineNumber);
FILE* file = nullptr;
errno_t err = fopen_s(&file, fileName, "wb");
if(err == 0)
{
fwrite(pStatsString, 1, strlen(pStatsString), file);
fclose(file);
}
else
{
printf("ERROR: Failed to write file: %s\n", fileName);
}
vmaFreeStatsString(m_Allocator, pStatsString);
}
void Player::Destroy(const Allocation& alloc)
{
if(alloc.buffer)
{
assert(alloc.image == VK_NULL_HANDLE);
vmaDestroyBuffer(m_Allocator, alloc.buffer, alloc.allocation);
}
else if(alloc.image)
{
vmaDestroyImage(m_Allocator, alloc.image, alloc.allocation);
}
else
vmaFreeMemory(m_Allocator, alloc.allocation);
}
bool Player::FindPool(size_t lineNumber, uint64_t origPool, VmaPool& outPool)
{
outPool = VK_NULL_HANDLE;
if(origPool != 0)
{
const auto poolIt = m_Pools.find(origPool);
if(poolIt != m_Pools.end())
{
outPool = poolIt->second.pool;
return true;
}
else
{
if(IssueWarning())
{
printf("Line %zu: Pool %llX not found.\n", lineNumber, origPool);
}
}
}
return true;
}
void Player::AddAllocation(size_t lineNumber, uint64_t origPtr, VkResult res, const char* functionName, Allocation&& allocDesc)
{
if(origPtr)
{
if(res == VK_SUCCESS)
{
// Originally succeeded, currently succeeded.
// Just save pointer (done below).
}
else
{
// Originally succeeded, currently failed.
// Print warning. Save null pointer.
if(IssueWarning())
{
printf("Line %zu: %s failed (%d), while originally succeeded.\n", lineNumber, functionName, res);
}
}
const auto existingIt = m_Allocations.find(origPtr);
if(existingIt != m_Allocations.end())
{
if(IssueWarning())
{
printf("Line %zu: Allocation %llX already exists.\n", lineNumber, origPtr);
}
}
m_Allocations[origPtr] = std::move(allocDesc);
}
else
{
if(res == VK_SUCCESS)
{
// Originally failed, currently succeeded.
// Print warning, destroy the object.
if(IssueWarning())
{
printf("Line %zu: %s succeeded, originally failed.\n", lineNumber, functionName);
}
Destroy(allocDesc);
}
else
{
// Originally failed, currently failed.
// Print warning.
if(IssueWarning())
{
printf("Line %zu: %s failed (%d), originally also failed.\n", lineNumber, functionName, res);
}
}
}
}
bool Player::IssueWarning()
{
if(g_Verbosity < VERBOSITY::MAXIMUM)
{
return m_WarningCount++ < MAX_WARNINGS_TO_SHOW;
}
else
{
++m_WarningCount;
return true;
}
}
int Player::InitVulkan()
{
if(g_Verbosity == VERBOSITY::MAXIMUM)
{
printf("Initializing Vulkan...\n");
}
uint32_t instanceLayerPropCount = 0;
VkResult res = vkEnumerateInstanceLayerProperties(&instanceLayerPropCount, nullptr);
assert(res == VK_SUCCESS);
std::vector<VkLayerProperties> instanceLayerProps(instanceLayerPropCount);
if(instanceLayerPropCount > 0)
{
res = vkEnumerateInstanceLayerProperties(&instanceLayerPropCount, instanceLayerProps.data());
assert(res == VK_SUCCESS);
}
const bool validationLayersAvailable =
IsLayerSupported(instanceLayerProps.data(), instanceLayerProps.size(), VALIDATION_LAYER_NAME);
bool validationLayersEnabled = false;
switch(g_VK_LAYER_KHRONOS_validation)
{
case VULKAN_EXTENSION_REQUEST::DISABLED:
break;
case VULKAN_EXTENSION_REQUEST::DEFAULT:
validationLayersEnabled = validationLayersAvailable;
break;
case VULKAN_EXTENSION_REQUEST::ENABLED:
validationLayersEnabled = validationLayersAvailable;
if(!validationLayersAvailable)
{
printf("WARNING: %s layer cannot be enabled.\n", VALIDATION_LAYER_NAME);
}
break;
default: assert(0);
}
uint32_t availableInstanceExtensionCount = 0;
res = vkEnumerateInstanceExtensionProperties(nullptr, &availableInstanceExtensionCount, nullptr);
assert(res == VK_SUCCESS);
std::vector<VkExtensionProperties> availableInstanceExtensions(availableInstanceExtensionCount);
if(availableInstanceExtensionCount > 0)
{
res = vkEnumerateInstanceExtensionProperties(nullptr, &availableInstanceExtensionCount, availableInstanceExtensions.data());
assert(res == VK_SUCCESS);
}
std::vector<const char*> enabledInstanceExtensions;
//enabledInstanceExtensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
//enabledInstanceExtensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
std::vector<const char*> instanceLayers;
if(validationLayersEnabled)
{
instanceLayers.push_back(VALIDATION_LAYER_NAME);
}
bool VK_KHR_get_physical_device_properties2_enabled = false;
bool VK_EXT_debug_utils_enabled = false;
for(const auto& extensionProperties : availableInstanceExtensions)
{
if(strcmp(extensionProperties.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0)
{
enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
VK_KHR_get_physical_device_properties2_enabled = true;
}
else if(strcmp(extensionProperties.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0)
{
if(validationLayersEnabled)
{
enabledInstanceExtensions.push_back("VK_EXT_debug_utils");
VK_EXT_debug_utils_enabled = true;
}
}
}
VkApplicationInfo appInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO };
appInfo.pApplicationName = "VmaReplay";
appInfo.applicationVersion = VK_MAKE_VERSION(2, 3, 0);
appInfo.pEngineName = "Vulkan Memory Allocator";
appInfo.engineVersion = VK_MAKE_VERSION(2, 3, 0);
appInfo.apiVersion = VULKAN_API_VERSION;
VkInstanceCreateInfo instInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO };
instInfo.pApplicationInfo = &appInfo;
instInfo.enabledExtensionCount = (uint32_t)enabledInstanceExtensions.size();
instInfo.ppEnabledExtensionNames = enabledInstanceExtensions.data();
instInfo.enabledLayerCount = (uint32_t)instanceLayers.size();
instInfo.ppEnabledLayerNames = instanceLayers.data();
res = vkCreateInstance(&instInfo, NULL, &m_VulkanInstance);
if(res != VK_SUCCESS)
{
printf("ERROR: vkCreateInstance failed (%d)\n", res);
return RESULT_ERROR_VULKAN;
}
if(VK_EXT_debug_utils_enabled)
{
RegisterDebugCallbacks();
}
// Find physical device
uint32_t physicalDeviceCount = 0;
res = vkEnumeratePhysicalDevices(m_VulkanInstance, &physicalDeviceCount, nullptr);
assert(res == VK_SUCCESS);
if(physicalDeviceCount == 0)
{
printf("ERROR: No Vulkan physical devices found.\n");
return RESULT_ERROR_VULKAN;
}
std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
res = vkEnumeratePhysicalDevices(m_VulkanInstance, &physicalDeviceCount, physicalDevices.data());
assert(res == VK_SUCCESS);
if(g_PhysicalDeviceIndex >= physicalDeviceCount)
{
printf("ERROR: Incorrect Vulkan physical device index %u. System has %u physical devices.\n",
g_PhysicalDeviceIndex,
physicalDeviceCount);
return RESULT_ERROR_VULKAN;
}
m_PhysicalDevice = physicalDevices[0];
// Find queue family index
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(m_PhysicalDevice, &queueFamilyCount, nullptr);
if(queueFamilyCount)
{
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(m_PhysicalDevice, &queueFamilyCount, queueFamilies.data());
for(uint32_t i = 0; i < queueFamilyCount; ++i)
{
if(queueFamilies[i].queueCount > 0)
{
if(m_GraphicsQueueFamilyIndex == UINT32_MAX &&
(queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)
{
m_GraphicsQueueFamilyIndex = i;
}
if(m_TransferQueueFamilyIndex == UINT32_MAX &&
(queueFamilies[i].queueFlags & VK_QUEUE_TRANSFER_BIT) != 0)
{
m_TransferQueueFamilyIndex = i;
}
}
}
}
if(m_GraphicsQueueFamilyIndex == UINT_MAX)
{
printf("ERROR: Couldn't find graphics queue.\n");
return RESULT_ERROR_VULKAN;
}
if(m_TransferQueueFamilyIndex == UINT_MAX)
{
printf("ERROR: Couldn't find transfer queue.\n");
return RESULT_ERROR_VULKAN;
}
VkPhysicalDeviceFeatures supportedFeatures;
vkGetPhysicalDeviceFeatures(m_PhysicalDevice, &supportedFeatures);
// Create logical device
const float queuePriority = 1.f;
VkDeviceQueueCreateInfo deviceQueueCreateInfo[2] = {};
deviceQueueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
deviceQueueCreateInfo[0].queueFamilyIndex = m_GraphicsQueueFamilyIndex;
deviceQueueCreateInfo[0].queueCount = 1;
deviceQueueCreateInfo[0].pQueuePriorities = &queuePriority;
if(m_TransferQueueFamilyIndex != m_GraphicsQueueFamilyIndex)
{
deviceQueueCreateInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
deviceQueueCreateInfo[1].queueFamilyIndex = m_TransferQueueFamilyIndex;
deviceQueueCreateInfo[1].queueCount = 1;
deviceQueueCreateInfo[1].pQueuePriorities = &queuePriority;
}
// Enable something what may interact with memory/buffer/image support.
VkPhysicalDeviceFeatures enabledFeatures;
InitVulkanFeatures(enabledFeatures, supportedFeatures);
bool VK_KHR_get_memory_requirements2_available = false;
// Determine list of device extensions to enable.
std::vector<const char*> enabledDeviceExtensions;
//enabledDeviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
bool memoryBudgetAvailable = false;
{
uint32_t propertyCount = 0;
res = vkEnumerateDeviceExtensionProperties(m_PhysicalDevice, nullptr, &propertyCount, nullptr);
assert(res == VK_SUCCESS);
if(propertyCount)
{
std::vector<VkExtensionProperties> properties{propertyCount};
res = vkEnumerateDeviceExtensionProperties(m_PhysicalDevice, nullptr, &propertyCount, properties.data());
assert(res == VK_SUCCESS);
for(uint32_t i = 0; i < propertyCount; ++i)
{
if(strcmp(properties[i].extensionName, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME) == 0)
{
VK_KHR_get_memory_requirements2_available = true;
}
else if(strcmp(properties[i].extensionName, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) == 0)
{
if(VK_KHR_get_physical_device_properties2_enabled)
{
memoryBudgetAvailable = true;
}
}
}
}
}
switch(g_VK_EXT_memory_budget_request)
{
case VULKAN_EXTENSION_REQUEST::DISABLED:
break;
case VULKAN_EXTENSION_REQUEST::DEFAULT:
m_MemoryBudgetEnabled = memoryBudgetAvailable;
break;
case VULKAN_EXTENSION_REQUEST::ENABLED:
m_MemoryBudgetEnabled = memoryBudgetAvailable;
if(!memoryBudgetAvailable)
{
printf("WARNING: VK_EXT_memory_budget extension cannot be enabled.\n");
}
break;
default: assert(0);
}
if(g_VK_AMD_device_coherent_memory_request == VULKAN_EXTENSION_REQUEST::ENABLED)
{
printf("WARNING: AMD_device_coherent_memory requested but not currently supported by the player.\n");
}
if(m_MemoryBudgetEnabled)
{
enabledDeviceExtensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
}
VkDeviceCreateInfo deviceCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
deviceCreateInfo.enabledExtensionCount = (uint32_t)enabledDeviceExtensions.size();
deviceCreateInfo.ppEnabledExtensionNames = !enabledDeviceExtensions.empty() ? enabledDeviceExtensions.data() : nullptr;
deviceCreateInfo.queueCreateInfoCount = m_TransferQueueFamilyIndex != m_GraphicsQueueFamilyIndex ? 2 : 1;
deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfo;
deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
res = vkCreateDevice(m_PhysicalDevice, &deviceCreateInfo, nullptr, &m_Device);
if(res != VK_SUCCESS)
{
printf("ERROR: vkCreateDevice failed (%d)\n", res);
return RESULT_ERROR_VULKAN;
}
// Fetch queues
vkGetDeviceQueue(m_Device, m_GraphicsQueueFamilyIndex, 0, &m_GraphicsQueue);
vkGetDeviceQueue(m_Device, m_TransferQueueFamilyIndex, 0, &m_TransferQueue);
// Create memory allocator
VmaDeviceMemoryCallbacks deviceMemoryCallbacks = {};
deviceMemoryCallbacks.pfnAllocate = AllocateDeviceMemoryCallback;
deviceMemoryCallbacks.pfnFree = FreeDeviceMemoryCallback;
VmaAllocatorCreateInfo allocatorInfo = {};
allocatorInfo.instance = m_VulkanInstance;
allocatorInfo.physicalDevice = m_PhysicalDevice;
allocatorInfo.device = m_Device;
allocatorInfo.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
allocatorInfo.pDeviceMemoryCallbacks = &deviceMemoryCallbacks;
allocatorInfo.vulkanApiVersion = VULKAN_API_VERSION;
if(m_MemoryBudgetEnabled)
{
allocatorInfo.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
}
res = vmaCreateAllocator(&allocatorInfo, &m_Allocator);
if(res != VK_SUCCESS)
{
printf("ERROR: vmaCreateAllocator failed (%d)\n", res);
return RESULT_ERROR_VULKAN;
}
vmaGetPhysicalDeviceProperties(m_Allocator, &m_DevProps);
vmaGetMemoryProperties(m_Allocator, &m_MemProps);
// Create command pool
VkCommandPoolCreateInfo cmdPoolCreateInfo = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
cmdPoolCreateInfo.queueFamilyIndex = m_TransferQueueFamilyIndex;
cmdPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
res = vkCreateCommandPool(m_Device, &cmdPoolCreateInfo, nullptr, &m_CommandPool);
if(res != VK_SUCCESS)
{
printf("ERROR: vkCreateCommandPool failed (%d)\n", res);
return RESULT_ERROR_VULKAN;
}
// Create command buffer
VkCommandBufferAllocateInfo cmdBufAllocInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
cmdBufAllocInfo.commandBufferCount = 1;
cmdBufAllocInfo.commandPool = m_CommandPool;
cmdBufAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
res = vkAllocateCommandBuffers(m_Device, &cmdBufAllocInfo, &m_CommandBuffer);
if(res != VK_SUCCESS)
{
printf("ERROR: vkAllocateCommandBuffers failed (%d)\n", res);
return RESULT_ERROR_VULKAN;
}
return 0;
}
void Player::FinalizeVulkan()
{
if(!m_DefragmentationContexts.empty())
{
printf("WARNING: Defragmentation contexts not destroyed: %zu.\n", m_DefragmentationContexts.size());
if(CLEANUP_LEAKED_OBJECTS)
{
for(const auto& it : m_DefragmentationContexts)
{
vmaDefragmentationEnd(m_Allocator, it.second);
}
}
m_DefragmentationContexts.clear();
}
if(!m_Allocations.empty())
{
printf("WARNING: Allocations not destroyed: %zu.\n", m_Allocations.size());
if(CLEANUP_LEAKED_OBJECTS)
{
for(const auto it : m_Allocations)
{
Destroy(it.second);
}
}
m_Allocations.clear();
}
if(!m_Pools.empty())
{
printf("WARNING: Custom pools not destroyed: %zu.\n", m_Pools.size());
if(CLEANUP_LEAKED_OBJECTS)
{
for(const auto it : m_Pools)
{
vmaDestroyPool(m_Allocator, it.second.pool);
}
}
m_Pools.clear();
}
vkDeviceWaitIdle(m_Device);
if(m_CommandBuffer != VK_NULL_HANDLE)
{
vkFreeCommandBuffers(m_Device, m_CommandPool, 1, &m_CommandBuffer);
m_CommandBuffer = VK_NULL_HANDLE;
}
if(m_CommandPool != VK_NULL_HANDLE)
{
vkDestroyCommandPool(m_Device, m_CommandPool, nullptr);
m_CommandPool = VK_NULL_HANDLE;
}
if(m_Allocator != VK_NULL_HANDLE)
{
vmaDestroyAllocator(m_Allocator);
m_Allocator = nullptr;
}
if(m_Device != VK_NULL_HANDLE)
{
vkDestroyDevice(m_Device, nullptr);
m_Device = nullptr;
}
UnregisterDebugCallbacks();
if(m_VulkanInstance != VK_NULL_HANDLE)
{
vkDestroyInstance(m_VulkanInstance, NULL);
m_VulkanInstance = VK_NULL_HANDLE;
}
}
void Player::RegisterDebugCallbacks()
{
static const VkDebugUtilsMessageSeverityFlagsEXT DEBUG_UTILS_MESSENGER_MESSAGE_SEVERITY =
//VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
//VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
static const VkDebugUtilsMessageTypeFlagsEXT DEBUG_UTILS_MESSENGER_MESSAGE_TYPE =
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
m_vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
m_VulkanInstance, "vkCreateDebugUtilsMessengerEXT");
m_vkDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
m_VulkanInstance, "vkDestroyDebugUtilsMessengerEXT");
assert(m_vkCreateDebugUtilsMessengerEXT);
assert(m_vkDestroyDebugUtilsMessengerEXT);
VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT };
messengerCreateInfo.messageSeverity = DEBUG_UTILS_MESSENGER_MESSAGE_SEVERITY;
messengerCreateInfo.messageType = DEBUG_UTILS_MESSENGER_MESSAGE_TYPE;
messengerCreateInfo.pfnUserCallback = MyDebugReportCallback;
VkResult res = m_vkCreateDebugUtilsMessengerEXT(m_VulkanInstance, &messengerCreateInfo, nullptr, &m_DebugUtilsMessenger);
if(res != VK_SUCCESS)
{
printf("ERROR: vkCreateDebugUtilsMessengerEXT failed (%d)\n", res);
m_DebugUtilsMessenger = VK_NULL_HANDLE;
}
}
void Player::UnregisterDebugCallbacks()
{
if(m_DebugUtilsMessenger)
{
m_vkDestroyDebugUtilsMessengerEXT(m_VulkanInstance, m_DebugUtilsMessenger, nullptr);
}
}
void Player::Defragment()
{
VmaStats stats;
vmaCalculateStats(m_Allocator, &stats);
PrintStats(stats, "before defragmentation");
const size_t allocCount = m_Allocations.size();
std::vector<VmaAllocation> allocations(allocCount);
size_t notNullAllocCount = 0;
for(const auto& it : m_Allocations)
{
if(it.second.allocation != VK_NULL_HANDLE)
{
allocations[notNullAllocCount] = it.second.allocation;
++notNullAllocCount;
}
}
if(notNullAllocCount == 0)
{
printf(" Nothing to defragment.\n");
return;
}
allocations.resize(notNullAllocCount);
std::vector<VkBool32> allocationsChanged(notNullAllocCount);
VmaDefragmentationStats defragStats = {};
VkCommandBufferBeginInfo cmdBufBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
cmdBufBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VkResult res = vkBeginCommandBuffer(m_CommandBuffer, &cmdBufBeginInfo);
if(res != VK_SUCCESS)
{
printf("ERROR: vkBeginCommandBuffer failed (%d)\n", res);
return;
}
const time_point timeBeg = std::chrono::high_resolution_clock::now();
VmaDefragmentationInfo2 defragInfo = {};
defragInfo.allocationCount = (uint32_t)notNullAllocCount;
defragInfo.pAllocations = allocations.data();
defragInfo.pAllocationsChanged = allocationsChanged.data();
defragInfo.maxCpuAllocationsToMove = UINT32_MAX;
defragInfo.maxCpuBytesToMove = VK_WHOLE_SIZE;
defragInfo.maxGpuAllocationsToMove = UINT32_MAX;
defragInfo.maxGpuBytesToMove = VK_WHOLE_SIZE;
defragInfo.flags = g_DefragmentationFlags;
defragInfo.commandBuffer = m_CommandBuffer;
VmaDefragmentationContext defragCtx = VK_NULL_HANDLE;
res = vmaDefragmentationBegin(m_Allocator, &defragInfo, &defragStats, &defragCtx);
const time_point timeAfterDefragBegin = std::chrono::high_resolution_clock::now();
vkEndCommandBuffer(m_CommandBuffer);
if(res >= VK_SUCCESS)
{
VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &m_CommandBuffer;
vkQueueSubmit(m_TransferQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(m_TransferQueue);
const time_point timeAfterGpu = std::chrono::high_resolution_clock::now();
vmaDefragmentationEnd(m_Allocator, defragCtx);
const time_point timeAfterDefragEnd = std::chrono::high_resolution_clock::now();
const duration defragDurationBegin = timeAfterDefragBegin - timeBeg;
const duration defragDurationGpu = timeAfterGpu - timeAfterDefragBegin;
const duration defragDurationEnd = timeAfterDefragEnd - timeAfterGpu;
// If anything changed.
if(defragStats.allocationsMoved > 0)
{
// Go over allocation that changed and destroy their buffers and images.
size_t i = 0; <