// | |
// 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) | |
{ | |
g_Statistics->RegisterDeviceMemoryAllocation(memoryType, size); | |
} | |
/// Callback function called before vkFreeMemory. | |
static void VKAPI_CALL FreeDeviceMemoryCallback( | |
VmaAllocator allocator, | |
uint32_t memoryType, | |
VkDeviceMemory memory, | |
VkDeviceSize size) | |
{ | |
// 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 = &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; | |
for(auto& it : m_Allocations) | |
{ | |
if(allocationsChanged[i] != VK_FALSE) | |
{ | |
if(it.second.buffer != VK_NULL_HANDLE) | |
{ | |
vkDestroyBuffer(m_Device, it.second.buffer, nullptr); | |
it.second.buffer = VK_NULL_HANDLE; | |
} | |
if(it.second.image != VK_NULL_HANDLE) | |
{ | |
vkDestroyImage(m_Device, it.second.image, nullptr); | |
it.second.image = VK_NULL_HANDLE; | |
} | |
} | |
++i; | |
} | |
} | |
// Print statistics | |
std::string defragDurationBeginStr; | |
std::string defragDurationGpuStr; | |
std::string defragDurationEndStr; | |
SecondsToFriendlyStr(ToFloatSeconds(defragDurationBegin), defragDurationBeginStr); | |
SecondsToFriendlyStr(ToFloatSeconds(defragDurationGpu), defragDurationGpuStr); | |
SecondsToFriendlyStr(ToFloatSeconds(defragDurationEnd), defragDurationEndStr); | |
printf(" Defragmentation took:\n"); | |
printf(" vmaDefragmentationBegin: %s\n", defragDurationBeginStr.c_str()); | |
printf(" GPU: %s\n", defragDurationGpuStr.c_str()); | |
printf(" vmaDefragmentationEnd: %s\n", defragDurationEndStr.c_str()); | |
printf(" VmaDefragmentationStats:\n"); | |
printf(" bytesMoved: %llu\n", defragStats.bytesMoved); | |
printf(" bytesFreed: %llu\n", defragStats.bytesFreed); | |
printf(" allocationsMoved: %u\n", defragStats.allocationsMoved); | |
printf(" deviceMemoryBlocksFreed: %u\n", defragStats.deviceMemoryBlocksFreed); | |
vmaCalculateStats(m_Allocator, &stats); | |
PrintStats(stats, "after defragmentation"); | |
} | |
else | |
{ | |
printf("vmaDefragmentationBegin failed (%d).\n", res); | |
} | |
vkResetCommandPool(m_Device, m_CommandPool, 0); | |
} | |
void Player::PrintStats() | |
{ | |
if(g_Verbosity == VERBOSITY::MINIMUM) | |
{ | |
return; | |
} | |
m_Stats.PrintDeviceMemStats(); | |
printf("Statistics:\n"); | |
if(m_Stats.GetAllocationCreationCount() > 0) | |
{ | |
printf(" Total allocations created: %zu\n", m_Stats.GetAllocationCreationCount()); | |
} | |
// Buffers | |
if(m_Stats.GetBufferCreationCount()) | |
{ | |
printf(" Total buffers created: %zu\n", m_Stats.GetBufferCreationCount()); | |
if(g_Verbosity == VERBOSITY::MAXIMUM) | |
{ | |
printf(" Class 0 (indirect/vertex/index): %zu\n", m_Stats.GetBufferCreationCount(0)); | |
printf(" Class 1 (storage): %zu\n", m_Stats.GetBufferCreationCount(1)); | |
printf(" Class 2 (uniform): %zu\n", m_Stats.GetBufferCreationCount(2)); | |
printf(" Class 3 (other): %zu\n", m_Stats.GetBufferCreationCount(3)); | |
} | |
} | |
// Images | |
const size_t imageCreationCount = | |
m_Stats.GetImageCreationCount(0) + | |
m_Stats.GetImageCreationCount(1) + | |
m_Stats.GetImageCreationCount(2) + | |
m_Stats.GetImageCreationCount(3) + | |
m_Stats.GetLinearImageCreationCount(); | |
if(imageCreationCount > 0) | |
{ | |
printf(" Total images created: %zu\n", imageCreationCount); | |
if(g_Verbosity == VERBOSITY::MAXIMUM) | |
{ | |
printf(" Class 0 (depth/stencil): %zu\n", m_Stats.GetImageCreationCount(0)); | |
printf(" Class 1 (attachment): %zu\n", m_Stats.GetImageCreationCount(1)); | |
printf(" Class 2 (sampled): %zu\n", m_Stats.GetImageCreationCount(2)); | |
printf(" Class 3 (other): %zu\n", m_Stats.GetImageCreationCount(3)); | |
if(m_Stats.GetLinearImageCreationCount() > 0) | |
{ | |
printf(" LINEAR tiling: %zu\n", m_Stats.GetLinearImageCreationCount()); | |
} | |
} | |
} | |
if(m_Stats.GetPoolCreationCount() > 0) | |
{ | |
printf(" Total custom pools created: %zu\n", m_Stats.GetPoolCreationCount()); | |
} | |
float lastTime; | |
if(!m_LastLineTimeStr.empty() && StrRangeToFloat(StrRange(m_LastLineTimeStr), lastTime)) | |
{ | |
std::string origTimeStr; | |
SecondsToFriendlyStr(lastTime, origTimeStr); | |
printf(" Original recording time: %s\n", origTimeStr.c_str()); | |
} | |
// Thread statistics. | |
const size_t threadCount = m_Threads.size(); | |
if(threadCount > 1) | |
{ | |
uint32_t threadCallCountMax = 0; | |
uint32_t threadCallCountSum = 0; | |
for(const auto& it : m_Threads) | |
{ | |
threadCallCountMax = std::max(threadCallCountMax, it.second.callCount); | |
threadCallCountSum += it.second.callCount; | |
} | |
printf(" Threads making calls to VMA: %zu\n", threadCount); | |
printf(" %.2f%% calls from most active thread.\n", | |
(float)threadCallCountMax * 100.f / (float)threadCallCountSum); | |
} | |
else | |
{ | |
printf(" VMA used from only one thread.\n"); | |
} | |
// Function call count | |
if(g_Verbosity == VERBOSITY::MAXIMUM) | |
{ | |
printf(" Function call count:\n"); | |
const size_t* const functionCallCount = m_Stats.GetFunctionCallCount(); | |
for(size_t i = 0; i < (size_t)VMA_FUNCTION::Count; ++i) | |
{ | |
if(functionCallCount[i] > 0) | |
{ | |
printf(" %s %zu\n", VMA_FUNCTION_NAMES[i], functionCallCount[i]); | |
} | |
} | |
} | |
// Detailed stats | |
if(g_Verbosity == VERBOSITY::MAXIMUM) | |
{ | |
m_Stats.PrintDetailedStats(); | |
} | |
if(g_MemStatsEnabled) | |
{ | |
m_Stats.PrintMemStats(); | |
} | |
} | |
bool Player::ValidateFunctionParameterCount(size_t lineNumber, const CsvSplit& csvSplit, size_t expectedParamCount, bool lastUnbound) | |
{ | |
bool ok; | |
if(lastUnbound) | |
ok = csvSplit.GetCount() >= FIRST_PARAM_INDEX + expectedParamCount - 1; | |
else | |
ok = csvSplit.GetCount() == FIRST_PARAM_INDEX + expectedParamCount; | |
if(!ok) | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Incorrect number of function parameters.\n", lineNumber); | |
} | |
} | |
return ok; | |
} | |
bool Player::PrepareUserData(size_t lineNumber, uint32_t allocCreateFlags, const StrRange& userDataColumn, const StrRange& wholeLine, void*& outUserData) | |
{ | |
if(!g_UserDataEnabled) | |
{ | |
outUserData = nullptr; | |
return true; | |
} | |
// String | |
if((allocCreateFlags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0) | |
{ | |
const size_t len = wholeLine.end - userDataColumn.beg; | |
m_UserDataTmpStr.resize(len + 1); | |
memcpy(m_UserDataTmpStr.data(), userDataColumn.beg, len); | |
m_UserDataTmpStr[len] = '\0'; | |
outUserData = m_UserDataTmpStr.data(); | |
return true; | |
} | |
// Pointer | |
else | |
{ | |
uint64_t pUserData = 0; | |
if(StrRangeToPtr(userDataColumn, pUserData)) | |
{ | |
outUserData = (void*)(uintptr_t)pUserData; | |
return true; | |
} | |
} | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid pUserData.\n", lineNumber); | |
} | |
outUserData = 0; | |
return false; | |
} | |
void Player::UpdateMemStats() | |
{ | |
if(!g_MemStatsEnabled) | |
{ | |
return; | |
} | |
VmaStats stats; | |
vmaCalculateStats(m_Allocator, &stats); | |
m_Stats.UpdateMemStats(stats); | |
} | |
void Player::ExecuteCreatePool(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::CreatePool); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 7, false)) | |
{ | |
VmaPoolCreateInfo poolCreateInfo = {}; | |
uint64_t origPtr = 0; | |
if(StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX), poolCreateInfo.memoryTypeIndex) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 1), poolCreateInfo.flags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 2), poolCreateInfo.blockSize) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 3), poolCreateInfo.minBlockCount) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 4), poolCreateInfo.maxBlockCount) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 5), poolCreateInfo.frameInUseCount) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 6), origPtr)) | |
{ | |
m_Stats.RegisterCreatePool(poolCreateInfo); | |
Pool poolDesc = {}; | |
VkResult res = vmaCreatePool(m_Allocator, &poolCreateInfo, &poolDesc.pool); | |
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: vmaCreatePool failed (%d), while originally succeeded.\n", lineNumber, res); | |
} | |
} | |
const auto existingIt = m_Pools.find(origPtr); | |
if(existingIt != m_Pools.end()) | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Pool %llX already exists.\n", lineNumber, origPtr); | |
} | |
} | |
m_Pools[origPtr] = poolDesc; | |
} | |
else | |
{ | |
if(res == VK_SUCCESS) | |
{ | |
// Originally failed, currently succeeded. | |
// Print warning, destroy the pool. | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: vmaCreatePool succeeded, originally failed.\n", lineNumber); | |
} | |
vmaDestroyPool(m_Allocator, poolDesc.pool); | |
} | |
else | |
{ | |
// Originally failed, currently failed. | |
// Print warning. | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: vmaCreatePool failed (%d), originally also failed.\n", lineNumber, res); | |
} | |
} | |
} | |
UpdateMemStats(); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaCreatePool.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteDestroyPool(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::DestroyPool); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 1, false)) | |
{ | |
uint64_t origPtr = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr)) | |
{ | |
if(origPtr != 0) | |
{ | |
const auto it = m_Pools.find(origPtr); | |
if(it != m_Pools.end()) | |
{ | |
vmaDestroyPool(m_Allocator, it->second.pool); | |
UpdateMemStats(); | |
m_Pools.erase(it); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Pool %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaDestroyPool.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteSetAllocationUserData(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::SetAllocationUserData); | |
if(!g_UserDataEnabled) | |
{ | |
return; | |
} | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 2, true)) | |
{ | |
uint64_t origPtr = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr)) | |
{ | |
const auto it = m_Allocations.find(origPtr); | |
if(it != m_Allocations.end()) | |
{ | |
void* pUserData = nullptr; | |
if(csvSplit.GetCount() > FIRST_PARAM_INDEX + 1) | |
{ | |
PrepareUserData( | |
lineNumber, | |
it->second.allocationFlags, | |
csvSplit.GetRange(FIRST_PARAM_INDEX + 1), | |
csvSplit.GetLine(), | |
pUserData); | |
} | |
vmaSetAllocationUserData(m_Allocator, it->second.allocation, pUserData); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Allocation %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaSetAllocationUserData.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteCreateBuffer(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::CreateBuffer); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 12, true)) | |
{ | |
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; | |
VmaAllocationCreateInfo allocCreateInfo = {}; | |
uint64_t origPool = 0; | |
uint64_t origPtr = 0; | |
if(StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX), bufCreateInfo.flags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 1), bufCreateInfo.size) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 2), bufCreateInfo.usage) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 3), (uint32_t&)bufCreateInfo.sharingMode) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 4), allocCreateInfo.flags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 5), (uint32_t&)allocCreateInfo.usage) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 6), allocCreateInfo.requiredFlags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 7), allocCreateInfo.preferredFlags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 8), allocCreateInfo.memoryTypeBits) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 9), origPool) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 10), origPtr)) | |
{ | |
FindPool(lineNumber, origPool, allocCreateInfo.pool); | |
if(csvSplit.GetCount() > FIRST_PARAM_INDEX + 11) | |
{ | |
PrepareUserData( | |
lineNumber, | |
allocCreateInfo.flags, | |
csvSplit.GetRange(FIRST_PARAM_INDEX + 11), | |
csvSplit.GetLine(), | |
allocCreateInfo.pUserData); | |
} | |
m_Stats.RegisterCreateBuffer(bufCreateInfo); | |
m_Stats.RegisterCreateAllocation(allocCreateInfo); | |
// Forcing VK_SHARING_MODE_EXCLUSIVE because we use only one queue anyway. | |
bufCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
Allocation allocDesc = { }; | |
allocDesc.allocationFlags = allocCreateInfo.flags; | |
VkResult res = vmaCreateBuffer(m_Allocator, &bufCreateInfo, &allocCreateInfo, &allocDesc.buffer, &allocDesc.allocation, nullptr); | |
UpdateMemStats(); | |
AddAllocation(lineNumber, origPtr, res, "vmaCreateBuffer", std::move(allocDesc)); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaCreateBuffer.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::DestroyAllocation(size_t lineNumber, const CsvSplit& csvSplit, const char* functionName) | |
{ | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 1, false)) | |
{ | |
uint64_t origAllocPtr = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origAllocPtr)) | |
{ | |
if(origAllocPtr != 0) | |
{ | |
const auto it = m_Allocations.find(origAllocPtr); | |
if(it != m_Allocations.end()) | |
{ | |
Destroy(it->second); | |
UpdateMemStats(); | |
m_Allocations.erase(it); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Allocation %llX not found.\n", lineNumber, origAllocPtr); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for %s.\n", lineNumber, functionName); | |
} | |
} | |
} | |
} | |
void Player::PrintStats(const VmaStats& stats, const char* suffix) | |
{ | |
printf(" VmaStats %s:\n", suffix); | |
printf(" total:\n"); | |
PrintStatInfo(stats.total); | |
if(g_Verbosity == VERBOSITY::MAXIMUM) | |
{ | |
for(uint32_t i = 0; i < m_MemProps->memoryHeapCount; ++i) | |
{ | |
printf(" memoryHeap[%u]:\n", i); | |
PrintStatInfo(stats.memoryHeap[i]); | |
} | |
for(uint32_t i = 0; i < m_MemProps->memoryTypeCount; ++i) | |
{ | |
printf(" memoryType[%u]:\n", i); | |
PrintStatInfo(stats.memoryType[i]); | |
} | |
} | |
} | |
void Player::PrintStatInfo(const VmaStatInfo& info) | |
{ | |
printf(" blockCount: %u\n", info.blockCount); | |
printf(" allocationCount: %u\n", info.allocationCount); | |
printf(" unusedRangeCount: %u\n", info.unusedRangeCount); | |
printf(" usedBytes: %llu\n", info.usedBytes); | |
printf(" unusedBytes: %llu\n", info.unusedBytes); | |
printf(" allocationSizeMin: %llu\n", info.allocationSizeMin); | |
printf(" allocationSizeAvg: %llu\n", info.allocationSizeAvg); | |
printf(" allocationSizeMax: %llu\n", info.allocationSizeMax); | |
printf(" unusedRangeSizeMin: %llu\n", info.unusedRangeSizeMin); | |
printf(" unusedRangeSizeAvg: %llu\n", info.unusedRangeSizeAvg); | |
printf(" unusedRangeSizeMax: %llu\n", info.unusedRangeSizeMax); | |
} | |
void Player::ExecuteCreateImage(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::CreateImage); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 21, true)) | |
{ | |
VkImageCreateInfo imageCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; | |
VmaAllocationCreateInfo allocCreateInfo = {}; | |
uint64_t origPool = 0; | |
uint64_t origPtr = 0; | |
if(StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX), imageCreateInfo.flags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 1), (uint32_t&)imageCreateInfo.imageType) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 2), (uint32_t&)imageCreateInfo.format) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 3), imageCreateInfo.extent.width) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 4), imageCreateInfo.extent.height) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 5), imageCreateInfo.extent.depth) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 6), imageCreateInfo.mipLevels) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 7), imageCreateInfo.arrayLayers) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 8), (uint32_t&)imageCreateInfo.samples) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 9), (uint32_t&)imageCreateInfo.tiling) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 10), imageCreateInfo.usage) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 11), (uint32_t&)imageCreateInfo.sharingMode) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 12), (uint32_t&)imageCreateInfo.initialLayout) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 13), allocCreateInfo.flags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 14), (uint32_t&)allocCreateInfo.usage) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 15), allocCreateInfo.requiredFlags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 16), allocCreateInfo.preferredFlags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 17), allocCreateInfo.memoryTypeBits) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 18), origPool) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 19), origPtr)) | |
{ | |
FindPool(lineNumber, origPool, allocCreateInfo.pool); | |
if(csvSplit.GetCount() > FIRST_PARAM_INDEX + 20) | |
{ | |
PrepareUserData( | |
lineNumber, | |
allocCreateInfo.flags, | |
csvSplit.GetRange(FIRST_PARAM_INDEX + 20), | |
csvSplit.GetLine(), | |
allocCreateInfo.pUserData); | |
} | |
m_Stats.RegisterCreateImage(imageCreateInfo); | |
m_Stats.RegisterCreateAllocation(allocCreateInfo); | |
// Forcing VK_SHARING_MODE_EXCLUSIVE because we use only one queue anyway. | |
imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
Allocation allocDesc = {}; | |
allocDesc.allocationFlags = allocCreateInfo.flags; | |
VkResult res = vmaCreateImage(m_Allocator, &imageCreateInfo, &allocCreateInfo, &allocDesc.image, &allocDesc.allocation, nullptr); | |
UpdateMemStats(); | |
AddAllocation(lineNumber, origPtr, res, "vmaCreateImage", std::move(allocDesc)); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaCreateImage.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteFreeMemoryPages(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::FreeMemoryPages); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 1, false)) | |
{ | |
std::vector<uint64_t> origAllocPtrs; | |
if(StrRangeToPtrList(csvSplit.GetRange(FIRST_PARAM_INDEX), origAllocPtrs)) | |
{ | |
const size_t allocCount = origAllocPtrs.size(); | |
size_t notNullCount = 0; | |
for(size_t i = 0; i < allocCount; ++i) | |
{ | |
const uint64_t origAllocPtr = origAllocPtrs[i]; | |
if(origAllocPtr != 0) | |
{ | |
const auto it = m_Allocations.find(origAllocPtr); | |
if(it != m_Allocations.end()) | |
{ | |
Destroy(it->second); | |
m_Allocations.erase(it); | |
++notNullCount; | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Allocation %llX not found.\n", lineNumber, origAllocPtr); | |
} | |
} | |
} | |
} | |
if(notNullCount) | |
{ | |
UpdateMemStats(); | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaFreeMemoryPages.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteCreateLostAllocation(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::CreateLostAllocation); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 1, false)) | |
{ | |
uint64_t origPtr = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr)) | |
{ | |
Allocation allocDesc = {}; | |
vmaCreateLostAllocation(m_Allocator, &allocDesc.allocation); | |
UpdateMemStats(); | |
m_Stats.RegisterCreateLostAllocation(); | |
AddAllocation(lineNumber, origPtr, VK_SUCCESS, "vmaCreateLostAllocation", std::move(allocDesc)); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaCreateLostAllocation.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteAllocateMemory(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::AllocateMemory); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 11, true)) | |
{ | |
VkMemoryRequirements memReq = {}; | |
VmaAllocationCreateInfo allocCreateInfo = {}; | |
uint64_t origPool = 0; | |
uint64_t origPtr = 0; | |
if(StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX), memReq.size) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 1), memReq.alignment) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 2), memReq.memoryTypeBits) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 3), allocCreateInfo.flags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 4), (uint32_t&)allocCreateInfo.usage) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 5), allocCreateInfo.requiredFlags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 6), allocCreateInfo.preferredFlags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 7), allocCreateInfo.memoryTypeBits) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 8), origPool) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 9), origPtr)) | |
{ | |
FindPool(lineNumber, origPool, allocCreateInfo.pool); | |
if(csvSplit.GetCount() > FIRST_PARAM_INDEX + 10) | |
{ | |
PrepareUserData( | |
lineNumber, | |
allocCreateInfo.flags, | |
csvSplit.GetRange(FIRST_PARAM_INDEX + 10), | |
csvSplit.GetLine(), | |
allocCreateInfo.pUserData); | |
} | |
UpdateMemStats(); | |
m_Stats.RegisterCreateAllocation(allocCreateInfo); | |
Allocation allocDesc = {}; | |
allocDesc.allocationFlags = allocCreateInfo.flags; | |
VkResult res = vmaAllocateMemory(m_Allocator, &memReq, &allocCreateInfo, &allocDesc.allocation, nullptr); | |
AddAllocation(lineNumber, origPtr, res, "vmaAllocateMemory", std::move(allocDesc)); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaAllocateMemory.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteAllocateMemoryPages(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::AllocateMemoryPages); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 11, true)) | |
{ | |
VkMemoryRequirements memReq = {}; | |
VmaAllocationCreateInfo allocCreateInfo = {}; | |
uint64_t origPool = 0; | |
std::vector<uint64_t> origPtrs; | |
if(StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX), memReq.size) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 1), memReq.alignment) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 2), memReq.memoryTypeBits) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 3), allocCreateInfo.flags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 4), (uint32_t&)allocCreateInfo.usage) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 5), allocCreateInfo.requiredFlags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 6), allocCreateInfo.preferredFlags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 7), allocCreateInfo.memoryTypeBits) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 8), origPool) && | |
StrRangeToPtrList(csvSplit.GetRange(FIRST_PARAM_INDEX + 9), origPtrs)) | |
{ | |
const size_t allocCount = origPtrs.size(); | |
if(allocCount > 0) | |
{ | |
FindPool(lineNumber, origPool, allocCreateInfo.pool); | |
if(csvSplit.GetCount() > FIRST_PARAM_INDEX + 10) | |
{ | |
PrepareUserData( | |
lineNumber, | |
allocCreateInfo.flags, | |
csvSplit.GetRange(FIRST_PARAM_INDEX + 10), | |
csvSplit.GetLine(), | |
allocCreateInfo.pUserData); | |
} | |
UpdateMemStats(); | |
m_Stats.RegisterCreateAllocation(allocCreateInfo, allocCount); | |
m_Stats.RegisterAllocateMemoryPages(allocCount); | |
std::vector<VmaAllocation> allocations(allocCount); | |
VkResult res = vmaAllocateMemoryPages(m_Allocator, &memReq, &allocCreateInfo, allocCount, allocations.data(), nullptr); | |
for(size_t i = 0; i < allocCount; ++i) | |
{ | |
Allocation allocDesc = {}; | |
allocDesc.allocationFlags = allocCreateInfo.flags; | |
allocDesc.allocation = allocations[i]; | |
AddAllocation(lineNumber, origPtrs[i], res, "vmaAllocateMemoryPages", std::move(allocDesc)); | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaAllocateMemoryPages.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteAllocateMemoryForBufferOrImage(size_t lineNumber, const CsvSplit& csvSplit, OBJECT_TYPE objType) | |
{ | |
switch(objType) | |
{ | |
case OBJECT_TYPE::BUFFER: | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::AllocateMemoryForBuffer); | |
break; | |
case OBJECT_TYPE::IMAGE: | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::AllocateMemoryForImage); | |
break; | |
default: assert(0); | |
} | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 13, true)) | |
{ | |
VkMemoryRequirements memReq = {}; | |
VmaAllocationCreateInfo allocCreateInfo = {}; | |
bool requiresDedicatedAllocation = false; | |
bool prefersDedicatedAllocation = false; | |
uint64_t origPool = 0; | |
uint64_t origPtr = 0; | |
if(StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX), memReq.size) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 1), memReq.alignment) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 2), memReq.memoryTypeBits) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 3), allocCreateInfo.flags) && | |
StrRangeToBool(csvSplit.GetRange(FIRST_PARAM_INDEX + 4), requiresDedicatedAllocation) && | |
StrRangeToBool(csvSplit.GetRange(FIRST_PARAM_INDEX + 5), prefersDedicatedAllocation) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 6), (uint32_t&)allocCreateInfo.usage) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 7), allocCreateInfo.requiredFlags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 8), allocCreateInfo.preferredFlags) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 9), allocCreateInfo.memoryTypeBits) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 10), origPool) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 11), origPtr)) | |
{ | |
FindPool(lineNumber, origPool, allocCreateInfo.pool); | |
if(csvSplit.GetCount() > FIRST_PARAM_INDEX + 12) | |
{ | |
PrepareUserData( | |
lineNumber, | |
allocCreateInfo.flags, | |
csvSplit.GetRange(FIRST_PARAM_INDEX + 12), | |
csvSplit.GetLine(), | |
allocCreateInfo.pUserData); | |
} | |
UpdateMemStats(); | |
m_Stats.RegisterCreateAllocation(allocCreateInfo); | |
if(requiresDedicatedAllocation || prefersDedicatedAllocation) | |
{ | |
allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; | |
} | |
if(!m_AllocateForBufferImageWarningIssued) | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: vmaAllocateMemoryForBuffer or vmaAllocateMemoryForImage cannot be replayed accurately. Using vmaCreateAllocation instead.\n", lineNumber); | |
} | |
m_AllocateForBufferImageWarningIssued = true; | |
} | |
Allocation allocDesc = {}; | |
allocDesc.allocationFlags = allocCreateInfo.flags; | |
VkResult res = vmaAllocateMemory(m_Allocator, &memReq, &allocCreateInfo, &allocDesc.allocation, nullptr); | |
AddAllocation(lineNumber, origPtr, res, "vmaAllocateMemory (called as vmaAllocateMemoryForBuffer or vmaAllocateMemoryForImage)", std::move(allocDesc)); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaAllocateMemoryForBuffer or vmaAllocateMemoryForImage.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteMapMemory(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::MapMemory); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 1, false)) | |
{ | |
uint64_t origPtr = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr)) | |
{ | |
if(origPtr != 0) | |
{ | |
const auto it = m_Allocations.find(origPtr); | |
if(it != m_Allocations.end()) | |
{ | |
if(it->second.allocation) | |
{ | |
void* pData; | |
VkResult res = vmaMapMemory(m_Allocator, it->second.allocation, &pData); | |
if(res != VK_SUCCESS) | |
{ | |
printf("Line %zu: vmaMapMemory failed (%d)\n", lineNumber, res); | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Cannot call vmaMapMemory - allocation is null.\n", lineNumber); | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Allocation %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaMapMemory.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteUnmapMemory(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::UnmapMemory); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 1, false)) | |
{ | |
uint64_t origPtr = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr)) | |
{ | |
if(origPtr != 0) | |
{ | |
const auto it = m_Allocations.find(origPtr); | |
if(it != m_Allocations.end()) | |
{ | |
if(it->second.allocation) | |
{ | |
vmaUnmapMemory(m_Allocator, it->second.allocation); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Cannot call vmaUnmapMemory - allocation is null.\n", lineNumber); | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Allocation %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaMapMemory.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteFlushAllocation(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::FlushAllocation); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 3, false)) | |
{ | |
uint64_t origPtr = 0; | |
uint64_t offset = 0; | |
uint64_t size = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 1), offset) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 2), size)) | |
{ | |
if(origPtr != 0) | |
{ | |
const auto it = m_Allocations.find(origPtr); | |
if(it != m_Allocations.end()) | |
{ | |
if(it->second.allocation) | |
{ | |
vmaFlushAllocation(m_Allocator, it->second.allocation, offset, size); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Cannot call vmaFlushAllocation - allocation is null.\n", lineNumber); | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Allocation %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaFlushAllocation.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteInvalidateAllocation(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::InvalidateAllocation); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 3, false)) | |
{ | |
uint64_t origPtr = 0; | |
uint64_t offset = 0; | |
uint64_t size = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 1), offset) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 2), size)) | |
{ | |
if(origPtr != 0) | |
{ | |
const auto it = m_Allocations.find(origPtr); | |
if(it != m_Allocations.end()) | |
{ | |
if(it->second.allocation) | |
{ | |
vmaInvalidateAllocation(m_Allocator, it->second.allocation, offset, size); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Cannot call vmaInvalidateAllocation - allocation is null.\n", lineNumber); | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Allocation %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaInvalidateAllocation.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteTouchAllocation(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::TouchAllocation); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 1, false)) | |
{ | |
uint64_t origPtr = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr)) | |
{ | |
const auto it = m_Allocations.find(origPtr); | |
if(it != m_Allocations.end()) | |
{ | |
if(it->second.allocation) | |
{ | |
vmaTouchAllocation(m_Allocator, it->second.allocation); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Cannot call vmaTouchAllocation - allocation is null.\n", lineNumber); | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Allocation %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaTouchAllocation.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteGetAllocationInfo(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::GetAllocationInfo); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 1, false)) | |
{ | |
uint64_t origPtr = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr)) | |
{ | |
const auto it = m_Allocations.find(origPtr); | |
if(it != m_Allocations.end()) | |
{ | |
if(it->second.allocation) | |
{ | |
VmaAllocationInfo allocInfo; | |
vmaGetAllocationInfo(m_Allocator, it->second.allocation, &allocInfo); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Cannot call vmaGetAllocationInfo - allocation is null.\n", lineNumber); | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Allocation %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaGetAllocationInfo.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteMakePoolAllocationsLost(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::MakePoolAllocationsLost); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 1, false)) | |
{ | |
uint64_t origPtr = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr)) | |
{ | |
if(origPtr != 0) | |
{ | |
const auto it = m_Pools.find(origPtr); | |
if(it != m_Pools.end()) | |
{ | |
vmaMakePoolAllocationsLost(m_Allocator, it->second.pool, nullptr); | |
UpdateMemStats(); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Pool %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaMakePoolAllocationsLost.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteResizeAllocation(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::ResizeAllocation); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 2, false)) | |
{ | |
uint64_t origPtr = 0; | |
uint64_t newSize = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 1), newSize)) | |
{ | |
if(origPtr != 0) | |
{ | |
const auto it = m_Allocations.find(origPtr); | |
if(it != m_Allocations.end()) | |
{ | |
vmaResizeAllocation(m_Allocator, it->second.allocation, newSize); | |
UpdateMemStats(); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Allocation %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaResizeAllocation.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteDefragmentationBegin(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::DefragmentationBegin); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 9, false)) | |
{ | |
VmaDefragmentationInfo2 defragInfo = {}; | |
std::vector<uint64_t> allocationOrigPtrs; | |
std::vector<uint64_t> poolOrigPtrs; | |
uint64_t cmdBufOrigPtr = 0; | |
uint64_t defragCtxOrigPtr = 0; | |
if(StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX), defragInfo.flags) && | |
StrRangeToPtrList(csvSplit.GetRange(FIRST_PARAM_INDEX + 1), allocationOrigPtrs) && | |
StrRangeToPtrList(csvSplit.GetRange(FIRST_PARAM_INDEX + 2), poolOrigPtrs) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 3), defragInfo.maxCpuBytesToMove) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 4), defragInfo.maxCpuAllocationsToMove) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 5), defragInfo.maxGpuBytesToMove) && | |
StrRangeToUint(csvSplit.GetRange(FIRST_PARAM_INDEX + 6), defragInfo.maxGpuAllocationsToMove) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 7), cmdBufOrigPtr) && | |
StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX + 8), defragCtxOrigPtr)) | |
{ | |
const size_t allocationOrigPtrCount = allocationOrigPtrs.size(); | |
std::vector<VmaAllocation> allocations; | |
allocations.reserve(allocationOrigPtrCount); | |
for(size_t i = 0; i < allocationOrigPtrCount; ++i) | |
{ | |
const auto it = m_Allocations.find(allocationOrigPtrs[i]); | |
if(it != m_Allocations.end() && it->second.allocation) | |
{ | |
allocations.push_back(it->second.allocation); | |
} | |
} | |
if(!allocations.empty()) | |
{ | |
defragInfo.allocationCount = (uint32_t)allocations.size(); | |
defragInfo.pAllocations = allocations.data(); | |
} | |
const size_t poolOrigPtrCount = poolOrigPtrs.size(); | |
std::vector<VmaPool> pools; | |
pools.reserve(poolOrigPtrCount); | |
for(size_t i = 0; i < poolOrigPtrCount; ++i) | |
{ | |
const auto it = m_Pools.find(poolOrigPtrs[i]); | |
if(it != m_Pools.end() && it->second.pool) | |
{ | |
pools.push_back(it->second.pool); | |
} | |
} | |
if(!pools.empty()) | |
{ | |
defragInfo.poolCount = (uint32_t)pools.size(); | |
defragInfo.pPools = pools.data(); | |
} | |
if(allocations.size() != allocationOrigPtrCount || | |
pools.size() != poolOrigPtrCount) | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Passing %zu allocations and %zu pools to vmaDefragmentationBegin, while originally %zu allocations and %zu pools were passed.\n", | |
lineNumber, | |
allocations.size(), pools.size(), | |
allocationOrigPtrCount, poolOrigPtrCount); | |
} | |
} | |
if(cmdBufOrigPtr) | |
{ | |
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) | |
{ | |
defragInfo.commandBuffer = m_CommandBuffer; | |
} | |
else | |
{ | |
printf("Line %zu: vkBeginCommandBuffer failed (%d)\n", lineNumber, res); | |
} | |
} | |
m_Stats.RegisterDefragmentation(defragInfo); | |
VmaDefragmentationContext defragCtx = nullptr; | |
VkResult res = vmaDefragmentationBegin(m_Allocator, &defragInfo, nullptr, &defragCtx); | |
if(defragInfo.commandBuffer) | |
{ | |
vkEndCommandBuffer(m_CommandBuffer); | |
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); | |
} | |
if(res >= VK_SUCCESS) | |
{ | |
if(defragCtx) | |
{ | |
if(defragCtxOrigPtr) | |
{ | |
// We have defragmentation context, originally had defragmentation context: Store it. | |
m_DefragmentationContexts[defragCtxOrigPtr] = defragCtx; | |
} | |
else | |
{ | |
// We have defragmentation context, originally it was null: End immediately. | |
vmaDefragmentationEnd(m_Allocator, defragCtx); | |
} | |
} | |
else | |
{ | |
if(defragCtxOrigPtr) | |
{ | |
// We have no defragmentation context, originally there was one: Store null. | |
m_DefragmentationContexts[defragCtxOrigPtr] = nullptr; | |
} | |
else | |
{ | |
// We have no defragmentation context, originally there wasn't as well - nothing to do. | |
} | |
} | |
} | |
else | |
{ | |
if(defragCtxOrigPtr) | |
{ | |
// Currently failed, originally succeeded. | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: vmaDefragmentationBegin failed (%d), while originally succeeded.\n", lineNumber, res); | |
} | |
} | |
else | |
{ | |
// Currently failed, originally don't know. | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: vmaDefragmentationBegin failed (%d).\n", lineNumber, res); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaDefragmentationBegin.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteDefragmentationEnd(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::DefragmentationEnd); | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 1, false)) | |
{ | |
uint64_t origPtr = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr)) | |
{ | |
if(origPtr != 0) | |
{ | |
const auto it = m_DefragmentationContexts.find(origPtr); | |
if(it != m_DefragmentationContexts.end()) | |
{ | |
vmaDefragmentationEnd(m_Allocator, it->second); | |
m_DefragmentationContexts.erase(it); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Defragmentation context %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaDefragmentationEnd.\n", lineNumber); | |
} | |
} | |
} | |
} | |
void Player::ExecuteSetPoolName(size_t lineNumber, const CsvSplit& csvSplit) | |
{ | |
m_Stats.RegisterFunctionCall(VMA_FUNCTION::SetPoolName); | |
if(!g_UserDataEnabled) | |
{ | |
return; | |
} | |
if(ValidateFunctionParameterCount(lineNumber, csvSplit, 2, true)) | |
{ | |
uint64_t origPtr = 0; | |
if(StrRangeToPtr(csvSplit.GetRange(FIRST_PARAM_INDEX), origPtr)) | |
{ | |
if(origPtr != 0) | |
{ | |
const auto it = m_Pools.find(origPtr); | |
if(it != m_Pools.end()) | |
{ | |
std::string poolName; | |
csvSplit.GetRange(FIRST_PARAM_INDEX + 1).to_str(poolName); | |
vmaSetPoolName(m_Allocator, it->second.pool, !poolName.empty() ? poolName.c_str() : nullptr); | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Pool %llX not found.\n", lineNumber, origPtr); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if(IssueWarning()) | |
{ | |
printf("Line %zu: Invalid parameters for vmaSetPoolName.\n", lineNumber); | |
} | |
} | |
} | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// Main functions | |
static void PrintCommandLineSyntax() | |
{ | |
printf( | |
"Command line syntax:\n" | |
" VmaReplay [Options] <SrcFile.csv>\n" | |
"Available options:\n" | |
" -v <Number> - Verbosity level:\n" | |
" 0 - Minimum verbosity. Prints only warnings and errors.\n" | |
" 1 - Default verbosity. Prints important messages and statistics.\n" | |
" 2 - Maximum verbosity. Prints a lot of information.\n" | |
" -i <Number> - Repeat playback given number of times (iterations)\n" | |
" Default is 1. Vulkan is reinitialized with every iteration.\n" | |
" --MemStats <Value> - 0 to disable or 1 to enable memory statistics.\n" | |
" Default is 0. Enabling it may negatively impact playback performance.\n" | |
" --DumpStatsAfterLine <Line> - Dump VMA statistics to JSON file after specified source file line finishes execution.\n" | |
" File is written to current directory with name: VmaReplay_Line####.json.\n" | |
" This parameter can be repeated.\n" | |
" --DumpDetailedStatsAfterLine <Line> - Like command above, but includes detailed map.\n" | |
" --DefragmentAfterLine <Line> - Defragment memory after specified source file line and print statistics.\n" | |
" It also prints detailed statistics to files VmaReplay_Line####_Defragment*.json\n" | |
" --DefragmentationFlags <Flags> - Flags to be applied when using DefragmentAfterLine.\n" | |
" --Lines <Ranges> - Replay only limited set of lines from file\n" | |
" Ranges is comma-separated list of ranges, e.g. \"-10,15,18-25,31-\".\n" | |
" --PhysicalDevice <Index> - Choice of Vulkan physical device. Default: 0.\n" | |
" --UserData <Value> - 0 to disable or 1 to enable setting pUserData during playback.\n" | |
" Default is 1. Affects both creation of buffers and images, as well as calls to vmaSetAllocationUserData.\n" | |
" --VK_LAYER_LUNARG_standard_validation <Value> - 0 to disable or 1 to enable validation layers.\n" | |
" By default the layers are silently enabled if available.\n" | |
" --VK_EXT_memory_budget <Value> - 0 to disable or 1 to enable this extension.\n" | |
" By default the extension is silently enabled if available.\n" | |
); | |
} | |
static int ProcessFile(size_t iterationIndex, const char* data, size_t numBytes, duration& outDuration) | |
{ | |
outDuration = duration::max(); | |
const bool useLineRanges = !g_LineRanges.IsEmpty(); | |
const bool useDumpStatsAfterLine = !g_DumpStatsAfterLine.empty(); | |
const bool useDefragmentAfterLine = !g_DefragmentAfterLine.empty(); | |
LineSplit lineSplit(data, numBytes); | |
StrRange line; | |
if(!lineSplit.GetNextLine(line) || | |
!StrRangeEq(line, "Vulkan Memory Allocator,Calls recording")) | |
{ | |
printf("ERROR: Incorrect file format.\n"); | |
return RESULT_ERROR_FORMAT; | |
} | |
if(!lineSplit.GetNextLine(line) || !ParseFileVersion(line) || !ValidateFileVersion()) | |
{ | |
printf("ERROR: Incorrect file format version.\n"); | |
return RESULT_ERROR_FORMAT; | |
} | |
if(g_Verbosity == VERBOSITY::MAXIMUM) | |
{ | |
printf("Format version: %u,%u\n", | |
GetVersionMajor(g_FileVersion), | |
GetVersionMinor(g_FileVersion)); | |
} | |
// Parse configuration | |
const bool configEnabled = g_FileVersion >= MakeVersion(1, 3); | |
ConfigurationParser configParser; | |
if(configEnabled) | |
{ | |
if(!configParser.Parse(lineSplit)) | |
{ | |
return RESULT_ERROR_FORMAT; | |
} | |
} | |
Player player; | |
int result = player.Init(); | |
if(configEnabled) | |
{ | |
player.ApplyConfig(configParser); | |
} | |
size_t executedLineCount = 0; | |
if(result == 0) | |
{ | |
if(g_Verbosity > VERBOSITY::MINIMUM) | |
{ | |
if(useLineRanges) | |
{ | |
printf("Playing #%zu (limited range of lines)...\n", iterationIndex + 1); | |
} | |
else | |
{ | |
printf("Playing #%zu...\n", iterationIndex + 1); | |
} | |
} | |
const time_point timeBeg = std::chrono::high_resolution_clock::now(); | |
while(lineSplit.GetNextLine(line)) | |
{ | |
const size_t currLineNumber = lineSplit.GetNextLineIndex(); | |
bool execute = true; | |
if(useLineRanges) | |
{ | |
execute = g_LineRanges.Includes(currLineNumber); | |
} | |
if(execute) | |
{ | |
player.ExecuteLine(currLineNumber, line); | |
++executedLineCount; | |
} | |
while(useDumpStatsAfterLine && | |
g_DumpStatsAfterLineNextIndex < g_DumpStatsAfterLine.size() && | |
currLineNumber >= g_DumpStatsAfterLine[g_DumpStatsAfterLineNextIndex].line) | |
{ | |
const size_t requestedLine = g_DumpStatsAfterLine[g_DumpStatsAfterLineNextIndex].line; | |
const bool detailed = g_DumpStatsAfterLine[g_DumpStatsAfterLineNextIndex].detailed; | |
if(g_Verbosity == VERBOSITY::MAXIMUM) | |
{ | |
printf("Dumping %sstats after line %zu actual line %zu...\n", | |
detailed ? "detailed " : "", | |
requestedLine, | |
currLineNumber); | |
} | |
player.DumpStats("VmaReplay_Line%04zu.json", requestedLine, detailed); | |
++g_DumpStatsAfterLineNextIndex; | |
} | |
while(useDefragmentAfterLine && | |
g_DefragmentAfterLineNextIndex < g_DefragmentAfterLine.size() && | |
currLineNumber >= g_DefragmentAfterLine[g_DefragmentAfterLineNextIndex]) | |
{ | |
const size_t requestedLine = g_DefragmentAfterLine[g_DefragmentAfterLineNextIndex]; | |
if(g_Verbosity >= VERBOSITY::DEFAULT) | |
{ | |
printf("Defragmenting after line %zu actual line %zu...\n", | |
requestedLine, | |
currLineNumber); | |
} | |
player.DumpStats("VmaReplay_Line%04zu_Defragment_1Before.json", requestedLine, true); | |
player.Defragment(); | |
player.DumpStats("VmaReplay_Line%04zu_Defragment_2After.json", requestedLine, true); | |
++g_DefragmentAfterLineNextIndex; | |
} | |
} | |
const duration playDuration = std::chrono::high_resolution_clock::now() - timeBeg; | |
outDuration = playDuration; | |
// End stats. | |
if(g_Verbosity > VERBOSITY::MINIMUM) | |
{ | |
std::string playDurationStr; | |
SecondsToFriendlyStr(ToFloatSeconds(playDuration), playDurationStr); | |
printf("Done.\n"); | |
printf("Playback took: %s\n", playDurationStr.c_str()); | |
} | |
if(g_Verbosity == VERBOSITY::MAXIMUM) | |
{ | |
printf("File lines: %zu\n", lineSplit.GetNextLineIndex()); | |
printf("Executed %zu file lines\n", executedLineCount); | |
} | |
player.PrintStats(); | |
} | |
return result; | |
} | |
static int ProcessFile() | |
{ | |
if(g_Verbosity > VERBOSITY::MINIMUM) | |
{ | |
printf("Loading file \"%s\"...\n", g_FilePath.c_str()); | |
} | |
int result = 0; | |
FILE* file = nullptr; | |
const errno_t err = fopen_s(&file, g_FilePath.c_str(), "rb"); | |
if(err == 0) | |
{ | |
_fseeki64(file, 0, SEEK_END); | |
const size_t fileSize = (size_t)_ftelli64(file); | |
_fseeki64(file, 0, SEEK_SET); | |
if(fileSize > 0) | |
{ | |
std::vector<char> fileContents(fileSize); | |
fread(fileContents.data(), 1, fileSize, file); | |
// Begin stats. | |
if(g_Verbosity == VERBOSITY::MAXIMUM) | |
{ | |
printf("File size: %zu B\n", fileSize); | |
} | |
duration durationSum = duration::zero(); | |
for(size_t i = 0; i < g_IterationCount; ++i) | |
{ | |
duration currDuration; | |
ProcessFile(i, fileContents.data(), fileContents.size(), currDuration); | |
durationSum += currDuration; | |
} | |
if(g_IterationCount > 1) | |
{ | |
std::string playDurationStr; | |
SecondsToFriendlyStr(ToFloatSeconds(durationSum / g_IterationCount), playDurationStr); | |
printf("Average playback time from %zu iterations: %s\n", g_IterationCount, playDurationStr.c_str()); | |
} | |
} | |
else | |
{ | |
printf("ERROR: Source file is empty.\n"); | |
result = RESULT_ERROR_SOURCE_FILE; | |
} | |
fclose(file); | |
} | |
else | |
{ | |
printf("ERROR: Couldn't open file (%i).\n", err); | |
result = RESULT_ERROR_SOURCE_FILE; | |
} | |
return result; | |
} | |
static int main2(int argc, char** argv) | |
{ | |
CmdLineParser cmdLineParser(argc, argv); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_VERBOSITY, 'v', true); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_ITERATIONS, 'i', true); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_LINES, "Lines", true); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_PHYSICAL_DEVICE, "PhysicalDevice", true); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_USER_DATA, "UserData", true); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_VK_EXT_MEMORY_BUDGET, "VK_EXT_memory_budget", true); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_VK_LAYER_LUNARG_STANDARD_VALIDATION, VALIDATION_LAYER_NAME, true); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_MEM_STATS, "MemStats", true); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_DUMP_STATS_AFTER_LINE, "DumpStatsAfterLine", true); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_DEFRAGMENT_AFTER_LINE, "DefragmentAfterLine", true); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_DEFRAGMENTATION_FLAGS, "DefragmentationFlags", true); | |
cmdLineParser.RegisterOpt(CMD_LINE_OPT_DUMP_DETAILED_STATS_AFTER_LINE, "DumpDetailedStatsAfterLine", true); | |
CmdLineParser::RESULT res; | |
while((res = cmdLineParser.ReadNext()) != CmdLineParser::RESULT_END) | |
{ | |
switch(res) | |
{ | |
case CmdLineParser::RESULT_OPT: | |
switch(cmdLineParser.GetOptId()) | |
{ | |
case CMD_LINE_OPT_VERBOSITY: | |
{ | |
uint32_t verbosityVal = UINT32_MAX; | |
if(StrRangeToUint(StrRange(cmdLineParser.GetParameter()), verbosityVal) && | |
verbosityVal < (uint32_t)VERBOSITY::COUNT) | |
{ | |
g_Verbosity = (VERBOSITY)verbosityVal; | |
} | |
else | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
} | |
break; | |
case CMD_LINE_OPT_ITERATIONS: | |
if(!StrRangeToUint(StrRange(cmdLineParser.GetParameter()), g_IterationCount)) | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
break; | |
case CMD_LINE_OPT_LINES: | |
if(!g_LineRanges.Parse(StrRange(cmdLineParser.GetParameter()))) | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
break; | |
case CMD_LINE_OPT_PHYSICAL_DEVICE: | |
if(!StrRangeToUint(StrRange(cmdLineParser.GetParameter()), g_PhysicalDeviceIndex)) | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
break; | |
case CMD_LINE_OPT_USER_DATA: | |
if(!StrRangeToBool(StrRange(cmdLineParser.GetParameter()), g_UserDataEnabled)) | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
break; | |
case CMD_LINE_OPT_VK_EXT_MEMORY_BUDGET: | |
{ | |
bool newValue; | |
if(StrRangeToBool(StrRange(cmdLineParser.GetParameter()), newValue)) | |
{ | |
g_VK_EXT_memory_budget_request = newValue ? | |
VULKAN_EXTENSION_REQUEST::ENABLED : | |
VULKAN_EXTENSION_REQUEST::DISABLED; | |
} | |
else | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
} | |
break; | |
case CMD_LINE_OPT_VK_LAYER_LUNARG_STANDARD_VALIDATION: | |
{ | |
bool newValue; | |
if(StrRangeToBool(StrRange(cmdLineParser.GetParameter()), newValue)) | |
{ | |
g_VK_LAYER_LUNARG_standard_validation = newValue ? | |
VULKAN_EXTENSION_REQUEST::ENABLED : | |
VULKAN_EXTENSION_REQUEST::DISABLED; | |
} | |
else | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
} | |
break; | |
case CMD_LINE_OPT_MEM_STATS: | |
if(!StrRangeToBool(StrRange(cmdLineParser.GetParameter()), g_MemStatsEnabled)) | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
break; | |
case CMD_LINE_OPT_DUMP_STATS_AFTER_LINE: | |
case CMD_LINE_OPT_DUMP_DETAILED_STATS_AFTER_LINE: | |
{ | |
size_t line; | |
if(StrRangeToUint(StrRange(cmdLineParser.GetParameter()), line)) | |
{ | |
const bool detailed = | |
cmdLineParser.GetOptId() == CMD_LINE_OPT_DUMP_DETAILED_STATS_AFTER_LINE; | |
g_DumpStatsAfterLine.push_back({line, detailed}); | |
} | |
else | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
} | |
break; | |
case CMD_LINE_OPT_DEFRAGMENT_AFTER_LINE: | |
{ | |
size_t line; | |
if(StrRangeToUint(StrRange(cmdLineParser.GetParameter()), line)) | |
{ | |
g_DefragmentAfterLine.push_back(line); | |
} | |
else | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
} | |
break; | |
case CMD_LINE_OPT_DEFRAGMENTATION_FLAGS: | |
{ | |
if(!StrRangeToUint(StrRange(cmdLineParser.GetParameter()), g_DefragmentationFlags)) | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
} | |
break; | |
default: | |
assert(0); | |
} | |
break; | |
case CmdLineParser::RESULT_PARAMETER: | |
if(g_FilePath.empty()) | |
{ | |
g_FilePath = cmdLineParser.GetParameter(); | |
} | |
else | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
break; | |
case CmdLineParser::RESULT_ERROR: | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
break; | |
default: | |
assert(0); | |
} | |
} | |
// Postprocess command line parameters. | |
if(g_FilePath.empty()) | |
{ | |
PrintCommandLineSyntax(); | |
return RESULT_ERROR_COMMAND_LINE; | |
} | |
// Sort g_DumpStatsAfterLine and make unique. | |
std::sort(g_DumpStatsAfterLine.begin(), g_DumpStatsAfterLine.end()); | |
g_DumpStatsAfterLine.erase( | |
std::unique(g_DumpStatsAfterLine.begin(), g_DumpStatsAfterLine.end()), | |
g_DumpStatsAfterLine.end()); | |
// Sort g_DefragmentAfterLine and make unique. | |
std::sort(g_DefragmentAfterLine.begin(), g_DefragmentAfterLine.end()); | |
g_DefragmentAfterLine.erase( | |
std::unique(g_DefragmentAfterLine.begin(), g_DefragmentAfterLine.end()), | |
g_DefragmentAfterLine.end()); | |
return ProcessFile(); | |
} | |
int main(int argc, char** argv) | |
{ | |
try | |
{ | |
return main2(argc, argv); | |
} | |
catch(const std::exception& e) | |
{ | |
printf("ERROR: %s\n", e.what()); | |
return RESULT_EXCEPTION; | |
} | |
catch(...) | |
{ | |
printf("UNKNOWN ERROR\n"); | |
return RESULT_EXCEPTION; | |
} | |
} |