blob: 5a1f96079c95bb0eca84b0044e8c02d8e6506f4c [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_LUNARG_standard_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_LUNARG_standard_validation";
static const bool g_MemoryAliasingWarningEnabled = false;
static VKAPI_ATTR VkBool32 VKAPI_CALL MyDebugReportCallback(
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType,
uint64_t object,
size_t location,
int32_t messageCode,
const char* pLayerPrefix,
const char* pMessage,
void* pUserData)
{
// "Non-linear image 0xebc91 is aliased with linear buffer 0xeb8e4 which may indicate a bug."
if(!g_MemoryAliasingWarningEnabled && flags == VK_DEBUG_REPORT_WARNING_BIT_EXT &&
(strstr(pMessage, " is aliased with non-linear ") || strstr(pMessage, " is aliased with linear ")))
{
return VK_FALSE;
}
// Ignoring because when VK_KHR_dedicated_allocation extension is enabled,
// vkGetBufferMemoryRequirements2KHR function is used instead, while Validation
// Layer seems to be unaware of it.
if (strstr(pMessage, "but vkGetBufferMemoryRequirements() has not been called on that buffer") != nullptr)
{
return VK_FALSE;
}
if (strstr(pMessage, "but vkGetImageMemoryRequirements() has not been called on that image") != nullptr)
{
return VK_FALSE;
}
/*
"Mapping an image with layout VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL can result in undefined behavior if this memory is used by the device. Only GENERAL or PREINITIALIZED should be used."
Ignoring because we map entire VkDeviceMemory blocks, where different types of
images and buffers may end up together, especially on GPUs with unified memory
like Intel.
*/
if(strstr(pMessage, "Mapping an image with layout") != nullptr &&
strstr(pMessage, "can result in undefined behavior if this memory is used by the device") != nullptr)
{
return VK_FALSE;
}
printf("%s \xBA %s\n", pLayerPrefix, 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_vkCreateDebugReportCallbackEXT m_pvkCreateDebugReportCallbackEXT;
PFN_vkDebugReportMessageEXT m_pvkDebugReportMessageEXT;
PFN_vkDestroyDebugReportCallbackEXT m_pvkDestroyDebugReportCallbackEXT;
VkDebugReportCallbackEXT m_hCallback;
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();
// 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_LUNARG_standard_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);
enabledInstanceExtensions.push_back("VK_EXT_debug_report");
}
bool VK_KHR_get_physical_device_properties2_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;
}
}
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(validationLayersEnabled)
{
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;
}
if(m_pvkDestroyDebugReportCallbackEXT && m_hCallback != VK_NULL_HANDLE)
{
m_pvkDestroyDebugReportCallbackEXT(m_VulkanInstance, m_hCallback, nullptr);
m_hCallback = VK_NULL_HANDLE;
}
if(m_VulkanInstance != VK_NULL_HANDLE)
{
vkDestroyInstance(m_VulkanInstance, NULL);
m_VulkanInstance = VK_NULL_HANDLE;
}
}
void Player::RegisterDebugCallbacks()
{
m_pvkCreateDebugReportCallbackEXT =
reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>
(vkGetInstanceProcAddr(m_VulkanInstance, "vkCreateDebugReportCallbackEXT"));
m_pvkDebugReportMessageEXT =
reinterpret_cast<PFN_vkDebugReportMessageEXT>
(vkGetInstanceProcAddr(m_VulkanInstance, "vkDebugReportMessageEXT"));
m_pvkDestroyDebugReportCallbackEXT =
reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>
(vkGetInstanceProcAddr(m_VulkanInstance, "vkDestroyDebugReportCallbackEXT"));
assert(m_pvkCreateDebugReportCallbackEXT);
assert(m_pvkDebugReportMessageEXT);
assert(m_pvkDestroyDebugReportCallbackEXT);
VkDebugReportCallbackCreateInfoEXT callbackCreateInfo = { VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT };
callbackCreateInfo.flags = //VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
VK_DEBUG_REPORT_ERROR_BIT_EXT |
VK_DEBUG_REPORT_WARNING_BIT_EXT |
VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT /*|
VK_DEBUG_REPORT_DEBUG_BIT_EXT*/;
callbackCreateInfo.pfnCallback = &MyDebugReportCallback;
VkResult res = m_pvkCreateDebugReportCallbackEXT(m_VulkanInstance, &callbackCreateInfo, nullptr, &m_hCallback);
assert(res == VK_SUCCESS);
}
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 =