Standardized line endings to LF See also #195
diff --git a/.gitignore b/.gitignore index 98d3156..f56901a 100644 --- a/.gitignore +++ b/.gitignore
@@ -1,13 +1,13 @@ -/bin/* - -/build/* -!/build/src/ -/build/src/* -!/build/src/Release/ -/build/src/Release/* -!/build/src/Release/VmaSample.exe -!/build/src/VmaReplay/ -/build/src/VmaReplay/* -!/build/src/VmaReplay/Release/ -/build/src/VmaReplay/Release/* -!/build/src/VmaReplay/Release/VmaReplay.exe +/bin/* + +/build/* +!/build/src/ +/build/src/* +!/build/src/Release/ +/build/src/Release/* +!/build/src/Release/VmaSample.exe +!/build/src/VmaReplay/ +/build/src/VmaReplay/* +!/build/src/VmaReplay/Release/ +/build/src/VmaReplay/Release/* +!/build/src/VmaReplay/Release/VmaReplay.exe
diff --git a/.travis.yml b/.travis.yml index cc88e33..86d758f 100644 --- a/.travis.yml +++ b/.travis.yml
@@ -1,37 +1,37 @@ -language: cpp -sudo: required -os: linux -dist: bionic - -branches: - only: - - master - -compiler: - - clang - - gcc - -before_script: - - sudo apt-get install - - eval "${MATRIX_EVAL}" - -install: - - sudo apt-get -qq update - - sudo apt-get install -y libassimp-dev libglm-dev graphviz libxcb-dri3-0 libxcb-present0 libpciaccess0 cmake libpng-dev libxcb-dri3-dev libx11-dev libx11-xcb-dev libmirclient-dev libwayland-dev libxrandr-dev - - export VK_VERSION=1.2.131.1 - - wget -O vulkansdk-linux-x86_64-$VK_VERSION.tar.gz https://sdk.lunarg.com/sdk/download/$VK_VERSION/linux/vulkansdk-linux-x86_64-$VK_VERSION.tar.gz?Human=true - - tar zxf vulkansdk-linux-x86_64-$VK_VERSION.tar.gz - - export VULKAN_SDK=$TRAVIS_BUILD_DIR/$VK_VERSION/x86_64 - -script: - - mkdir -p build - - cd build - - cmake .. - - make - -notifications: - email: - recipients: - - adam.sawicki@amd.com - on_success: change - on_failure: always +language: cpp +sudo: required +os: linux +dist: bionic + +branches: + only: + - master + +compiler: + - clang + - gcc + +before_script: + - sudo apt-get install + - eval "${MATRIX_EVAL}" + +install: + - sudo apt-get -qq update + - sudo apt-get install -y libassimp-dev libglm-dev graphviz libxcb-dri3-0 libxcb-present0 libpciaccess0 cmake libpng-dev libxcb-dri3-dev libx11-dev libx11-xcb-dev libmirclient-dev libwayland-dev libxrandr-dev + - export VK_VERSION=1.2.131.1 + - wget -O vulkansdk-linux-x86_64-$VK_VERSION.tar.gz https://sdk.lunarg.com/sdk/download/$VK_VERSION/linux/vulkansdk-linux-x86_64-$VK_VERSION.tar.gz?Human=true + - tar zxf vulkansdk-linux-x86_64-$VK_VERSION.tar.gz + - export VULKAN_SDK=$TRAVIS_BUILD_DIR/$VK_VERSION/x86_64 + +script: + - mkdir -p build + - cd build + - cmake .. + - make + +notifications: + email: + recipients: + - adam.sawicki@amd.com + on_success: change + on_failure: always
diff --git a/CHANGELOG.md b/CHANGELOG.md index 87ad08d..218a98b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -1,123 +1,123 @@ -# 2.3.0 (2019-12-04) - -Major release after a year of development in "master" branch and feature branches. Notable new features: supporting Vulkan 1.1, supporting query for memory budget. - -Major changes: - -- Added support for Vulkan 1.1. - - Added member `VmaAllocatorCreateInfo::vulkanApiVersion`. - - When Vulkan 1.1 is used, there is no need to enable VK_KHR_dedicated_allocation or VK_KHR_bind_memory2 extensions, as they are promoted to Vulkan itself. -- Added support for query for memory budget and staying within the budget. - - Added function `vmaGetBudget`, structure `VmaBudget`. This can also serve as simple statistics, more efficient than `vmaCalculateStats`. - - By default the budget it is estimated based on memory heap sizes. It may be queried from the system using VK_EXT_memory_budget extension if you use `VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT` flag and `VmaAllocatorCreateInfo::instance` member. - - Added flag `VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT` that fails an allocation if it would exceed the budget. -- Added new memory usage options: - - `VMA_MEMORY_USAGE_CPU_COPY` for memory that is preferably not `DEVICE_LOCAL` but not guaranteed to be `HOST_VISIBLE`. - - `VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED` for memory that is `LAZILY_ALLOCATED`. -- Added support for VK_KHR_bind_memory2 extension: - - Added `VMA_ALLOCATION_CREATE_DONT_BIND_BIT` flag that lets you create both buffer/image and allocation, but don't bind them together. - - Added flag `VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT`, functions `vmaBindBufferMemory2`, `vmaBindImageMemory2` that let you specify additional local offset and `pNext` pointer while binding. -- Added functions `vmaSetPoolName`, `vmaGetPoolName` that let you assign string names to custom pools. JSON dump file format and VmaDumpVis tool is updated to show these names. -- Defragmentation is legal only on buffers and images in `VK_IMAGE_TILING_LINEAR`. This is due to the way it is currently implemented in the library and the restrictions of the Vulkan specification. Clarified documentation in this regard. See discussion in #59. - -Minor changes: - -- Made `vmaResizeAllocation` function deprecated, always returning failure. -- Made changes in the internal algorithm for the choice of memory type. Be careful! You may now get a type that is not `HOST_VISIBLE` or `HOST_COHERENT` if it's not stated as always ensured by some `VMA_MEMORY_USAGE_*` flag. -- Extended VmaReplay application with more detailed statistics printed at the end. -- Added macros `VMA_CALL_PRE`, `VMA_CALL_POST` that let you decorate declarations of all library functions if you want to e.g. export/import them as dynamically linked library. -- Optimized `VmaAllocation` objects to be allocated out of an internal free-list allocator. This makes allocation and deallocation causing 0 dynamic CPU heap allocations on average. -- Updated recording CSV file format version to 1.8, to support new functions. -- Many additions and fixes in documentation. Many compatibility fixes for various compilers and platforms. Other internal bugfixes, optimizations, updates, refactoring... - -# 2.2.0 (2018-12-13) - -Major release after many months of development in "master" branch and feature branches. Notable new features: defragmentation of GPU memory, buddy algorithm, convenience functions for sparse binding. - -Major changes: - -- New, more powerful defragmentation: - - Added structure `VmaDefragmentationInfo2`, functions `vmaDefragmentationBegin`, `vmaDefragmentationEnd`. - - Added support for defragmentation of GPU memory. - - Defragmentation of CPU memory now uses `memmove`, so it can move data to overlapping regions. - - Defragmentation of CPU memory is now available for memory types that are `HOST_VISIBLE` but not `HOST_COHERENT`. - - Added structure member `VmaVulkanFunctions::vkCmdCopyBuffer`. - - Major internal changes in defragmentation algorithm. - - VmaReplay: added parameters: `--DefragmentAfterLine`, `--DefragmentationFlags`. - - Old interface (structure `VmaDefragmentationInfo`, function `vmaDefragment`) is now deprecated. -- Added buddy algorithm, available for custom pools - flag `VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT`. -- Added convenience functions for multiple allocations and deallocations at once, intended for sparse binding resources - functions `vmaAllocateMemoryPages`, `vmaFreeMemoryPages`. -- Added function that tries to resize existing allocation in place: `vmaResizeAllocation`. -- Added flags for allocation strategy: `VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT`, `VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT`, `VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT`, and their aliases: `VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT`, `VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT`, `VMA_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT`. - -Minor changes: - -- Changed behavior of allocation functions to return `VK_ERROR_VALIDATION_FAILED_EXT` when trying to allocate memory of size 0, create buffer with size 0, or image with one of the dimensions 0. -- VmaReplay: Added support for Windows end of lines. -- Updated recording CSV file format version to 1.5, to support new functions. -- Internal optimization: using read-write mutex on some platforms. -- Many additions and fixes in documentation. Many compatibility fixes for various compilers. Other internal bugfixes, optimizations, refactoring, added more internal validation... - -# 2.1.0 (2018-09-10) - -Minor bugfixes. - -# 2.1.0-beta.1 (2018-08-27) - -Major release after many months of development in "development" branch and features branches. Many new features added, some bugs fixed. API stays backward-compatible. - -Major changes: - -- Added linear allocation algorithm, accessible for custom pools, that can be used as free-at-once, stack, double stack, or ring buffer. See "Linear allocation algorithm" documentation chapter. - - Added `VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT`, `VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT`. -- Added feature to record sequence of calls to the library to a file and replay it using dedicated application. See documentation chapter "Record and replay". - - Recording: added `VmaAllocatorCreateInfo::pRecordSettings`. - - Replaying: added VmaReplay project. - - Recording file format: added document "docs/Recording file format.md". -- Improved support for non-coherent memory. - - Added functions: `vmaFlushAllocation`, `vmaInvalidateAllocation`. - - `nonCoherentAtomSize` is now respected automatically. - - Added `VmaVulkanFunctions::vkFlushMappedMemoryRanges`, `vkInvalidateMappedMemoryRanges`. -- Improved debug features related to detecting incorrect mapped memory usage. See documentation chapter "Debugging incorrect memory usage". - - Added debug macro `VMA_DEBUG_DETECT_CORRUPTION`, functions `vmaCheckCorruption`, `vmaCheckPoolCorruption`. - - Added debug macro `VMA_DEBUG_INITIALIZE_ALLOCATIONS` to initialize contents of allocations with a bit pattern. - - Changed behavior of `VMA_DEBUG_MARGIN` macro - it now adds margin also before first and after last allocation in a block. -- Changed format of JSON dump returned by `vmaBuildStatsString` (not backward compatible!). - - Custom pools and memory blocks now have IDs that don't change after sorting. - - Added properties: "CreationFrameIndex", "LastUseFrameIndex", "Usage". - - Changed VmaDumpVis tool to use these new properties for better coloring. - - Changed behavior of `vmaGetAllocationInfo` and `vmaTouchAllocation` to update `allocation.lastUseFrameIndex` even if allocation cannot become lost. - -Minor changes: - -- Changes in custom pools: - - Added new structure member `VmaPoolStats::blockCount`. - - Changed behavior of `VmaPoolCreateInfo::blockSize` = 0 (default) - it now means that pool may use variable block sizes, just like default pools do. -- Improved logic of `vmaFindMemoryTypeIndex` for some cases, especially integrated GPUs. -- VulkanSample application: Removed dependency on external library MathFu. Added own vector and matrix structures. -- Changes that improve compatibility with various platforms, including: Visual Studio 2012, 32-bit code, C compilers. - - Changed usage of "VK_KHR_dedicated_allocation" extension in the code to be optional, driven by macro `VMA_DEDICATED_ALLOCATION`, for compatibility with Android. -- Many additions and fixes in documentation, including description of new features, as well as "Validation layer warnings". -- Other bugfixes. - -# 2.0.0 (2018-03-19) - -A major release with many compatibility-breaking changes. - -Notable new features: - -- Introduction of `VmaAllocation` handle that you must retrieve from allocation functions and pass to deallocation functions next to normal `VkBuffer` and `VkImage`. -- Introduction of `VmaAllocationInfo` structure that you can retrieve from `VmaAllocation` handle to access parameters of the allocation (like `VkDeviceMemory` and offset) instead of retrieving them directly from allocation functions. -- Support for reference-counted mapping and persistently mapped allocations - see `vmaMapMemory`, `VMA_ALLOCATION_CREATE_MAPPED_BIT`. -- Support for custom memory pools - see `VmaPool` handle, `VmaPoolCreateInfo` structure, `vmaCreatePool` function. -- Support for defragmentation (compaction) of allocations - see function `vmaDefragment` and related structures. -- Support for "lost allocations" - see appropriate chapter on documentation Main Page. - -# 1.0.1 (2017-07-04) - -- Fixes for Linux GCC compilation. -- Changed "CONFIGURATION SECTION" to contain #ifndef so you can define these macros before including this header, not necessarily change them in the file. - -# 1.0.0 (2017-06-16) - -First public release. +# 2.3.0 (2019-12-04) + +Major release after a year of development in "master" branch and feature branches. Notable new features: supporting Vulkan 1.1, supporting query for memory budget. + +Major changes: + +- Added support for Vulkan 1.1. + - Added member `VmaAllocatorCreateInfo::vulkanApiVersion`. + - When Vulkan 1.1 is used, there is no need to enable VK_KHR_dedicated_allocation or VK_KHR_bind_memory2 extensions, as they are promoted to Vulkan itself. +- Added support for query for memory budget and staying within the budget. + - Added function `vmaGetBudget`, structure `VmaBudget`. This can also serve as simple statistics, more efficient than `vmaCalculateStats`. + - By default the budget it is estimated based on memory heap sizes. It may be queried from the system using VK_EXT_memory_budget extension if you use `VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT` flag and `VmaAllocatorCreateInfo::instance` member. + - Added flag `VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT` that fails an allocation if it would exceed the budget. +- Added new memory usage options: + - `VMA_MEMORY_USAGE_CPU_COPY` for memory that is preferably not `DEVICE_LOCAL` but not guaranteed to be `HOST_VISIBLE`. + - `VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED` for memory that is `LAZILY_ALLOCATED`. +- Added support for VK_KHR_bind_memory2 extension: + - Added `VMA_ALLOCATION_CREATE_DONT_BIND_BIT` flag that lets you create both buffer/image and allocation, but don't bind them together. + - Added flag `VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT`, functions `vmaBindBufferMemory2`, `vmaBindImageMemory2` that let you specify additional local offset and `pNext` pointer while binding. +- Added functions `vmaSetPoolName`, `vmaGetPoolName` that let you assign string names to custom pools. JSON dump file format and VmaDumpVis tool is updated to show these names. +- Defragmentation is legal only on buffers and images in `VK_IMAGE_TILING_LINEAR`. This is due to the way it is currently implemented in the library and the restrictions of the Vulkan specification. Clarified documentation in this regard. See discussion in #59. + +Minor changes: + +- Made `vmaResizeAllocation` function deprecated, always returning failure. +- Made changes in the internal algorithm for the choice of memory type. Be careful! You may now get a type that is not `HOST_VISIBLE` or `HOST_COHERENT` if it's not stated as always ensured by some `VMA_MEMORY_USAGE_*` flag. +- Extended VmaReplay application with more detailed statistics printed at the end. +- Added macros `VMA_CALL_PRE`, `VMA_CALL_POST` that let you decorate declarations of all library functions if you want to e.g. export/import them as dynamically linked library. +- Optimized `VmaAllocation` objects to be allocated out of an internal free-list allocator. This makes allocation and deallocation causing 0 dynamic CPU heap allocations on average. +- Updated recording CSV file format version to 1.8, to support new functions. +- Many additions and fixes in documentation. Many compatibility fixes for various compilers and platforms. Other internal bugfixes, optimizations, updates, refactoring... + +# 2.2.0 (2018-12-13) + +Major release after many months of development in "master" branch and feature branches. Notable new features: defragmentation of GPU memory, buddy algorithm, convenience functions for sparse binding. + +Major changes: + +- New, more powerful defragmentation: + - Added structure `VmaDefragmentationInfo2`, functions `vmaDefragmentationBegin`, `vmaDefragmentationEnd`. + - Added support for defragmentation of GPU memory. + - Defragmentation of CPU memory now uses `memmove`, so it can move data to overlapping regions. + - Defragmentation of CPU memory is now available for memory types that are `HOST_VISIBLE` but not `HOST_COHERENT`. + - Added structure member `VmaVulkanFunctions::vkCmdCopyBuffer`. + - Major internal changes in defragmentation algorithm. + - VmaReplay: added parameters: `--DefragmentAfterLine`, `--DefragmentationFlags`. + - Old interface (structure `VmaDefragmentationInfo`, function `vmaDefragment`) is now deprecated. +- Added buddy algorithm, available for custom pools - flag `VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT`. +- Added convenience functions for multiple allocations and deallocations at once, intended for sparse binding resources - functions `vmaAllocateMemoryPages`, `vmaFreeMemoryPages`. +- Added function that tries to resize existing allocation in place: `vmaResizeAllocation`. +- Added flags for allocation strategy: `VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT`, `VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT`, `VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT`, and their aliases: `VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT`, `VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT`, `VMA_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT`. + +Minor changes: + +- Changed behavior of allocation functions to return `VK_ERROR_VALIDATION_FAILED_EXT` when trying to allocate memory of size 0, create buffer with size 0, or image with one of the dimensions 0. +- VmaReplay: Added support for Windows end of lines. +- Updated recording CSV file format version to 1.5, to support new functions. +- Internal optimization: using read-write mutex on some platforms. +- Many additions and fixes in documentation. Many compatibility fixes for various compilers. Other internal bugfixes, optimizations, refactoring, added more internal validation... + +# 2.1.0 (2018-09-10) + +Minor bugfixes. + +# 2.1.0-beta.1 (2018-08-27) + +Major release after many months of development in "development" branch and features branches. Many new features added, some bugs fixed. API stays backward-compatible. + +Major changes: + +- Added linear allocation algorithm, accessible for custom pools, that can be used as free-at-once, stack, double stack, or ring buffer. See "Linear allocation algorithm" documentation chapter. + - Added `VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT`, `VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT`. +- Added feature to record sequence of calls to the library to a file and replay it using dedicated application. See documentation chapter "Record and replay". + - Recording: added `VmaAllocatorCreateInfo::pRecordSettings`. + - Replaying: added VmaReplay project. + - Recording file format: added document "docs/Recording file format.md". +- Improved support for non-coherent memory. + - Added functions: `vmaFlushAllocation`, `vmaInvalidateAllocation`. + - `nonCoherentAtomSize` is now respected automatically. + - Added `VmaVulkanFunctions::vkFlushMappedMemoryRanges`, `vkInvalidateMappedMemoryRanges`. +- Improved debug features related to detecting incorrect mapped memory usage. See documentation chapter "Debugging incorrect memory usage". + - Added debug macro `VMA_DEBUG_DETECT_CORRUPTION`, functions `vmaCheckCorruption`, `vmaCheckPoolCorruption`. + - Added debug macro `VMA_DEBUG_INITIALIZE_ALLOCATIONS` to initialize contents of allocations with a bit pattern. + - Changed behavior of `VMA_DEBUG_MARGIN` macro - it now adds margin also before first and after last allocation in a block. +- Changed format of JSON dump returned by `vmaBuildStatsString` (not backward compatible!). + - Custom pools and memory blocks now have IDs that don't change after sorting. + - Added properties: "CreationFrameIndex", "LastUseFrameIndex", "Usage". + - Changed VmaDumpVis tool to use these new properties for better coloring. + - Changed behavior of `vmaGetAllocationInfo` and `vmaTouchAllocation` to update `allocation.lastUseFrameIndex` even if allocation cannot become lost. + +Minor changes: + +- Changes in custom pools: + - Added new structure member `VmaPoolStats::blockCount`. + - Changed behavior of `VmaPoolCreateInfo::blockSize` = 0 (default) - it now means that pool may use variable block sizes, just like default pools do. +- Improved logic of `vmaFindMemoryTypeIndex` for some cases, especially integrated GPUs. +- VulkanSample application: Removed dependency on external library MathFu. Added own vector and matrix structures. +- Changes that improve compatibility with various platforms, including: Visual Studio 2012, 32-bit code, C compilers. + - Changed usage of "VK_KHR_dedicated_allocation" extension in the code to be optional, driven by macro `VMA_DEDICATED_ALLOCATION`, for compatibility with Android. +- Many additions and fixes in documentation, including description of new features, as well as "Validation layer warnings". +- Other bugfixes. + +# 2.0.0 (2018-03-19) + +A major release with many compatibility-breaking changes. + +Notable new features: + +- Introduction of `VmaAllocation` handle that you must retrieve from allocation functions and pass to deallocation functions next to normal `VkBuffer` and `VkImage`. +- Introduction of `VmaAllocationInfo` structure that you can retrieve from `VmaAllocation` handle to access parameters of the allocation (like `VkDeviceMemory` and offset) instead of retrieving them directly from allocation functions. +- Support for reference-counted mapping and persistently mapped allocations - see `vmaMapMemory`, `VMA_ALLOCATION_CREATE_MAPPED_BIT`. +- Support for custom memory pools - see `VmaPool` handle, `VmaPoolCreateInfo` structure, `vmaCreatePool` function. +- Support for defragmentation (compaction) of allocations - see function `vmaDefragment` and related structures. +- Support for "lost allocations" - see appropriate chapter on documentation Main Page. + +# 1.0.1 (2017-07-04) + +- Fixes for Linux GCC compilation. +- Changed "CONFIGURATION SECTION" to contain #ifndef so you can define these macros before including this header, not necessarily change them in the file. + +# 1.0.0 (2017-06-16) + +First public release.
diff --git a/Doxyfile b/Doxyfile index 5750c8d..9cbc624 100644 --- a/Doxyfile +++ b/Doxyfile
@@ -1,2662 +1,2662 @@ -# Doxyfile 1.9.1 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the configuration -# file that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# https://www.gnu.org/software/libiconv/ for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = "Vulkan Memory Allocator" - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -PROJECT_LOGO = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = docs - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = NO - -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line -# such as -# /*************** -# as being the beginning of a Javadoc-style comment "banner". If set to NO, the -# Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. -# The default value is: NO. - -JAVADOC_BANNER = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# By default Python docstrings are displayed as preformatted text and doxygen's -# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the -# doxygen's special commands can be used and the contents of the docstring -# documentation blocks is shown as doxygen documentation. -# The default value is: YES. - -PYTHON_DOCSTRING = YES - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice -# sources only. Doxygen will then generate output that is more tailored for that -# language. For instance, namespaces will be presented as modules, types will be -# separated into more groups, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_SLICE = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: -# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser -# tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files). For instance to make doxygen treat .inc files -# as Fortran files (default is PHP), and .f files as C (default is Fortran), -# use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. When specifying no_extension you should add -# * to the FILE_PATTERNS. -# -# Note see also the list of default file extension mappings. - -EXTENSION_MAPPING = - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up -# to that level are automatically included in the table of contents, even if -# they do not have an id attribute. -# Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 5. -# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. - -TOC_INCLUDE_HEADINGS = 0 - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = NO - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use -# during processing. When set to 0 doxygen will based this on the number of -# cores available in the system. You can set it explicitly to a value larger -# than 0 to get more control over the balance between CPU load and processing -# speed. At this moment only the input processing can be done using multiple -# threads. Since this is still an experimental feature the default is set to 1, -# which efficively disables parallel processing. Please report any issues you -# encounter. Generating dot graphs in parallel is controlled by the -# DOT_NUM_THREADS setting. -# Minimum value: 0, maximum value: 32, default value: 1. - -NUM_PROC_THREADS = 1 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual -# methods of a class will be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIV_VIRTUAL = NO - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If this flag is set to YES, the name of an unnamed parameter in a declaration -# will be determined by the corresponding definition. By default unnamed -# parameters remain unnamed in the output. -# The default value is: YES. - -RESOLVE_UNNAMED_PARAMS = YES - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# declarations. If set to NO, these declarations will be included in the -# documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# With the correct setting of option CASE_SENSE_NAMES doxygen will better be -# able to match the capabilities of the underlying filesystem. In case the -# filesystem is case sensitive (i.e. it supports files in the same directory -# whose names only differ in casing), the option must be set to YES to properly -# deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with -# output files written for symbols that only differ in casing, such as for two -# classes, one named CLASS and the other named Class, and to also support -# references to files without having to specify the exact matching casing. On -# Windows (including Cygwin) and MacOS, users should typically set this option -# to NO, whereas on Linux or other Unix flavors it should typically be set to -# YES. -# The default value is: system dependent. - -CASE_SENSE_NAMES = NO - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if <section_label> ... \endif and \cond <section_label> -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS -# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but -# at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. -# The default value is: NO. - -WARN_AS_ERROR = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -INPUT = include/vk_mem_alloc.h - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: -# https://www.gnu.org/software/libiconv/) for the list of possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# Note the list of default checked file patterns might differ from the list of -# default file extension mappings. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, -# *.ucf, *.qsf and *.ice. - -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ - *.md \ - *.mm \ - *.dox \ - *.py \ - *.pyw \ - *.f90 \ - *.f95 \ - *.f03 \ - *.f08 \ - *.f \ - *.for \ - *.tcl \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = NO - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = * - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# <filter> <input-file> -# -# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# entity all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see https://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = NO - -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: -# http://clang.llvm.org/) for more accurate parsing at the cost of reduced -# performance. This can be particularly helpful with template rich C++ code for -# which doxygen's built-in parser lacks the necessary type information. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. -# The default value is: NO. - -CLANG_ASSISTED_PARSING = NO - -# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to -# YES then doxygen will add the directory of each input to the include path. -# The default value is: YES. - -CLANG_ADD_INC_PATHS = YES - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_OPTIONS = - -# If clang assisted parsing is enabled you can provide the clang parser with the -# path to the directory containing a file called compile_commands.json. This -# file is the compilation database (see: -# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the -# options used when the source files were built. This is equivalent to -# specifying the -p option to a clang tool, such as clang-check. These options -# will then be passed to the parser. Any options specified with CLANG_OPTIONS -# will be added as well. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. - -CLANG_DATABASE_PATH = - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# https://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - -# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML -# documentation will contain a main index with vertical navigation menus that -# are dynamically created via JavaScript. If disabled, the navigation index will -# consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have JavaScript, -# like the Qt help browser. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_MENUS = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: -# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To -# create a documentation set, doxygen will generate a Makefile in the HTML -# output directory. Running make will produce the docset in that directory and -# running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy -# genXcode/_index.html for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: -# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the main .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location (absolute path -# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to -# run qhelpgenerator on the generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg -# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see -# https://inkscape.org) to generate formulas as SVG images instead of PNGs for -# the HTML output. These images will generally look nicer at scaled resolutions. -# Possible values are: png (the default) and svg (looks nicer but requires the -# pdf2svg or inkscape tool). -# The default value is: png. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FORMULA_FORMAT = png - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands -# to create new LaTeX commands to be used in formulas as building blocks. See -# the section "Including formulas" for details. - -FORMULA_MACROFILE = - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side JavaScript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use <access key> + S -# (what the <access key> is depends on the OS and browser, but it is typically -# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down -# key> to jump into the search results window, the results can be navigated -# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel -# the search. The filter options can be selected when the cursor is inside the -# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> -# to select a filter and <Enter> or <escape> to activate or cancel the filter -# option. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -SEARCHENGINE = YES - -# When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using JavaScript. There -# are two flavors of web server based searching depending on the EXTERNAL_SEARCH -# setting. When disabled, doxygen will generate a PHP script for searching and -# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing -# and searching needs to be provided by external tools. See the section -# "External Indexing and Searching" for details. -# The default value is: NO. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SERVER_BASED_SEARCH = NO - -# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP -# script for searching. Instead the search results are written to an XML file -# which needs to be processed by an external indexer. Doxygen will invoke an -# external search engine pointed to by the SEARCHENGINE_URL option to obtain the -# search results. -# -# Doxygen ships with an example indexer (doxyindexer) and search engine -# (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: -# https://xapian.org/). -# -# See the section "External Indexing and Searching" for details. -# The default value is: NO. -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTERNAL_SEARCH = NO - -# The SEARCHENGINE_URL should point to a search engine hosted by a web server -# which will return the search results when EXTERNAL_SEARCH is enabled. -# -# Doxygen ships with an example indexer (doxyindexer) and search engine -# (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: -# https://xapian.org/). See the section "External Indexing and Searching" for -# details. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SEARCHENGINE_URL = - -# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed -# search data is written to a file for indexing by an external tool. With the -# SEARCHDATA_FILE tag the name of this file can be specified. -# The default file is: searchdata.xml. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SEARCHDATA_FILE = searchdata.xml - -# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the -# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is -# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple -# projects and redirect the results back to the right project. -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTERNAL_SEARCH_ID = - -# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen -# projects other than the one defined by this configuration file, but that are -# all added to the same external search index. Each project needs to have a -# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of -# to a relative location where the documentation can be found. The format is: -# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTRA_SEARCH_MAPPINGS = - -#--------------------------------------------------------------------------- -# Configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. -# The default value is: YES. - -GENERATE_LATEX = NO - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: latex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. -# -# Note that when not enabling USE_PDFLATEX the default is latex when enabling -# USE_PDFLATEX the default is pdflatex and when in the later case latex is -# chosen this is overwritten by pdflatex. For specific output languages the -# default can have been set differently, this depends on the implementation of -# the output language. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate -# index for LaTeX. -# Note: This tag is used in the Makefile / make.bat. -# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file -# (.tex). -# The default file is: makeindex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -MAKEINDEX_CMD_NAME = makeindex - -# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to -# generate index for LaTeX. In case there is no backslash (\) as first character -# it will be automatically added in the LaTeX code. -# Note: This tag is used in the generated output file (.tex). -# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. -# The default value is: makeindex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_MAKEINDEX_CMD = makeindex - -# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX -# documents. This may be useful for small projects and may help to save some -# trees in general. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used by the -# printer. -# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x -# 14 inches) and executive (7.25 x 10.5 inches). -# The default value is: a4. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -PAPER_TYPE = a4 - -# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names -# that should be included in the LaTeX output. The package can be specified just -# by its name or with the correct syntax as to be used with the LaTeX -# \usepackage command. To get the times font for instance you can specify : -# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} -# To use the option intlimits with the amsmath package you can specify: -# EXTRA_PACKAGES=[intlimits]{amsmath} -# If left blank no extra packages will be included. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. -# -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_HEADER = - -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See -# LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_FOOTER = - -# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# LaTeX style sheets that are included after the standard style sheets created -# by doxygen. Using this option one can overrule certain style aspects. Doxygen -# will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EXTRA_STYLESHEET = - -# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the LATEX_OUTPUT output -# directory. Note that the files will be copied as-is; there are no commands or -# markers available. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EXTRA_FILES = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is -# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will -# contain links (just like the HTML output) instead of page references. This -# makes the output suitable for online browsing using a PDF viewer. -# The default value is: YES. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -PDF_HYPERLINKS = YES - -# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as -# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX -# files. Set this option to YES, to get a higher quality PDF documentation. -# -# See also section LATEX_CMD_NAME for selecting the engine. -# The default value is: YES. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -USE_PDFLATEX = YES - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_BATCHMODE = NO - -# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the -# index chapters (such as File Index, Compound Index, etc.) in the output. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_HIDE_INDICES = NO - -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - -# The LATEX_BIB_STYLE tag can be used to specify the style to use for the -# bibliography, e.g. plainnat, or ieeetr. See -# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. -# The default value is: plain. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_BIB_STYLE = plain - -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - -# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) -# path from which the emoji images will be read. If a relative path is entered, -# it will be relative to the LATEX_OUTPUT directory. If left blank the -# LATEX_OUTPUT directory will be used. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EMOJI_DIRECTORY = - -#--------------------------------------------------------------------------- -# Configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The -# RTF output is optimized for Word 97 and may not look too pretty with other RTF -# readers/editors. -# The default value is: NO. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: rtf. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF -# documents. This may be useful for small projects and may help to save some -# trees in general. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will -# contain hyperlink fields. The RTF file will contain links (just like the HTML -# output) instead of page references. This makes the output suitable for online -# browsing using Word or some other Word compatible readers that support those -# fields. -# -# Note: WordPad (write) and others do not support links. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# configuration file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. -# -# See also section "Doxygen usage" for information on how to generate the -# default style sheet that doxygen normally uses. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's configuration file. A template extensions file can be -# generated using doxygen -e rtf extensionFile. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_EXTENSIONS_FILE = - -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_SOURCE_CODE = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for -# classes and files. -# The default value is: NO. - -GENERATE_MAN = NO - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. A directory man3 will be created inside the directory specified by -# MAN_OUTPUT. -# The default directory is: man. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to the generated -# man pages. In case the manual section does not start with a number, the number -# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is -# optional. -# The default value is: .3. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_EXTENSION = .3 - -# The MAN_SUBDIR tag determines the name of the directory created within -# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by -# MAN_EXTENSION with the initial . removed. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_SUBDIR = - -# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it -# will generate one additional man file for each entity documented in the real -# man page(s). These additional files only source the real man page, but without -# them the man command would be unable to find the correct page. -# The default value is: NO. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that -# captures the structure of the code including all documentation. -# The default value is: NO. - -GENERATE_XML = NO - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: xml. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_OUTPUT = xml - -# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program -# listings (including syntax highlighting and cross-referencing information) to -# the XML output. Note that enabling this will significantly increase the size -# of the XML output. -# The default value is: YES. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_PROGRAMLISTING = YES - -# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include -# namespace members in file scope as well, matching the HTML output. -# The default value is: NO. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_NS_MEMB_FILE_SCOPE = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the DOCBOOK output -#--------------------------------------------------------------------------- - -# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files -# that can be used to generate PDF. -# The default value is: NO. - -GENERATE_DOCBOOK = NO - -# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in -# front of it. -# The default directory is: docbook. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_OUTPUT = docbook - -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - -#--------------------------------------------------------------------------- -# Configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures -# the structure of the code including all documentation. Note that this feature -# is still experimental and incomplete at the moment. -# The default value is: NO. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module -# file that captures the structure of the code including all documentation. -# -# Note that this feature is still experimental and incomplete at the moment. -# The default value is: NO. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary -# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI -# output from the Perl module output. -# The default value is: NO. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely -# formatted so it can be parsed by a human reader. This is useful if you want to -# understand what is going on. On the other hand, if this tag is set to NO, the -# size of the Perl module output will be much smaller and Perl will parse it -# just the same. -# The default value is: YES. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file are -# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful -# so different doxyrules.make files included by the same Makefile don't -# overwrite each other's variables. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all -# C-preprocessor directives found in the sources and include files. -# The default value is: YES. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names -# in the source code. If set to NO, only conditional compilation will be -# performed. Macro expansion can be done in a controlled way by setting -# EXPAND_ONLY_PREDEF to YES. -# The default value is: NO. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -MACRO_EXPANSION = YES - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then -# the macro expansion is limited to the macros specified with the PREDEFINED and -# EXPAND_AS_DEFINED tags. -# The default value is: NO. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -EXPAND_ONLY_PREDEF = YES - -# If the SEARCH_INCLUDES tag is set to YES, the include files in the -# INCLUDE_PATH will be searched if a #include is found. -# The default value is: YES. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -SEARCH_INCLUDES = YES - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by the -# preprocessor. -# This tag requires that the tag SEARCH_INCLUDES is set to YES. - -INCLUDE_PATH = - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will be -# used. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that are -# defined before the preprocessor is started (similar to the -D option of e.g. -# gcc). The argument of the tag is a list of macros of the form: name or -# name=definition (no spaces). If the definition and the "=" are omitted, "=1" -# is assumed. To prevent a macro definition from being undefined via #undef or -# recursively expanded use the := operator instead of the = operator. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -PREDEFINED = VMA_CALL_PRE= \ - VMA_CALL_POST= \ - VMA_NOT_NULL= \ - VMA_NULLABLE= \ - VMA_LEN_IF_NOT_NULL(len)= \ - VMA_NOT_NULL_NON_DISPATCHABLE= \ - VMA_NULLABLE_NON_DISPATCHABLE= \ - VMA_EXTERNAL_MEMORY=1 - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this -# tag can be used to specify a list of macro names that should be expanded. The -# macro definition that is found in the sources will be used. Use the PREDEFINED -# tag if you want to use a different macro definition that overrules the -# definition found in the source code. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will -# remove all references to function-like macros that are alone on a line, have -# an all uppercase name, and do not end with a semicolon. Such function macros -# are typically used for boiler-plate code, and will confuse the parser if not -# removed. -# The default value is: YES. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration options related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES tag can be used to specify one or more tag files. For each tag -# file the location of the external documentation should be added. The format of -# a tag file without this location is as follows: -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where loc1 and loc2 can be relative or absolute paths or URLs. See the -# section "Linking to external documentation" for more information about the use -# of tag files. -# Note: Each tag file must have a unique name (where the name does NOT include -# the path). If a tag file is not located in the directory in which doxygen is -# run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create a -# tag file that is based on the input files it reads. See section "Linking to -# external documentation" for more information about the usage of tag files. - -GENERATE_TAGFILE = - -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. -# The default value is: NO. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be -# listed. -# The default value is: YES. - -EXTERNAL_GROUPS = YES - -# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in -# the related pages index. If set to NO, only the current project's pages will -# be listed. -# The default value is: YES. - -EXTERNAL_PAGES = YES - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - -# If set to YES the inheritance and collaboration graphs will hide inheritance -# and usage relations if the target is undocumented or is not a class. -# The default value is: YES. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent -# Bell Labs. The other options in this section have no effect if this option is -# set to NO -# The default value is: NO. - -HAVE_DOT = NO - -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed -# to run in parallel. When set to 0 doxygen will base this on the number of -# processors available in the system. You can set it explicitly to a value -# larger than 0 to get control over the balance between CPU load and processing -# speed. -# Minimum value: 0, maximum value: 32, default value: 0. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_NUM_THREADS = 0 - -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTNAME = Helvetica - -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTSIZE = 10 - -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTPATH = - -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a -# graph for each documented class showing the direct and indirect implementation -# dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -UML_LOOK = NO - -# If the UML_LOOK tag is enabled, the fields and methods are shown inside the -# class node. If there are many fields or methods and many nodes the graph may -# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the -# number of items for each type to make the size more manageable. Set this to 0 -# for no limit. Note that the threshold may be exceeded by 50% before the limit -# is enforced. So when you set the threshold to 10, up to 15 fields may appear, -# but if the number exceeds 15, the total amount of fields shown is limited to -# 10. -# Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag UML_LOOK is set to YES. - -UML_LIMIT_NUM_FIELDS = 10 - -# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and -# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS -# tag is set to YES, doxygen will add type and arguments for attributes and -# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen -# will not generate fields with class member information in the UML graphs. The -# class diagrams will look similar to the default class diagrams but using UML -# notation for the relationships. -# Possible values are: NO, YES and NONE. -# The default value is: NO. -# This tag requires that the tag UML_LOOK is set to YES. - -DOT_UML_DETAILS = NO - -# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters -# to display on a single line. If the actual line length exceeds this threshold -# significantly it will wrapped across multiple lines. Some heuristics are apply -# to avoid ugly line breaks. -# Minimum value: 0, maximum value: 1000, default value: 17. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_WRAP_THRESHOLD = 17 - -# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and -# collaboration graphs will show the relations between templates and their -# instances. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -TEMPLATE_RELATIONS = NO - -# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to -# YES then doxygen will generate a graph for each documented file showing the -# direct and indirect include dependencies of the file with other documented -# files. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -INCLUDE_GRAPH = YES - -# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are -# set to YES then doxygen will generate a graph for each documented file showing -# the direct and indirect include dependencies of the file with other documented -# files. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH tag is set to YES then doxygen will generate a call -# dependency graph for every global function or class method. -# -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. Disabling a call graph can be -# accomplished by means of the command \hidecallgraph. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -CALL_GRAPH = NO - -# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller -# dependency graph for every global function or class method. -# -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable caller graphs for selected -# functions only using the \callergraph command. Disabling a caller graph can be -# accomplished by means of the command \hidecallergraph. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical -# hierarchy of all classes instead of a textual one. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the -# dependencies a directory has on other directories in a graphical way. The -# dependency relations are determined by the #include relations between the -# files in the directories. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. For an explanation of the image formats see the section -# output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). -# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order -# to make the SVG files visible in IE 9+ (other browsers do not have this -# requirement). -# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, -# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and -# png:gdiplus:gdiplus. -# The default value is: png. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_IMAGE_FORMAT = png - -# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to -# enable generation of interactive SVG images that allow zooming and panning. -# -# Note that this requires a modern browser other than Internet Explorer. Tested -# and working are Firefox, Chrome, Safari, and Opera. -# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make -# the SVG files visible. Older versions of IE do not have SVG support. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -INTERACTIVE_SVG = NO - -# The DOT_PATH tag can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_PATH = - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the \dotfile -# command). -# This tag requires that the tag HAVE_DOT is set to YES. - -DOTFILE_DIRS = - -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). - -MSCFILE_DIRS = - -# The DIAFILE_DIRS tag can be used to specify one or more directories that -# contain dia files that are included in the documentation (see the \diafile -# command). - -DIAFILE_DIRS = - -# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. - -PLANTUML_JAR_PATH = - -# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a -# configuration file for plantuml. - -PLANTUML_CFG_FILE = - -# When using plantuml, the specified paths are searched for files specified by -# the !include statement in a plantuml block. - -PLANTUML_INCLUDE_PATH = - -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes -# that will be shown in the graph. If the number of nodes in a graph becomes -# larger than this value, doxygen will truncate the graph, which is visualized -# by representing a node as a red box. Note that doxygen if the number of direct -# children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that -# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. -# Minimum value: 0, maximum value: 10000, default value: 50. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_GRAPH_MAX_NODES = 50 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs -# generated by dot. A depth value of 3 means that only nodes reachable from the -# root by following a path via at most 3 edges will be shown. Nodes that lay -# further from the root node will be omitted. Note that setting this option to 1 -# or 2 may greatly reduce the computation time needed for large code bases. Also -# note that the size of a graph can be further restricted by -# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. -# Minimum value: 0, maximum value: 1000, default value: 0. -# This tag requires that the tag HAVE_DOT is set to YES. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) support -# this, this feature is disabled by default. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_MULTI_TARGETS = NO - -# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page -# explaining the meaning of the various boxes and arrows in the dot generated -# graphs. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate -# files that are used to generate the various graphs. -# -# Note: This setting is not only used for dot files but also for msc and -# plantuml temporary files. -# The default value is: YES. - -DOT_CLEANUP = YES +# Doxyfile 1.9.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Vulkan Memory Allocator" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = include/vk_mem_alloc.h + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using JavaScript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the +# generated LaTeX document. The header should contain everything until the first +# chapter. If it is left blank doxygen will generate a standard header. See +# section "Doxygen usage" for information on how to let doxygen write the +# default header to a separate file. +# +# Note: Only use a user-defined header if you know what you are doing! The +# following commands have a special meaning inside the header: $title, +# $datetime, $date, $doxygenversion, $projectname, $projectnumber, +# $projectbrief, $projectlogo. Doxygen will replace $title with the empty +# string, for the replacement values of the other commands the user is referred +# to HTML_HEADER. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the +# generated LaTeX document. The footer should contain everything after the last +# chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. +# +# Note: Only use a user-defined footer if you know what you are doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. This option is also used +# when generating formulas in HTML. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source +# code with syntax highlighting in the LaTeX output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code +# with syntax highlighting in the RTF output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the +# program listings (including syntax highlighting and cross-referencing +# information) to the DOCBOOK output. Note that enabling this will significantly +# increase the size of the DOCBOOK output. +# The default value is: NO. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = YES + +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = VMA_CALL_PRE= \ + VMA_CALL_POST= \ + VMA_NOT_NULL= \ + VMA_NULLABLE= \ + VMA_LEN_IF_NOT_NULL(len)= \ + VMA_NOT_NULL_NON_DISPATCHABLE= \ + VMA_NULLABLE_NON_DISPATCHABLE= \ + VMA_EXTERNAL_MEMORY=1 + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram +# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to +# NO turns the diagrams off. Note that this option also works with HAVE_DOT +# disabled, but it is recommended to install and use dot, since it yields more +# powerful graphs. +# The default value is: YES. + +CLASS_DIAGRAMS = YES + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# When you want a differently looking font in the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for +# each documented class showing the direct and indirect inheritance relations. +# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file. If left blank, it is assumed +# PlantUML is not used or called during a preprocessing step. Doxygen will +# generate a warning when it encounters a \startuml command in this case and +# will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc and +# plantuml temporary files. +# The default value is: YES. + +DOT_CLEANUP = YES
diff --git a/src/.editorconfig b/src/.editorconfig index 8abf0ef..3b730e6 100644 --- a/src/.editorconfig +++ b/src/.editorconfig
@@ -1,5 +1,6 @@ -root = true - -[**.{cpp,h}] -indent_style = space -indent_size = 4 +root = true + +[**.{cpp,h}] +indent_style = space +indent_size = 4 +end_of_line = lf
diff --git a/src/Common.cpp b/src/Common.cpp index 2dcbb68..b4bf54f 100644 --- a/src/Common.cpp +++ b/src/Common.cpp
@@ -1,328 +1,328 @@ -// -// Copyright (c) 2017-2021 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 "Common.h" - -#ifdef _WIN32 - -void ReadFile(std::vector<char>& out, const char* fileName) -{ - std::ifstream file(fileName, std::ios::ate | std::ios::binary); - assert(file.is_open()); - size_t fileSize = (size_t)file.tellg(); - if(fileSize > 0) - { - out.resize(fileSize); - file.seekg(0); - file.read(out.data(), fileSize); - } - else - out.clear(); -} - -void SetConsoleColor(CONSOLE_COLOR color) -{ - WORD attr = 0; - switch(color) - { - case CONSOLE_COLOR::INFO: - attr = FOREGROUND_INTENSITY; - break; - case CONSOLE_COLOR::NORMAL: - attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - break; - case CONSOLE_COLOR::WARNING: - attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; - break; - case CONSOLE_COLOR::ERROR_: - attr = FOREGROUND_RED | FOREGROUND_INTENSITY; - break; - default: - assert(0); - } - - HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(out, attr); -} - -void PrintMessage(CONSOLE_COLOR color, const char* msg) -{ - if(color != CONSOLE_COLOR::NORMAL) - SetConsoleColor(color); - - printf("%s\n", msg); - - if (color != CONSOLE_COLOR::NORMAL) - SetConsoleColor(CONSOLE_COLOR::NORMAL); -} - -void PrintMessage(CONSOLE_COLOR color, const wchar_t* msg) -{ - if(color != CONSOLE_COLOR::NORMAL) - SetConsoleColor(color); - - wprintf(L"%s\n", msg); - - if (color != CONSOLE_COLOR::NORMAL) - SetConsoleColor(CONSOLE_COLOR::NORMAL); -} - -static const size_t CONSOLE_SMALL_BUF_SIZE = 256; - -void PrintMessageV(CONSOLE_COLOR color, const char* format, va_list argList) -{ - size_t dstLen = (size_t)::_vscprintf(format, argList); - if(dstLen) - { - bool useSmallBuf = dstLen < CONSOLE_SMALL_BUF_SIZE; - char smallBuf[CONSOLE_SMALL_BUF_SIZE]; - std::vector<char> bigBuf(useSmallBuf ? 0 : dstLen + 1); - char* bufPtr = useSmallBuf ? smallBuf : bigBuf.data(); - ::vsprintf_s(bufPtr, dstLen + 1, format, argList); - PrintMessage(color, bufPtr); - } -} - -void PrintMessageV(CONSOLE_COLOR color, const wchar_t* format, va_list argList) -{ - size_t dstLen = (size_t)::_vcwprintf(format, argList); - if(dstLen) - { - bool useSmallBuf = dstLen < CONSOLE_SMALL_BUF_SIZE; - wchar_t smallBuf[CONSOLE_SMALL_BUF_SIZE]; - std::vector<wchar_t> bigBuf(useSmallBuf ? 0 : dstLen + 1); - wchar_t* bufPtr = useSmallBuf ? smallBuf : bigBuf.data(); - ::vswprintf_s(bufPtr, dstLen + 1, format, argList); - PrintMessage(color, bufPtr); - } -} - -void PrintMessageF(CONSOLE_COLOR color, const char* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(color, format, argList); - va_end(argList); -} - -void PrintMessageF(CONSOLE_COLOR color, const wchar_t* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(color, format, argList); - va_end(argList); -} - -void PrintWarningF(const char* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); - va_end(argList); -} - -void PrintWarningF(const wchar_t* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); - va_end(argList); -} - -void PrintErrorF(const char* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); - va_end(argList); -} - -void PrintErrorF(const wchar_t* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); - va_end(argList); -} - -void SaveFile(const wchar_t* filePath, const void* data, size_t dataSize) -{ - FILE* f = nullptr; - _wfopen_s(&f, filePath, L"wb"); - if(f) - { - fwrite(data, 1, dataSize, f); - fclose(f); - } - else - assert(0); -} - -std::wstring SizeToStr(size_t size) -{ - if(size == 0) - return L"0"; - wchar_t result[32]; - double size2 = (double)size; - if (size2 >= 1024.0*1024.0*1024.0*1024.0) - { - swprintf_s(result, L"%.2f TB", size2 / (1024.0*1024.0*1024.0*1024.0)); - } - else if (size2 >= 1024.0*1024.0*1024.0) - { - swprintf_s(result, L"%.2f GB", size2 / (1024.0*1024.0*1024.0)); - } - else if (size2 >= 1024.0*1024.0) - { - swprintf_s(result, L"%.2f MB", size2 / (1024.0*1024.0)); - } - else if (size2 >= 1024.0) - { - swprintf_s(result, L"%.2f KB", size2 / 1024.0); - } - else - swprintf_s(result, L"%llu B", size); - return result; -} - -bool ConvertCharsToUnicode(std::wstring *outStr, const std::string &s, unsigned codePage) -{ - if (s.empty()) - { - outStr->clear(); - return true; - } - - // Phase 1 - Get buffer size. - const int size = MultiByteToWideChar(codePage, 0, s.data(), (int)s.length(), NULL, 0); - if (size == 0) - { - outStr->clear(); - return false; - } - - // Phase 2 - Do conversion. - std::unique_ptr<wchar_t[]> buf(new wchar_t[(size_t)size]); - int result = MultiByteToWideChar(codePage, 0, s.data(), (int)s.length(), buf.get(), size); - if (result == 0) - { - outStr->clear(); - return false; - } - - outStr->assign(buf.get(), (size_t)size); - return true; -} - -bool ConvertCharsToUnicode(std::wstring *outStr, const char *s, size_t sCharCount, unsigned codePage) -{ - if (sCharCount == 0) - { - outStr->clear(); - return true; - } - - assert(sCharCount <= (size_t)INT_MAX); - - // Phase 1 - Get buffer size. - int size = MultiByteToWideChar(codePage, 0, s, (int)sCharCount, NULL, 0); - if (size == 0) - { - outStr->clear(); - return false; - } - - // Phase 2 - Do conversion. - std::unique_ptr<wchar_t[]> buf(new wchar_t[(size_t)size]); - int result = MultiByteToWideChar(codePage, 0, s, (int)sCharCount, buf.get(), size); - if (result == 0) - { - outStr->clear(); - return false; - } - - outStr->assign(buf.get(), (size_t)size); - return true; -} - -const wchar_t* PhysicalDeviceTypeToStr(VkPhysicalDeviceType type) -{ - // Skipping common prefix VK_PHYSICAL_DEVICE_TYPE_ - static const wchar_t* const VALUES[] = { - L"OTHER", - L"INTEGRATED_GPU", - L"DISCRETE_GPU", - L"VIRTUAL_GPU", - L"CPU", - }; - return (uint32_t)type < _countof(VALUES) ? VALUES[(uint32_t)type] : L""; -} - -const wchar_t* VendorIDToStr(uint32_t vendorID) -{ - switch(vendorID) - { - // Skipping common prefix VK_VENDOR_ID_ for these: - case 0x10001: return L"VIV"; - case 0x10002: return L"VSI"; - case 0x10003: return L"KAZAN"; - case 0x10004: return L"CODEPLAY"; - case 0x10005: return L"MESA"; - case 0x10006: return L"POCL"; - // Others... - case VENDOR_ID_AMD: return L"AMD"; - case VENDOR_ID_NVIDIA: return L"NVIDIA"; - case VENDOR_ID_INTEL: return L"Intel"; - case 0x1010: return L"ImgTec"; - case 0x13B5: return L"ARM"; - case 0x5143: return L"Qualcomm"; - } - return L""; -} - -#if VMA_VULKAN_VERSION >= 1002000 -const wchar_t* DriverIDToStr(VkDriverId driverID) -{ - // Skipping common prefix VK_DRIVER_ID_ - static const wchar_t* const VALUES[] = { - L"", - L"AMD_PROPRIETARY", - L"AMD_OPEN_SOURCE", - L"MESA_RADV", - L"NVIDIA_PROPRIETARY", - L"INTEL_PROPRIETARY_WINDOWS", - L"INTEL_OPEN_SOURCE_MESA", - L"IMAGINATION_PROPRIETARY", - L"QUALCOMM_PROPRIETARY", - L"ARM_PROPRIETARY", - L"GOOGLE_SWIFTSHADER", - L"GGP_PROPRIETARY", - L"BROADCOM_PROPRIETARY", - L"MESA_LLVMPIPE", - L"MOLTENVK", - }; - return (uint32_t)driverID < _countof(VALUES) ? VALUES[(uint32_t)driverID] : L""; -} -#endif // #if VMA_VULKAN_VERSION >= 1002000 - - -#endif // #ifdef _WIN32 +// +// Copyright (c) 2017-2021 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 "Common.h" + +#ifdef _WIN32 + +void ReadFile(std::vector<char>& out, const char* fileName) +{ + std::ifstream file(fileName, std::ios::ate | std::ios::binary); + assert(file.is_open()); + size_t fileSize = (size_t)file.tellg(); + if(fileSize > 0) + { + out.resize(fileSize); + file.seekg(0); + file.read(out.data(), fileSize); + } + else + out.clear(); +} + +void SetConsoleColor(CONSOLE_COLOR color) +{ + WORD attr = 0; + switch(color) + { + case CONSOLE_COLOR::INFO: + attr = FOREGROUND_INTENSITY; + break; + case CONSOLE_COLOR::NORMAL: + attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + break; + case CONSOLE_COLOR::WARNING: + attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; + break; + case CONSOLE_COLOR::ERROR_: + attr = FOREGROUND_RED | FOREGROUND_INTENSITY; + break; + default: + assert(0); + } + + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(out, attr); +} + +void PrintMessage(CONSOLE_COLOR color, const char* msg) +{ + if(color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(color); + + printf("%s\n", msg); + + if (color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(CONSOLE_COLOR::NORMAL); +} + +void PrintMessage(CONSOLE_COLOR color, const wchar_t* msg) +{ + if(color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(color); + + wprintf(L"%s\n", msg); + + if (color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(CONSOLE_COLOR::NORMAL); +} + +static const size_t CONSOLE_SMALL_BUF_SIZE = 256; + +void PrintMessageV(CONSOLE_COLOR color, const char* format, va_list argList) +{ + size_t dstLen = (size_t)::_vscprintf(format, argList); + if(dstLen) + { + bool useSmallBuf = dstLen < CONSOLE_SMALL_BUF_SIZE; + char smallBuf[CONSOLE_SMALL_BUF_SIZE]; + std::vector<char> bigBuf(useSmallBuf ? 0 : dstLen + 1); + char* bufPtr = useSmallBuf ? smallBuf : bigBuf.data(); + ::vsprintf_s(bufPtr, dstLen + 1, format, argList); + PrintMessage(color, bufPtr); + } +} + +void PrintMessageV(CONSOLE_COLOR color, const wchar_t* format, va_list argList) +{ + size_t dstLen = (size_t)::_vcwprintf(format, argList); + if(dstLen) + { + bool useSmallBuf = dstLen < CONSOLE_SMALL_BUF_SIZE; + wchar_t smallBuf[CONSOLE_SMALL_BUF_SIZE]; + std::vector<wchar_t> bigBuf(useSmallBuf ? 0 : dstLen + 1); + wchar_t* bufPtr = useSmallBuf ? smallBuf : bigBuf.data(); + ::vswprintf_s(bufPtr, dstLen + 1, format, argList); + PrintMessage(color, bufPtr); + } +} + +void PrintMessageF(CONSOLE_COLOR color, const char* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(color, format, argList); + va_end(argList); +} + +void PrintMessageF(CONSOLE_COLOR color, const wchar_t* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(color, format, argList); + va_end(argList); +} + +void PrintWarningF(const char* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} + +void PrintWarningF(const wchar_t* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} + +void PrintErrorF(const char* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} + +void PrintErrorF(const wchar_t* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} + +void SaveFile(const wchar_t* filePath, const void* data, size_t dataSize) +{ + FILE* f = nullptr; + _wfopen_s(&f, filePath, L"wb"); + if(f) + { + fwrite(data, 1, dataSize, f); + fclose(f); + } + else + assert(0); +} + +std::wstring SizeToStr(size_t size) +{ + if(size == 0) + return L"0"; + wchar_t result[32]; + double size2 = (double)size; + if (size2 >= 1024.0*1024.0*1024.0*1024.0) + { + swprintf_s(result, L"%.2f TB", size2 / (1024.0*1024.0*1024.0*1024.0)); + } + else if (size2 >= 1024.0*1024.0*1024.0) + { + swprintf_s(result, L"%.2f GB", size2 / (1024.0*1024.0*1024.0)); + } + else if (size2 >= 1024.0*1024.0) + { + swprintf_s(result, L"%.2f MB", size2 / (1024.0*1024.0)); + } + else if (size2 >= 1024.0) + { + swprintf_s(result, L"%.2f KB", size2 / 1024.0); + } + else + swprintf_s(result, L"%llu B", size); + return result; +} + +bool ConvertCharsToUnicode(std::wstring *outStr, const std::string &s, unsigned codePage) +{ + if (s.empty()) + { + outStr->clear(); + return true; + } + + // Phase 1 - Get buffer size. + const int size = MultiByteToWideChar(codePage, 0, s.data(), (int)s.length(), NULL, 0); + if (size == 0) + { + outStr->clear(); + return false; + } + + // Phase 2 - Do conversion. + std::unique_ptr<wchar_t[]> buf(new wchar_t[(size_t)size]); + int result = MultiByteToWideChar(codePage, 0, s.data(), (int)s.length(), buf.get(), size); + if (result == 0) + { + outStr->clear(); + return false; + } + + outStr->assign(buf.get(), (size_t)size); + return true; +} + +bool ConvertCharsToUnicode(std::wstring *outStr, const char *s, size_t sCharCount, unsigned codePage) +{ + if (sCharCount == 0) + { + outStr->clear(); + return true; + } + + assert(sCharCount <= (size_t)INT_MAX); + + // Phase 1 - Get buffer size. + int size = MultiByteToWideChar(codePage, 0, s, (int)sCharCount, NULL, 0); + if (size == 0) + { + outStr->clear(); + return false; + } + + // Phase 2 - Do conversion. + std::unique_ptr<wchar_t[]> buf(new wchar_t[(size_t)size]); + int result = MultiByteToWideChar(codePage, 0, s, (int)sCharCount, buf.get(), size); + if (result == 0) + { + outStr->clear(); + return false; + } + + outStr->assign(buf.get(), (size_t)size); + return true; +} + +const wchar_t* PhysicalDeviceTypeToStr(VkPhysicalDeviceType type) +{ + // Skipping common prefix VK_PHYSICAL_DEVICE_TYPE_ + static const wchar_t* const VALUES[] = { + L"OTHER", + L"INTEGRATED_GPU", + L"DISCRETE_GPU", + L"VIRTUAL_GPU", + L"CPU", + }; + return (uint32_t)type < _countof(VALUES) ? VALUES[(uint32_t)type] : L""; +} + +const wchar_t* VendorIDToStr(uint32_t vendorID) +{ + switch(vendorID) + { + // Skipping common prefix VK_VENDOR_ID_ for these: + case 0x10001: return L"VIV"; + case 0x10002: return L"VSI"; + case 0x10003: return L"KAZAN"; + case 0x10004: return L"CODEPLAY"; + case 0x10005: return L"MESA"; + case 0x10006: return L"POCL"; + // Others... + case VENDOR_ID_AMD: return L"AMD"; + case VENDOR_ID_NVIDIA: return L"NVIDIA"; + case VENDOR_ID_INTEL: return L"Intel"; + case 0x1010: return L"ImgTec"; + case 0x13B5: return L"ARM"; + case 0x5143: return L"Qualcomm"; + } + return L""; +} + +#if VMA_VULKAN_VERSION >= 1002000 +const wchar_t* DriverIDToStr(VkDriverId driverID) +{ + // Skipping common prefix VK_DRIVER_ID_ + static const wchar_t* const VALUES[] = { + L"", + L"AMD_PROPRIETARY", + L"AMD_OPEN_SOURCE", + L"MESA_RADV", + L"NVIDIA_PROPRIETARY", + L"INTEL_PROPRIETARY_WINDOWS", + L"INTEL_OPEN_SOURCE_MESA", + L"IMAGINATION_PROPRIETARY", + L"QUALCOMM_PROPRIETARY", + L"ARM_PROPRIETARY", + L"GOOGLE_SWIFTSHADER", + L"GGP_PROPRIETARY", + L"BROADCOM_PROPRIETARY", + L"MESA_LLVMPIPE", + L"MOLTENVK", + }; + return (uint32_t)driverID < _countof(VALUES) ? VALUES[(uint32_t)driverID] : L""; +} +#endif // #if VMA_VULKAN_VERSION >= 1002000 + + +#endif // #ifdef _WIN32
diff --git a/src/Common.h b/src/Common.h index a718234..4e2a0dd 100644 --- a/src/Common.h +++ b/src/Common.h
@@ -1,339 +1,339 @@ -// -// Copyright (c) 2017-2021 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. -// - -#ifndef COMMON_H_ -#define COMMON_H_ - -#include "VmaUsage.h" - -#ifdef _WIN32 - -#include <iostream> -#include <fstream> -#include <vector> -#include <memory> -#include <algorithm> -#include <numeric> -#include <array> -#include <type_traits> -#include <utility> -#include <chrono> -#include <string> -#include <exception> - -#include <cassert> -#include <cstdlib> -#include <cstdio> -#include <cstdarg> - -typedef std::chrono::high_resolution_clock::time_point time_point; -typedef std::chrono::high_resolution_clock::duration duration; - -#define STRINGIZE(x) STRINGIZE2(x) -#define STRINGIZE2(x) #x -#define LINE_STRING STRINGIZE(__LINE__) -#define TEST(expr) do { if(!(expr)) { \ - assert(0 && #expr); \ - throw std::runtime_error(__FILE__ "(" LINE_STRING "): ( " #expr " ) == false"); \ - } } while(false) -#define ERR_GUARD_VULKAN(expr) do { if((expr) < 0) { \ - assert(0 && #expr); \ - throw std::runtime_error(__FILE__ "(" LINE_STRING "): VkResult( " #expr " ) < 0"); \ - } } while(false) - -static const uint32_t VENDOR_ID_AMD = 0x1002; -static const uint32_t VENDOR_ID_NVIDIA = 0x10DE; -static const uint32_t VENDOR_ID_INTEL = 0x8086; - -extern VkInstance g_hVulkanInstance; -extern VkPhysicalDevice g_hPhysicalDevice; -extern VkDevice g_hDevice; -extern VkInstance g_hVulkanInstance; -extern VmaAllocator g_hAllocator; -extern bool VK_AMD_device_coherent_memory_enabled; - -void SetAllocatorCreateInfo(VmaAllocatorCreateInfo& outInfo); - -inline float ToFloatSeconds(duration d) -{ - return std::chrono::duration_cast<std::chrono::duration<float>>(d).count(); -} - -template <typename T> -inline T ceil_div(T x, T y) -{ - return (x+y-1) / y; -} -template <typename T> -inline T round_div(T x, T y) -{ - return (x+y/(T)2) / y; -} - -template <typename T> -static inline T align_up(T val, T align) -{ - return (val + align - 1) / align * align; -} - -static const float PI = 3.14159265358979323846264338327950288419716939937510582f; - -template<typename MainT, typename NewT> -inline void PnextChainPushFront(MainT* mainStruct, NewT* newStruct) -{ - newStruct->pNext = mainStruct->pNext; - mainStruct->pNext = newStruct; -} -template<typename MainT, typename NewT> -inline void PnextChainPushBack(MainT* mainStruct, NewT* newStruct) -{ - struct VkAnyStruct - { - VkStructureType sType; - void* pNext; - }; - VkAnyStruct* lastStruct = (VkAnyStruct*)mainStruct; - while(lastStruct->pNext != nullptr) - { - lastStruct = (VkAnyStruct*)lastStruct->pNext; - } - newStruct->pNext = nullptr; - lastStruct->pNext = newStruct; -} - -struct vec3 -{ - float x, y, z; - - vec3() { } - vec3(float x, float y, float z) : x(x), y(y), z(z) { } - - float& operator[](uint32_t index) { return *(&x + index); } - const float& operator[](uint32_t index) const { return *(&x + index); } - - vec3 operator+(const vec3& rhs) const { return vec3(x + rhs.x, y + rhs.y, z + rhs.z); } - vec3 operator-(const vec3& rhs) const { return vec3(x - rhs.x, y - rhs.y, z - rhs.z); } - vec3 operator*(float s) const { return vec3(x * s, y * s, z * s); } - - vec3 Normalized() const - { - return (*this) * (1.f / sqrt(x * x + y * y + z * z)); - } -}; - -inline float Dot(const vec3& lhs, const vec3& rhs) -{ - return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; -} -inline vec3 Cross(const vec3& lhs, const vec3& rhs) -{ - return vec3( - lhs.y * rhs.z - lhs.z * rhs.y, - lhs.z * rhs.x - lhs.x * rhs.z, - lhs.x * rhs.y - lhs.y * rhs.x); -} - -struct vec4 -{ - float x, y, z, w; - - vec4() { } - vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) { } - vec4(const vec3& v, float w) : x(v.x), y(v.y), z(v.z), w(w) { } - - float& operator[](uint32_t index) { return *(&x + index); } - const float& operator[](uint32_t index) const { return *(&x + index); } -}; - -struct mat4 -{ - union - { - struct - { - float _11, _12, _13, _14; - float _21, _22, _23, _24; - float _31, _32, _33, _34; - float _41, _42, _43, _44; - }; - float m[4][4]; // [row][column] - }; - - mat4() { } - - mat4( - float _11, float _12, float _13, float _14, - float _21, float _22, float _23, float _24, - float _31, float _32, float _33, float _34, - float _41, float _42, float _43, float _44) : - _11(_11), _12(_12), _13(_13), _14(_14), - _21(_21), _22(_22), _23(_23), _24(_24), - _31(_31), _32(_32), _33(_33), _34(_34), - _41(_41), _42(_42), _43(_43), _44(_44) - { - } - - mat4( - const vec4& row1, - const vec4& row2, - const vec4& row3, - const vec4& row4) : - _11(row1.x), _12(row1.y), _13(row1.z), _14(row1.w), - _21(row2.x), _22(row2.y), _23(row2.z), _24(row2.w), - _31(row3.x), _32(row3.y), _33(row3.z), _34(row3.w), - _41(row4.x), _42(row4.y), _43(row4.z), _44(row4.w) - { - } - - mat4 operator*(const mat4 &rhs) const - { - return mat4( - _11 * rhs._11 + _12 * rhs._21 + _13 * rhs._31 + _14 * rhs._41, - _11 * rhs._12 + _12 * rhs._22 + _13 * rhs._32 + _14 * rhs._42, - _11 * rhs._13 + _12 * rhs._23 + _13 * rhs._33 + _14 * rhs._43, - _11 * rhs._14 + _12 * rhs._24 + _13 * rhs._34 + _14 * rhs._44, - - _21 * rhs._11 + _22 * rhs._21 + _23 * rhs._31 + _24 * rhs._41, - _21 * rhs._12 + _22 * rhs._22 + _23 * rhs._32 + _24 * rhs._42, - _21 * rhs._13 + _22 * rhs._23 + _23 * rhs._33 + _24 * rhs._43, - _21 * rhs._14 + _22 * rhs._24 + _23 * rhs._34 + _24 * rhs._44, - - _31 * rhs._11 + _32 * rhs._21 + _33 * rhs._31 + _34 * rhs._41, - _31 * rhs._12 + _32 * rhs._22 + _33 * rhs._32 + _34 * rhs._42, - _31 * rhs._13 + _32 * rhs._23 + _33 * rhs._33 + _34 * rhs._43, - _31 * rhs._14 + _32 * rhs._24 + _33 * rhs._34 + _34 * rhs._44, - - _41 * rhs._11 + _42 * rhs._21 + _43 * rhs._31 + _44 * rhs._41, - _41 * rhs._12 + _42 * rhs._22 + _43 * rhs._32 + _44 * rhs._42, - _41 * rhs._13 + _42 * rhs._23 + _43 * rhs._33 + _44 * rhs._43, - _41 * rhs._14 + _42 * rhs._24 + _43 * rhs._34 + _44 * rhs._44); - } - - static mat4 RotationY(float angle) - { - const float s = sin(angle), c = cos(angle); - return mat4( - c, 0.f, -s, 0.f, - 0.f, 1.f, 0.f, 0.f, - s, 0.f, c, 0.f, - 0.f, 0.f, 0.f, 1.f); - } - - static mat4 Perspective(float fovY, float aspectRatio, float zNear, float zFar) - { - float yScale = 1.0f / tan(fovY * 0.5f); - float xScale = yScale / aspectRatio; - return mat4( - xScale, 0.0f, 0.0f, 0.0f, - 0.0f, yScale, 0.0f, 0.0f, - 0.0f, 0.0f, zFar / (zFar - zNear), 1.0f, - 0.0f, 0.0f, -zNear * zFar / (zFar - zNear), 0.0f); - } - - static mat4 LookAt(vec3 at, vec3 eye, vec3 up) - { - vec3 zAxis = (at - eye).Normalized(); - vec3 xAxis = Cross(up, zAxis).Normalized(); - vec3 yAxis = Cross(zAxis, xAxis); - return mat4( - xAxis.x, yAxis.x, zAxis.x, 0.0f, - xAxis.y, yAxis.y, zAxis.y, 0.0f, - xAxis.z, yAxis.z, zAxis.z, 0.0f, - -Dot(xAxis, eye), -Dot(yAxis, eye), -Dot(zAxis, eye), 1.0f); - } -}; - -class RandomNumberGenerator -{ -public: - RandomNumberGenerator() : m_Value{GetTickCount()} {} - RandomNumberGenerator(uint32_t seed) : m_Value{seed} { } - void Seed(uint32_t seed) { m_Value = seed; } - uint32_t Generate() { return GenerateFast() ^ (GenerateFast() >> 7); } - -private: - uint32_t m_Value; - uint32_t GenerateFast() { return m_Value = (m_Value * 196314165 + 907633515); } -}; - -// Wrapper for RandomNumberGenerator compatible with STL "UniformRandomNumberGenerator" idea. -struct MyUniformRandomNumberGenerator -{ - typedef uint32_t result_type; - MyUniformRandomNumberGenerator(RandomNumberGenerator& gen) : m_Gen(gen) { } - static uint32_t min() { return 0; } - static uint32_t max() { return UINT32_MAX; } - uint32_t operator()() { return m_Gen.Generate(); } - -private: - RandomNumberGenerator& m_Gen; -}; - -void ReadFile(std::vector<char>& out, const char* fileName); - -enum class CONSOLE_COLOR -{ - INFO, - NORMAL, - WARNING, - ERROR_, - COUNT -}; - -void SetConsoleColor(CONSOLE_COLOR color); - -void PrintMessage(CONSOLE_COLOR color, const char* msg); -void PrintMessage(CONSOLE_COLOR color, const wchar_t* msg); - -inline void Print(const char* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); } -inline void Print(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); } -inline void PrintWarning(const char* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); } -inline void PrintWarning(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); } -inline void PrintError(const char* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); } -inline void PrintError(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); } - -void PrintMessageV(CONSOLE_COLOR color, const char* format, va_list argList); -void PrintMessageV(CONSOLE_COLOR color, const wchar_t* format, va_list argList); -void PrintMessageF(CONSOLE_COLOR color, const char* format, ...); -void PrintMessageF(CONSOLE_COLOR color, const wchar_t* format, ...); -void PrintWarningF(const char* format, ...); -void PrintWarningF(const wchar_t* format, ...); -void PrintErrorF(const char* format, ...); -void PrintErrorF(const wchar_t* format, ...); - -void SaveFile(const wchar_t* filePath, const void* data, size_t dataSize); - -std::wstring SizeToStr(size_t size); -// As codePage use e.g. CP_ACP for native Windows 1-byte codepage or CP_UTF8. -bool ConvertCharsToUnicode(std::wstring *outStr, const std::string &s, unsigned codePage); -bool ConvertCharsToUnicode(std::wstring *outStr, const char *s, size_t sCharCount, unsigned codePage); - -const wchar_t* PhysicalDeviceTypeToStr(VkPhysicalDeviceType type); -const wchar_t* VendorIDToStr(uint32_t vendorID); - -#if VMA_VULKAN_VERSION >= 1002000 -const wchar_t* DriverIDToStr(VkDriverId driverID); -#endif - -#endif // #ifdef _WIN32 - -#endif +// +// Copyright (c) 2017-2021 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. +// + +#ifndef COMMON_H_ +#define COMMON_H_ + +#include "VmaUsage.h" + +#ifdef _WIN32 + +#include <iostream> +#include <fstream> +#include <vector> +#include <memory> +#include <algorithm> +#include <numeric> +#include <array> +#include <type_traits> +#include <utility> +#include <chrono> +#include <string> +#include <exception> + +#include <cassert> +#include <cstdlib> +#include <cstdio> +#include <cstdarg> + +typedef std::chrono::high_resolution_clock::time_point time_point; +typedef std::chrono::high_resolution_clock::duration duration; + +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define LINE_STRING STRINGIZE(__LINE__) +#define TEST(expr) do { if(!(expr)) { \ + assert(0 && #expr); \ + throw std::runtime_error(__FILE__ "(" LINE_STRING "): ( " #expr " ) == false"); \ + } } while(false) +#define ERR_GUARD_VULKAN(expr) do { if((expr) < 0) { \ + assert(0 && #expr); \ + throw std::runtime_error(__FILE__ "(" LINE_STRING "): VkResult( " #expr " ) < 0"); \ + } } while(false) + +static const uint32_t VENDOR_ID_AMD = 0x1002; +static const uint32_t VENDOR_ID_NVIDIA = 0x10DE; +static const uint32_t VENDOR_ID_INTEL = 0x8086; + +extern VkInstance g_hVulkanInstance; +extern VkPhysicalDevice g_hPhysicalDevice; +extern VkDevice g_hDevice; +extern VkInstance g_hVulkanInstance; +extern VmaAllocator g_hAllocator; +extern bool VK_AMD_device_coherent_memory_enabled; + +void SetAllocatorCreateInfo(VmaAllocatorCreateInfo& outInfo); + +inline float ToFloatSeconds(duration d) +{ + return std::chrono::duration_cast<std::chrono::duration<float>>(d).count(); +} + +template <typename T> +inline T ceil_div(T x, T y) +{ + return (x+y-1) / y; +} +template <typename T> +inline T round_div(T x, T y) +{ + return (x+y/(T)2) / y; +} + +template <typename T> +static inline T align_up(T val, T align) +{ + return (val + align - 1) / align * align; +} + +static const float PI = 3.14159265358979323846264338327950288419716939937510582f; + +template<typename MainT, typename NewT> +inline void PnextChainPushFront(MainT* mainStruct, NewT* newStruct) +{ + newStruct->pNext = mainStruct->pNext; + mainStruct->pNext = newStruct; +} +template<typename MainT, typename NewT> +inline void PnextChainPushBack(MainT* mainStruct, NewT* newStruct) +{ + struct VkAnyStruct + { + VkStructureType sType; + void* pNext; + }; + VkAnyStruct* lastStruct = (VkAnyStruct*)mainStruct; + while(lastStruct->pNext != nullptr) + { + lastStruct = (VkAnyStruct*)lastStruct->pNext; + } + newStruct->pNext = nullptr; + lastStruct->pNext = newStruct; +} + +struct vec3 +{ + float x, y, z; + + vec3() { } + vec3(float x, float y, float z) : x(x), y(y), z(z) { } + + float& operator[](uint32_t index) { return *(&x + index); } + const float& operator[](uint32_t index) const { return *(&x + index); } + + vec3 operator+(const vec3& rhs) const { return vec3(x + rhs.x, y + rhs.y, z + rhs.z); } + vec3 operator-(const vec3& rhs) const { return vec3(x - rhs.x, y - rhs.y, z - rhs.z); } + vec3 operator*(float s) const { return vec3(x * s, y * s, z * s); } + + vec3 Normalized() const + { + return (*this) * (1.f / sqrt(x * x + y * y + z * z)); + } +}; + +inline float Dot(const vec3& lhs, const vec3& rhs) +{ + return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; +} +inline vec3 Cross(const vec3& lhs, const vec3& rhs) +{ + return vec3( + lhs.y * rhs.z - lhs.z * rhs.y, + lhs.z * rhs.x - lhs.x * rhs.z, + lhs.x * rhs.y - lhs.y * rhs.x); +} + +struct vec4 +{ + float x, y, z, w; + + vec4() { } + vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) { } + vec4(const vec3& v, float w) : x(v.x), y(v.y), z(v.z), w(w) { } + + float& operator[](uint32_t index) { return *(&x + index); } + const float& operator[](uint32_t index) const { return *(&x + index); } +}; + +struct mat4 +{ + union + { + struct + { + float _11, _12, _13, _14; + float _21, _22, _23, _24; + float _31, _32, _33, _34; + float _41, _42, _43, _44; + }; + float m[4][4]; // [row][column] + }; + + mat4() { } + + mat4( + float _11, float _12, float _13, float _14, + float _21, float _22, float _23, float _24, + float _31, float _32, float _33, float _34, + float _41, float _42, float _43, float _44) : + _11(_11), _12(_12), _13(_13), _14(_14), + _21(_21), _22(_22), _23(_23), _24(_24), + _31(_31), _32(_32), _33(_33), _34(_34), + _41(_41), _42(_42), _43(_43), _44(_44) + { + } + + mat4( + const vec4& row1, + const vec4& row2, + const vec4& row3, + const vec4& row4) : + _11(row1.x), _12(row1.y), _13(row1.z), _14(row1.w), + _21(row2.x), _22(row2.y), _23(row2.z), _24(row2.w), + _31(row3.x), _32(row3.y), _33(row3.z), _34(row3.w), + _41(row4.x), _42(row4.y), _43(row4.z), _44(row4.w) + { + } + + mat4 operator*(const mat4 &rhs) const + { + return mat4( + _11 * rhs._11 + _12 * rhs._21 + _13 * rhs._31 + _14 * rhs._41, + _11 * rhs._12 + _12 * rhs._22 + _13 * rhs._32 + _14 * rhs._42, + _11 * rhs._13 + _12 * rhs._23 + _13 * rhs._33 + _14 * rhs._43, + _11 * rhs._14 + _12 * rhs._24 + _13 * rhs._34 + _14 * rhs._44, + + _21 * rhs._11 + _22 * rhs._21 + _23 * rhs._31 + _24 * rhs._41, + _21 * rhs._12 + _22 * rhs._22 + _23 * rhs._32 + _24 * rhs._42, + _21 * rhs._13 + _22 * rhs._23 + _23 * rhs._33 + _24 * rhs._43, + _21 * rhs._14 + _22 * rhs._24 + _23 * rhs._34 + _24 * rhs._44, + + _31 * rhs._11 + _32 * rhs._21 + _33 * rhs._31 + _34 * rhs._41, + _31 * rhs._12 + _32 * rhs._22 + _33 * rhs._32 + _34 * rhs._42, + _31 * rhs._13 + _32 * rhs._23 + _33 * rhs._33 + _34 * rhs._43, + _31 * rhs._14 + _32 * rhs._24 + _33 * rhs._34 + _34 * rhs._44, + + _41 * rhs._11 + _42 * rhs._21 + _43 * rhs._31 + _44 * rhs._41, + _41 * rhs._12 + _42 * rhs._22 + _43 * rhs._32 + _44 * rhs._42, + _41 * rhs._13 + _42 * rhs._23 + _43 * rhs._33 + _44 * rhs._43, + _41 * rhs._14 + _42 * rhs._24 + _43 * rhs._34 + _44 * rhs._44); + } + + static mat4 RotationY(float angle) + { + const float s = sin(angle), c = cos(angle); + return mat4( + c, 0.f, -s, 0.f, + 0.f, 1.f, 0.f, 0.f, + s, 0.f, c, 0.f, + 0.f, 0.f, 0.f, 1.f); + } + + static mat4 Perspective(float fovY, float aspectRatio, float zNear, float zFar) + { + float yScale = 1.0f / tan(fovY * 0.5f); + float xScale = yScale / aspectRatio; + return mat4( + xScale, 0.0f, 0.0f, 0.0f, + 0.0f, yScale, 0.0f, 0.0f, + 0.0f, 0.0f, zFar / (zFar - zNear), 1.0f, + 0.0f, 0.0f, -zNear * zFar / (zFar - zNear), 0.0f); + } + + static mat4 LookAt(vec3 at, vec3 eye, vec3 up) + { + vec3 zAxis = (at - eye).Normalized(); + vec3 xAxis = Cross(up, zAxis).Normalized(); + vec3 yAxis = Cross(zAxis, xAxis); + return mat4( + xAxis.x, yAxis.x, zAxis.x, 0.0f, + xAxis.y, yAxis.y, zAxis.y, 0.0f, + xAxis.z, yAxis.z, zAxis.z, 0.0f, + -Dot(xAxis, eye), -Dot(yAxis, eye), -Dot(zAxis, eye), 1.0f); + } +}; + +class RandomNumberGenerator +{ +public: + RandomNumberGenerator() : m_Value{GetTickCount()} {} + RandomNumberGenerator(uint32_t seed) : m_Value{seed} { } + void Seed(uint32_t seed) { m_Value = seed; } + uint32_t Generate() { return GenerateFast() ^ (GenerateFast() >> 7); } + +private: + uint32_t m_Value; + uint32_t GenerateFast() { return m_Value = (m_Value * 196314165 + 907633515); } +}; + +// Wrapper for RandomNumberGenerator compatible with STL "UniformRandomNumberGenerator" idea. +struct MyUniformRandomNumberGenerator +{ + typedef uint32_t result_type; + MyUniformRandomNumberGenerator(RandomNumberGenerator& gen) : m_Gen(gen) { } + static uint32_t min() { return 0; } + static uint32_t max() { return UINT32_MAX; } + uint32_t operator()() { return m_Gen.Generate(); } + +private: + RandomNumberGenerator& m_Gen; +}; + +void ReadFile(std::vector<char>& out, const char* fileName); + +enum class CONSOLE_COLOR +{ + INFO, + NORMAL, + WARNING, + ERROR_, + COUNT +}; + +void SetConsoleColor(CONSOLE_COLOR color); + +void PrintMessage(CONSOLE_COLOR color, const char* msg); +void PrintMessage(CONSOLE_COLOR color, const wchar_t* msg); + +inline void Print(const char* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); } +inline void Print(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); } +inline void PrintWarning(const char* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); } +inline void PrintWarning(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); } +inline void PrintError(const char* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); } +inline void PrintError(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); } + +void PrintMessageV(CONSOLE_COLOR color, const char* format, va_list argList); +void PrintMessageV(CONSOLE_COLOR color, const wchar_t* format, va_list argList); +void PrintMessageF(CONSOLE_COLOR color, const char* format, ...); +void PrintMessageF(CONSOLE_COLOR color, const wchar_t* format, ...); +void PrintWarningF(const char* format, ...); +void PrintWarningF(const wchar_t* format, ...); +void PrintErrorF(const char* format, ...); +void PrintErrorF(const wchar_t* format, ...); + +void SaveFile(const wchar_t* filePath, const void* data, size_t dataSize); + +std::wstring SizeToStr(size_t size); +// As codePage use e.g. CP_ACP for native Windows 1-byte codepage or CP_UTF8. +bool ConvertCharsToUnicode(std::wstring *outStr, const std::string &s, unsigned codePage); +bool ConvertCharsToUnicode(std::wstring *outStr, const char *s, size_t sCharCount, unsigned codePage); + +const wchar_t* PhysicalDeviceTypeToStr(VkPhysicalDeviceType type); +const wchar_t* VendorIDToStr(uint32_t vendorID); + +#if VMA_VULKAN_VERSION >= 1002000 +const wchar_t* DriverIDToStr(VkDriverId driverID); +#endif + +#endif // #ifdef _WIN32 + +#endif
diff --git a/src/Shaders/Shader.frag b/src/Shaders/Shader.frag index 207ec8b..daf5888 100644 --- a/src/Shaders/Shader.frag +++ b/src/Shaders/Shader.frag
@@ -1,37 +1,37 @@ -// -// Copyright (c) 2017-2021 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. -// - -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(location = 0) in vec3 inColor; -layout(location = 1) in vec2 inTexCoord; - -layout(location = 0) out vec4 outColor; - -layout(binding = 1) uniform sampler2D texSampler; - -void main() -{ - outColor = texture(texSampler, inTexCoord); - outColor.rgb *= inColor; -} +// +// Copyright (c) 2017-2021 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. +// + +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 inColor; +layout(location = 1) in vec2 inTexCoord; + +layout(location = 0) out vec4 outColor; + +layout(binding = 1) uniform sampler2D texSampler; + +void main() +{ + outColor = texture(texSampler, inTexCoord); + outColor.rgb *= inColor; +}
diff --git a/src/Shaders/Shader.vert b/src/Shaders/Shader.vert index 7ef5ddb..7652943 100644 --- a/src/Shaders/Shader.vert +++ b/src/Shaders/Shader.vert
@@ -1,42 +1,42 @@ -// -// Copyright (c) 2017-2021 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. -// - -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(push_constant) uniform UniformBufferObject -{ - mat4 ModelViewProj; -} ubo; - -layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inColor; -layout(location = 2) in vec2 inTexCoord; - -layout(location = 0) out vec3 outColor; -layout(location = 1) out vec2 outTexCoord; - -void main() { - gl_Position = ubo.ModelViewProj * vec4(inPosition, 1.0); - outColor = inColor; - outTexCoord = inTexCoord; -} +// +// Copyright (c) 2017-2021 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. +// + +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(push_constant) uniform UniformBufferObject +{ + mat4 ModelViewProj; +} ubo; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; + +layout(location = 0) out vec3 outColor; +layout(location = 1) out vec2 outTexCoord; + +void main() { + gl_Position = ubo.ModelViewProj * vec4(inPosition, 1.0); + outColor = inColor; + outTexCoord = inTexCoord; +}
diff --git a/src/Shaders/SparseBindingTest.comp b/src/Shaders/SparseBindingTest.comp index b94027d..1e7d63b 100644 --- a/src/Shaders/SparseBindingTest.comp +++ b/src/Shaders/SparseBindingTest.comp
@@ -1,44 +1,44 @@ -// -// Copyright (c) 2018-2021 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. -// - -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(local_size_x=1, local_size_y=1, local_size_z=1) in; - -layout(binding=0) uniform sampler2D img; -layout(binding=1) buffer buf -{ - uint bufValues[]; -}; - -void main() -{ - ivec2 xy = ivec2(bufValues[gl_GlobalInvocationID.x * 3], - bufValues[gl_GlobalInvocationID.x * 3 + 1]); - vec4 color = texture(img, xy); - bufValues[gl_GlobalInvocationID.x * 3 + 2] = - uint(color.r * 255.0) << 24 | - uint(color.g * 255.0) << 16 | - uint(color.b * 255.0) << 8 | - uint(color.a * 255.0); -} +// +// Copyright (c) 2018-2021 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. +// + +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(local_size_x=1, local_size_y=1, local_size_z=1) in; + +layout(binding=0) uniform sampler2D img; +layout(binding=1) buffer buf +{ + uint bufValues[]; +}; + +void main() +{ + ivec2 xy = ivec2(bufValues[gl_GlobalInvocationID.x * 3], + bufValues[gl_GlobalInvocationID.x * 3 + 1]); + vec4 color = texture(img, xy); + bufValues[gl_GlobalInvocationID.x * 3 + 2] = + uint(color.r * 255.0) << 24 | + uint(color.g * 255.0) << 16 | + uint(color.b * 255.0) << 8 | + uint(color.a * 255.0); +}
diff --git a/src/SparseBindingTest.cpp b/src/SparseBindingTest.cpp index b56a048..0430a1b 100644 --- a/src/SparseBindingTest.cpp +++ b/src/SparseBindingTest.cpp
@@ -1,597 +1,597 @@ -// -// Copyright (c) 2017-2021 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 "Common.h" -#include "SparseBindingTest.h" - -#ifdef _WIN32 - -//////////////////////////////////////////////////////////////////////////////// -// External imports - -extern VkDevice g_hDevice; -extern VmaAllocator g_hAllocator; -extern uint32_t g_FrameIndex; -extern bool g_SparseBindingEnabled; -extern VkQueue g_hSparseBindingQueue; -extern VkFence g_ImmediateFence; -extern VkCommandBuffer g_hTemporaryCommandBuffer; - -void BeginSingleTimeCommands(); -void EndSingleTimeCommands(); -void SaveAllocatorStatsToFile(const wchar_t* filePath); -void LoadShader(std::vector<char>& out, const char* fileName); - -//////////////////////////////////////////////////////////////////////////////// -// Class definitions - -static uint32_t CalculateMipMapCount(uint32_t width, uint32_t height, uint32_t depth) -{ - uint32_t mipMapCount = 1; - while(width > 1 || height > 1 || depth > 1) - { - ++mipMapCount; - width /= 2; - height /= 2; - depth /= 2; - } - return mipMapCount; -} - -class BaseImage -{ -public: - virtual void Init(RandomNumberGenerator& rand) = 0; - virtual ~BaseImage(); - - const VkImageCreateInfo& GetCreateInfo() const { return m_CreateInfo; } - - void TestContent(RandomNumberGenerator& rand); - -protected: - VkImageCreateInfo m_CreateInfo = {}; - VkImage m_Image = VK_NULL_HANDLE; - - void FillImageCreateInfo(RandomNumberGenerator& rand); - void UploadContent(); - void ValidateContent(RandomNumberGenerator& rand); -}; - -class TraditionalImage : public BaseImage -{ -public: - virtual void Init(RandomNumberGenerator& rand); - virtual ~TraditionalImage(); - -private: - VmaAllocation m_Allocation = VK_NULL_HANDLE; -}; - -class SparseBindingImage : public BaseImage -{ -public: - virtual void Init(RandomNumberGenerator& rand); - virtual ~SparseBindingImage(); - -private: - std::vector<VmaAllocation> m_Allocations; -}; - -//////////////////////////////////////////////////////////////////////////////// -// class BaseImage - -BaseImage::~BaseImage() -{ - if(m_Image) - { - vkDestroyImage(g_hDevice, m_Image, nullptr); - } -} - -void BaseImage::TestContent(RandomNumberGenerator& rand) -{ - printf("Validating content of %u x %u texture...\n", - m_CreateInfo.extent.width, m_CreateInfo.extent.height); - UploadContent(); - ValidateContent(rand); -} - -void BaseImage::FillImageCreateInfo(RandomNumberGenerator& rand) -{ - constexpr uint32_t imageSizeMin = 8; - constexpr uint32_t imageSizeMax = 2048; - - const bool useMipMaps = rand.Generate() % 2 != 0; - - ZeroMemory(&m_CreateInfo, sizeof(m_CreateInfo)); - m_CreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - m_CreateInfo.imageType = VK_IMAGE_TYPE_2D; - m_CreateInfo.extent.width = rand.Generate() % (imageSizeMax - imageSizeMin) + imageSizeMin; - m_CreateInfo.extent.height = rand.Generate() % (imageSizeMax - imageSizeMin) + imageSizeMin; - m_CreateInfo.extent.depth = 1; - m_CreateInfo.mipLevels = useMipMaps ? - CalculateMipMapCount(m_CreateInfo.extent.width, m_CreateInfo.extent.height, m_CreateInfo.extent.depth) : 1; - m_CreateInfo.arrayLayers = 1; - m_CreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - m_CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - m_CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - m_CreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; - m_CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; - m_CreateInfo.flags = 0; -} - -void BaseImage::UploadContent() -{ - VkBufferCreateInfo srcBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - srcBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - srcBufCreateInfo.size = 4 * m_CreateInfo.extent.width * m_CreateInfo.extent.height; - - VmaAllocationCreateInfo srcBufAllocCreateInfo = {}; - srcBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - srcBufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - - VkBuffer srcBuf = nullptr; - VmaAllocation srcBufAlloc = nullptr; - VmaAllocationInfo srcAllocInfo = {}; - TEST( vmaCreateBuffer(g_hAllocator, &srcBufCreateInfo, &srcBufAllocCreateInfo, &srcBuf, &srcBufAlloc, &srcAllocInfo) == VK_SUCCESS ); - - // Fill texels with: r = x % 255, g = u % 255, b = 13, a = 25 - uint32_t* srcBufPtr = (uint32_t*)srcAllocInfo.pMappedData; - for(uint32_t y = 0, sizeY = m_CreateInfo.extent.height; y < sizeY; ++y) - { - for(uint32_t x = 0, sizeX = m_CreateInfo.extent.width; x < sizeX; ++x, ++srcBufPtr) - { - const uint8_t r = (uint8_t)x; - const uint8_t g = (uint8_t)y; - const uint8_t b = 13; - const uint8_t a = 25; - *srcBufPtr = (uint32_t)r << 24 | (uint32_t)g << 16 | - (uint32_t)b << 8 | (uint32_t)a; - } - } - - BeginSingleTimeCommands(); - - // Barrier undefined to transfer dst. - { - VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = m_Image; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.layerCount = 1; - barrier.subresourceRange.levelCount = 1; - - vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // srcStageMask - VK_PIPELINE_STAGE_TRANSFER_BIT, // dstStageMask - 0, // dependencyFlags - 0, nullptr, // memoryBarriers - 0, nullptr, // bufferMemoryBarriers - 1, &barrier); // imageMemoryBarriers - } - - // CopyBufferToImage - { - VkBufferImageCopy region = {}; - region.bufferOffset = 0; - region.bufferRowLength = 0; // Zeros mean tightly packed. - region.bufferImageHeight = 0; // Zeros mean tightly packed. - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.mipLevel = 0; - region.imageSubresource.baseArrayLayer = 0; - region.imageSubresource.layerCount = 1; - region.imageOffset = { 0, 0, 0 }; - region.imageExtent = m_CreateInfo.extent; - vkCmdCopyBufferToImage(g_hTemporaryCommandBuffer, srcBuf, m_Image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); - } - - // Barrier transfer dst to fragment shader read only. - { - VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = m_Image; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.layerCount = 1; - barrier.subresourceRange.levelCount = 1; - - vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, - VK_PIPELINE_STAGE_TRANSFER_BIT, // srcStageMask - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, // dstStageMask - 0, // dependencyFlags - 0, nullptr, // memoryBarriers - 0, nullptr, // bufferMemoryBarriers - 1, &barrier); // imageMemoryBarriers - } - - EndSingleTimeCommands(); - - vmaDestroyBuffer(g_hAllocator, srcBuf, srcBufAlloc); -} - -void BaseImage::ValidateContent(RandomNumberGenerator& rand) -{ - /* - dstBuf has following layout: - For each of texels to be sampled, [0..valueCount): - struct { - in uint32_t pixelX; - in uint32_t pixelY; - out uint32_t pixelColor; - } - */ - - const uint32_t valueCount = 128; - - VkBufferCreateInfo dstBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - dstBufCreateInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; - dstBufCreateInfo.size = valueCount * sizeof(uint32_t) * 3; - - VmaAllocationCreateInfo dstBufAllocCreateInfo = {}; - dstBufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - dstBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; - - VkBuffer dstBuf = nullptr; - VmaAllocation dstBufAlloc = nullptr; - VmaAllocationInfo dstBufAllocInfo = {}; - TEST( vmaCreateBuffer(g_hAllocator, &dstBufCreateInfo, &dstBufAllocCreateInfo, &dstBuf, &dstBufAlloc, &dstBufAllocInfo) == VK_SUCCESS ); - - // Fill dstBuf input data. - { - uint32_t* dstBufContent = (uint32_t*)dstBufAllocInfo.pMappedData; - for(uint32_t i = 0; i < valueCount; ++i) - { - const uint32_t x = rand.Generate() % m_CreateInfo.extent.width; - const uint32_t y = rand.Generate() % m_CreateInfo.extent.height; - dstBufContent[i * 3 ] = x; - dstBufContent[i * 3 + 1] = y; - dstBufContent[i * 3 + 2] = 0; - } - } - - VkSamplerCreateInfo samplerCreateInfo = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; - samplerCreateInfo.magFilter = VK_FILTER_NEAREST; - samplerCreateInfo.minFilter = VK_FILTER_NEAREST; - samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; - samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerCreateInfo.unnormalizedCoordinates = VK_TRUE; - - VkSampler sampler = nullptr; - TEST( vkCreateSampler( g_hDevice, &samplerCreateInfo, nullptr, &sampler) == VK_SUCCESS ); - - VkDescriptorSetLayoutBinding bindings[2] = {}; - bindings[0].binding = 0; - bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - bindings[0].descriptorCount = 1; - bindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; - bindings[0].pImmutableSamplers = &sampler; - bindings[1].binding = 1; - bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - bindings[1].descriptorCount = 1; - bindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; - - VkDescriptorSetLayoutCreateInfo descSetLayoutCreateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; - descSetLayoutCreateInfo.bindingCount = 2; - descSetLayoutCreateInfo.pBindings = bindings; - - VkDescriptorSetLayout descSetLayout = nullptr; - TEST( vkCreateDescriptorSetLayout(g_hDevice, &descSetLayoutCreateInfo, nullptr, &descSetLayout) == VK_SUCCESS ); - - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; - pipelineLayoutCreateInfo.setLayoutCount = 1; - pipelineLayoutCreateInfo.pSetLayouts = &descSetLayout; - - VkPipelineLayout pipelineLayout = nullptr; - TEST( vkCreatePipelineLayout(g_hDevice, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout) == VK_SUCCESS ); - - std::vector<char> shaderCode; - LoadShader(shaderCode, "SparseBindingTest.comp.spv"); - - VkShaderModuleCreateInfo shaderModuleCreateInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; - shaderModuleCreateInfo.codeSize = shaderCode.size(); - shaderModuleCreateInfo.pCode = (const uint32_t*)shaderCode.data(); - - VkShaderModule shaderModule = nullptr; - TEST( vkCreateShaderModule(g_hDevice, &shaderModuleCreateInfo, nullptr, &shaderModule) == VK_SUCCESS ); - - VkComputePipelineCreateInfo pipelineCreateInfo = { VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO }; - pipelineCreateInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - pipelineCreateInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; - pipelineCreateInfo.stage.module = shaderModule; - pipelineCreateInfo.stage.pName = "main"; - pipelineCreateInfo.layout = pipelineLayout; - - VkPipeline pipeline = nullptr; - TEST( vkCreateComputePipelines(g_hDevice, nullptr, 1, &pipelineCreateInfo, nullptr, &pipeline) == VK_SUCCESS ); - - VkDescriptorPoolSize poolSizes[2] = {}; - poolSizes[0].type = bindings[0].descriptorType; - poolSizes[0].descriptorCount = bindings[0].descriptorCount; - poolSizes[1].type = bindings[1].descriptorType; - poolSizes[1].descriptorCount = bindings[1].descriptorCount; - - VkDescriptorPoolCreateInfo descPoolCreateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; - descPoolCreateInfo.maxSets = 1; - descPoolCreateInfo.poolSizeCount = 2; - descPoolCreateInfo.pPoolSizes = poolSizes; - - VkDescriptorPool descPool = nullptr; - TEST( vkCreateDescriptorPool(g_hDevice, &descPoolCreateInfo, nullptr, &descPool) == VK_SUCCESS ); - - VkDescriptorSetAllocateInfo descSetAllocInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; - descSetAllocInfo.descriptorPool = descPool; - descSetAllocInfo.descriptorSetCount = 1; - descSetAllocInfo.pSetLayouts = &descSetLayout; - - VkDescriptorSet descSet = nullptr; - TEST( vkAllocateDescriptorSets(g_hDevice, &descSetAllocInfo, &descSet) == VK_SUCCESS ); - - VkImageViewCreateInfo imageViewCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; - imageViewCreateInfo.image = m_Image; - imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - imageViewCreateInfo.format = m_CreateInfo.format; - imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - imageViewCreateInfo.subresourceRange.layerCount = 1; - imageViewCreateInfo.subresourceRange.levelCount = 1; - - VkImageView imageView = nullptr; - TEST( vkCreateImageView(g_hDevice, &imageViewCreateInfo, nullptr, &imageView) == VK_SUCCESS ); - - VkDescriptorImageInfo descImageInfo = {}; - descImageInfo.imageView = imageView; - descImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - - VkDescriptorBufferInfo descBufferInfo = {}; - descBufferInfo.buffer = dstBuf; - descBufferInfo.offset = 0; - descBufferInfo.range = VK_WHOLE_SIZE; - - VkWriteDescriptorSet descWrites[2] = {}; - descWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descWrites[0].dstSet = descSet; - descWrites[0].dstBinding = bindings[0].binding; - descWrites[0].dstArrayElement = 0; - descWrites[0].descriptorCount = 1; - descWrites[0].descriptorType = bindings[0].descriptorType; - descWrites[0].pImageInfo = &descImageInfo; - descWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descWrites[1].dstSet = descSet; - descWrites[1].dstBinding = bindings[1].binding; - descWrites[1].dstArrayElement = 0; - descWrites[1].descriptorCount = 1; - descWrites[1].descriptorType = bindings[1].descriptorType; - descWrites[1].pBufferInfo = &descBufferInfo; - vkUpdateDescriptorSets(g_hDevice, 2, descWrites, 0, nullptr); - - BeginSingleTimeCommands(); - vkCmdBindPipeline(g_hTemporaryCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); - vkCmdBindDescriptorSets(g_hTemporaryCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descSet, 0, nullptr); - vkCmdDispatch(g_hTemporaryCommandBuffer, valueCount, 1, 1); - EndSingleTimeCommands(); - - // Validate dstBuf output data. - { - const uint32_t* dstBufContent = (const uint32_t*)dstBufAllocInfo.pMappedData; - for(uint32_t i = 0; i < valueCount; ++i) - { - const uint32_t x = dstBufContent[i * 3 ]; - const uint32_t y = dstBufContent[i * 3 + 1]; - const uint32_t color = dstBufContent[i * 3 + 2]; - const uint8_t a = (uint8_t)(color >> 24); - const uint8_t b = (uint8_t)(color >> 16); - const uint8_t g = (uint8_t)(color >> 8); - const uint8_t r = (uint8_t)color; - TEST(r == (uint8_t)x && g == (uint8_t)y && b == 13 && a == 25); - } - } - - vkDestroyImageView(g_hDevice, imageView, nullptr); - vkDestroyDescriptorPool(g_hDevice, descPool, nullptr); - vmaDestroyBuffer(g_hAllocator, dstBuf, dstBufAlloc); - vkDestroyPipeline(g_hDevice, pipeline, nullptr); - vkDestroyShaderModule(g_hDevice, shaderModule, nullptr); - vkDestroyPipelineLayout(g_hDevice, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(g_hDevice, descSetLayout, nullptr); - vkDestroySampler(g_hDevice, sampler, nullptr); -} - -//////////////////////////////////////////////////////////////////////////////// -// class TraditionalImage - -void TraditionalImage::Init(RandomNumberGenerator& rand) -{ - FillImageCreateInfo(rand); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - // Default BEST_FIT is clearly better. - //allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT; - - ERR_GUARD_VULKAN( vmaCreateImage(g_hAllocator, &m_CreateInfo, &allocCreateInfo, - &m_Image, &m_Allocation, nullptr) ); -} - -TraditionalImage::~TraditionalImage() -{ - if(m_Allocation) - { - vmaFreeMemory(g_hAllocator, m_Allocation); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// class SparseBindingImage - -void SparseBindingImage::Init(RandomNumberGenerator& rand) -{ - assert(g_SparseBindingEnabled && g_hSparseBindingQueue); - - // Create image. - FillImageCreateInfo(rand); - m_CreateInfo.flags |= VK_IMAGE_CREATE_SPARSE_BINDING_BIT; - ERR_GUARD_VULKAN( vkCreateImage(g_hDevice, &m_CreateInfo, nullptr, &m_Image) ); - - // Get memory requirements. - VkMemoryRequirements imageMemReq; - vkGetImageMemoryRequirements(g_hDevice, m_Image, &imageMemReq); - - // This is just to silence validation layer warning. - // But it doesn't help. Looks like a bug in Vulkan validation layers. - // See: https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/364 - uint32_t sparseMemReqCount = 0; - vkGetImageSparseMemoryRequirements(g_hDevice, m_Image, &sparseMemReqCount, nullptr); - TEST(sparseMemReqCount <= 8); - VkSparseImageMemoryRequirements sparseMemReq[8]; - vkGetImageSparseMemoryRequirements(g_hDevice, m_Image, &sparseMemReqCount, sparseMemReq); - - // According to Vulkan specification, for sparse resources memReq.alignment is also page size. - const VkDeviceSize pageSize = imageMemReq.alignment; - const uint32_t pageCount = (uint32_t)ceil_div<VkDeviceSize>(imageMemReq.size, pageSize); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - VkMemoryRequirements pageMemReq = imageMemReq; - pageMemReq.size = pageSize; - - // Allocate and bind memory pages. - m_Allocations.resize(pageCount); - std::fill(m_Allocations.begin(), m_Allocations.end(), nullptr); - std::vector<VkSparseMemoryBind> binds{pageCount}; - std::vector<VmaAllocationInfo> allocInfo{pageCount}; - ERR_GUARD_VULKAN( vmaAllocateMemoryPages(g_hAllocator, &pageMemReq, &allocCreateInfo, pageCount, m_Allocations.data(), allocInfo.data()) ); - - for(uint32_t i = 0; i < pageCount; ++i) - { - binds[i] = {}; - binds[i].resourceOffset = pageSize * i; - binds[i].size = pageSize; - binds[i].memory = allocInfo[i].deviceMemory; - binds[i].memoryOffset = allocInfo[i].offset; - } - - VkSparseImageOpaqueMemoryBindInfo imageBindInfo; - imageBindInfo.image = m_Image; - imageBindInfo.bindCount = pageCount; - imageBindInfo.pBinds = binds.data(); - - VkBindSparseInfo bindSparseInfo = { VK_STRUCTURE_TYPE_BIND_SPARSE_INFO }; - bindSparseInfo.pImageOpaqueBinds = &imageBindInfo; - bindSparseInfo.imageOpaqueBindCount = 1; - - ERR_GUARD_VULKAN( vkResetFences(g_hDevice, 1, &g_ImmediateFence) ); - ERR_GUARD_VULKAN( vkQueueBindSparse(g_hSparseBindingQueue, 1, &bindSparseInfo, g_ImmediateFence) ); - ERR_GUARD_VULKAN( vkWaitForFences(g_hDevice, 1, &g_ImmediateFence, VK_TRUE, UINT64_MAX) ); -} - -SparseBindingImage::~SparseBindingImage() -{ - vmaFreeMemoryPages(g_hAllocator, m_Allocations.size(), m_Allocations.data()); -} - -//////////////////////////////////////////////////////////////////////////////// -// Private functions - -//////////////////////////////////////////////////////////////////////////////// -// Public functions - -void TestSparseBinding() -{ - wprintf(L"TESTING SPARSE BINDING:\n"); - - struct ImageInfo - { - std::unique_ptr<BaseImage> image; - uint32_t endFrame; - }; - std::vector<ImageInfo> images; - - constexpr uint32_t frameCount = 1000; - constexpr uint32_t imageLifeFramesMin = 1; - constexpr uint32_t imageLifeFramesMax = 400; - - RandomNumberGenerator rand(4652467); - - for(uint32_t frameIndex = 0; frameIndex < frameCount; ++frameIndex) - { - // Bump frame index. - ++g_FrameIndex; - vmaSetCurrentFrameIndex(g_hAllocator, g_FrameIndex); - - // Create one new, random image. - ImageInfo imageInfo; - //imageInfo.image = std::make_unique<TraditionalImage>(); - imageInfo.image = std::make_unique<SparseBindingImage>(); - imageInfo.image->Init(rand); - imageInfo.endFrame = g_FrameIndex + rand.Generate() % (imageLifeFramesMax - imageLifeFramesMin) + imageLifeFramesMin; - images.push_back(std::move(imageInfo)); - - // Delete all images that expired. - for(size_t imageIndex = images.size(); imageIndex--; ) - { - if(g_FrameIndex >= images[imageIndex].endFrame) - { - images.erase(images.begin() + imageIndex); - } - } - } - - SaveAllocatorStatsToFile(L"SparseBindingTest.json"); - - // Choose biggest image. Test uploading and sampling. - BaseImage* biggestImage = nullptr; - for(size_t i = 0, count = images.size(); i < count; ++i) - { - if(!biggestImage || - images[i].image->GetCreateInfo().extent.width * images[i].image->GetCreateInfo().extent.height > - biggestImage->GetCreateInfo().extent.width * biggestImage->GetCreateInfo().extent.height) - { - biggestImage = images[i].image.get(); - } - } - assert(biggestImage); - - biggestImage->TestContent(rand); - - // Free remaining images. - images.clear(); - - wprintf(L"Done.\n"); -} - -#endif // #ifdef _WIN32 +// +// Copyright (c) 2017-2021 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 "Common.h" +#include "SparseBindingTest.h" + +#ifdef _WIN32 + +//////////////////////////////////////////////////////////////////////////////// +// External imports + +extern VkDevice g_hDevice; +extern VmaAllocator g_hAllocator; +extern uint32_t g_FrameIndex; +extern bool g_SparseBindingEnabled; +extern VkQueue g_hSparseBindingQueue; +extern VkFence g_ImmediateFence; +extern VkCommandBuffer g_hTemporaryCommandBuffer; + +void BeginSingleTimeCommands(); +void EndSingleTimeCommands(); +void SaveAllocatorStatsToFile(const wchar_t* filePath); +void LoadShader(std::vector<char>& out, const char* fileName); + +//////////////////////////////////////////////////////////////////////////////// +// Class definitions + +static uint32_t CalculateMipMapCount(uint32_t width, uint32_t height, uint32_t depth) +{ + uint32_t mipMapCount = 1; + while(width > 1 || height > 1 || depth > 1) + { + ++mipMapCount; + width /= 2; + height /= 2; + depth /= 2; + } + return mipMapCount; +} + +class BaseImage +{ +public: + virtual void Init(RandomNumberGenerator& rand) = 0; + virtual ~BaseImage(); + + const VkImageCreateInfo& GetCreateInfo() const { return m_CreateInfo; } + + void TestContent(RandomNumberGenerator& rand); + +protected: + VkImageCreateInfo m_CreateInfo = {}; + VkImage m_Image = VK_NULL_HANDLE; + + void FillImageCreateInfo(RandomNumberGenerator& rand); + void UploadContent(); + void ValidateContent(RandomNumberGenerator& rand); +}; + +class TraditionalImage : public BaseImage +{ +public: + virtual void Init(RandomNumberGenerator& rand); + virtual ~TraditionalImage(); + +private: + VmaAllocation m_Allocation = VK_NULL_HANDLE; +}; + +class SparseBindingImage : public BaseImage +{ +public: + virtual void Init(RandomNumberGenerator& rand); + virtual ~SparseBindingImage(); + +private: + std::vector<VmaAllocation> m_Allocations; +}; + +//////////////////////////////////////////////////////////////////////////////// +// class BaseImage + +BaseImage::~BaseImage() +{ + if(m_Image) + { + vkDestroyImage(g_hDevice, m_Image, nullptr); + } +} + +void BaseImage::TestContent(RandomNumberGenerator& rand) +{ + printf("Validating content of %u x %u texture...\n", + m_CreateInfo.extent.width, m_CreateInfo.extent.height); + UploadContent(); + ValidateContent(rand); +} + +void BaseImage::FillImageCreateInfo(RandomNumberGenerator& rand) +{ + constexpr uint32_t imageSizeMin = 8; + constexpr uint32_t imageSizeMax = 2048; + + const bool useMipMaps = rand.Generate() % 2 != 0; + + ZeroMemory(&m_CreateInfo, sizeof(m_CreateInfo)); + m_CreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + m_CreateInfo.imageType = VK_IMAGE_TYPE_2D; + m_CreateInfo.extent.width = rand.Generate() % (imageSizeMax - imageSizeMin) + imageSizeMin; + m_CreateInfo.extent.height = rand.Generate() % (imageSizeMax - imageSizeMin) + imageSizeMin; + m_CreateInfo.extent.depth = 1; + m_CreateInfo.mipLevels = useMipMaps ? + CalculateMipMapCount(m_CreateInfo.extent.width, m_CreateInfo.extent.height, m_CreateInfo.extent.depth) : 1; + m_CreateInfo.arrayLayers = 1; + m_CreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + m_CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + m_CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + m_CreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + m_CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + m_CreateInfo.flags = 0; +} + +void BaseImage::UploadContent() +{ + VkBufferCreateInfo srcBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + srcBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + srcBufCreateInfo.size = 4 * m_CreateInfo.extent.width * m_CreateInfo.extent.height; + + VmaAllocationCreateInfo srcBufAllocCreateInfo = {}; + srcBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + srcBufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VkBuffer srcBuf = nullptr; + VmaAllocation srcBufAlloc = nullptr; + VmaAllocationInfo srcAllocInfo = {}; + TEST( vmaCreateBuffer(g_hAllocator, &srcBufCreateInfo, &srcBufAllocCreateInfo, &srcBuf, &srcBufAlloc, &srcAllocInfo) == VK_SUCCESS ); + + // Fill texels with: r = x % 255, g = u % 255, b = 13, a = 25 + uint32_t* srcBufPtr = (uint32_t*)srcAllocInfo.pMappedData; + for(uint32_t y = 0, sizeY = m_CreateInfo.extent.height; y < sizeY; ++y) + { + for(uint32_t x = 0, sizeX = m_CreateInfo.extent.width; x < sizeX; ++x, ++srcBufPtr) + { + const uint8_t r = (uint8_t)x; + const uint8_t g = (uint8_t)y; + const uint8_t b = 13; + const uint8_t a = 25; + *srcBufPtr = (uint32_t)r << 24 | (uint32_t)g << 16 | + (uint32_t)b << 8 | (uint32_t)a; + } + } + + BeginSingleTimeCommands(); + + // Barrier undefined to transfer dst. + { + VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = m_Image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // srcStageMask + VK_PIPELINE_STAGE_TRANSFER_BIT, // dstStageMask + 0, // dependencyFlags + 0, nullptr, // memoryBarriers + 0, nullptr, // bufferMemoryBarriers + 1, &barrier); // imageMemoryBarriers + } + + // CopyBufferToImage + { + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; // Zeros mean tightly packed. + region.bufferImageHeight = 0; // Zeros mean tightly packed. + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = { 0, 0, 0 }; + region.imageExtent = m_CreateInfo.extent; + vkCmdCopyBufferToImage(g_hTemporaryCommandBuffer, srcBuf, m_Image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + } + + // Barrier transfer dst to fragment shader read only. + { + VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = m_Image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, // srcStageMask + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, // dstStageMask + 0, // dependencyFlags + 0, nullptr, // memoryBarriers + 0, nullptr, // bufferMemoryBarriers + 1, &barrier); // imageMemoryBarriers + } + + EndSingleTimeCommands(); + + vmaDestroyBuffer(g_hAllocator, srcBuf, srcBufAlloc); +} + +void BaseImage::ValidateContent(RandomNumberGenerator& rand) +{ + /* + dstBuf has following layout: + For each of texels to be sampled, [0..valueCount): + struct { + in uint32_t pixelX; + in uint32_t pixelY; + out uint32_t pixelColor; + } + */ + + const uint32_t valueCount = 128; + + VkBufferCreateInfo dstBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + dstBufCreateInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + dstBufCreateInfo.size = valueCount * sizeof(uint32_t) * 3; + + VmaAllocationCreateInfo dstBufAllocCreateInfo = {}; + dstBufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + dstBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; + + VkBuffer dstBuf = nullptr; + VmaAllocation dstBufAlloc = nullptr; + VmaAllocationInfo dstBufAllocInfo = {}; + TEST( vmaCreateBuffer(g_hAllocator, &dstBufCreateInfo, &dstBufAllocCreateInfo, &dstBuf, &dstBufAlloc, &dstBufAllocInfo) == VK_SUCCESS ); + + // Fill dstBuf input data. + { + uint32_t* dstBufContent = (uint32_t*)dstBufAllocInfo.pMappedData; + for(uint32_t i = 0; i < valueCount; ++i) + { + const uint32_t x = rand.Generate() % m_CreateInfo.extent.width; + const uint32_t y = rand.Generate() % m_CreateInfo.extent.height; + dstBufContent[i * 3 ] = x; + dstBufContent[i * 3 + 1] = y; + dstBufContent[i * 3 + 2] = 0; + } + } + + VkSamplerCreateInfo samplerCreateInfo = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; + samplerCreateInfo.magFilter = VK_FILTER_NEAREST; + samplerCreateInfo.minFilter = VK_FILTER_NEAREST; + samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerCreateInfo.unnormalizedCoordinates = VK_TRUE; + + VkSampler sampler = nullptr; + TEST( vkCreateSampler( g_hDevice, &samplerCreateInfo, nullptr, &sampler) == VK_SUCCESS ); + + VkDescriptorSetLayoutBinding bindings[2] = {}; + bindings[0].binding = 0; + bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + bindings[0].descriptorCount = 1; + bindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bindings[0].pImmutableSamplers = &sampler; + bindings[1].binding = 1; + bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bindings[1].descriptorCount = 1; + bindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + + VkDescriptorSetLayoutCreateInfo descSetLayoutCreateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; + descSetLayoutCreateInfo.bindingCount = 2; + descSetLayoutCreateInfo.pBindings = bindings; + + VkDescriptorSetLayout descSetLayout = nullptr; + TEST( vkCreateDescriptorSetLayout(g_hDevice, &descSetLayoutCreateInfo, nullptr, &descSetLayout) == VK_SUCCESS ); + + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; + pipelineLayoutCreateInfo.setLayoutCount = 1; + pipelineLayoutCreateInfo.pSetLayouts = &descSetLayout; + + VkPipelineLayout pipelineLayout = nullptr; + TEST( vkCreatePipelineLayout(g_hDevice, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout) == VK_SUCCESS ); + + std::vector<char> shaderCode; + LoadShader(shaderCode, "SparseBindingTest.comp.spv"); + + VkShaderModuleCreateInfo shaderModuleCreateInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; + shaderModuleCreateInfo.codeSize = shaderCode.size(); + shaderModuleCreateInfo.pCode = (const uint32_t*)shaderCode.data(); + + VkShaderModule shaderModule = nullptr; + TEST( vkCreateShaderModule(g_hDevice, &shaderModuleCreateInfo, nullptr, &shaderModule) == VK_SUCCESS ); + + VkComputePipelineCreateInfo pipelineCreateInfo = { VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO }; + pipelineCreateInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + pipelineCreateInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + pipelineCreateInfo.stage.module = shaderModule; + pipelineCreateInfo.stage.pName = "main"; + pipelineCreateInfo.layout = pipelineLayout; + + VkPipeline pipeline = nullptr; + TEST( vkCreateComputePipelines(g_hDevice, nullptr, 1, &pipelineCreateInfo, nullptr, &pipeline) == VK_SUCCESS ); + + VkDescriptorPoolSize poolSizes[2] = {}; + poolSizes[0].type = bindings[0].descriptorType; + poolSizes[0].descriptorCount = bindings[0].descriptorCount; + poolSizes[1].type = bindings[1].descriptorType; + poolSizes[1].descriptorCount = bindings[1].descriptorCount; + + VkDescriptorPoolCreateInfo descPoolCreateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; + descPoolCreateInfo.maxSets = 1; + descPoolCreateInfo.poolSizeCount = 2; + descPoolCreateInfo.pPoolSizes = poolSizes; + + VkDescriptorPool descPool = nullptr; + TEST( vkCreateDescriptorPool(g_hDevice, &descPoolCreateInfo, nullptr, &descPool) == VK_SUCCESS ); + + VkDescriptorSetAllocateInfo descSetAllocInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; + descSetAllocInfo.descriptorPool = descPool; + descSetAllocInfo.descriptorSetCount = 1; + descSetAllocInfo.pSetLayouts = &descSetLayout; + + VkDescriptorSet descSet = nullptr; + TEST( vkAllocateDescriptorSets(g_hDevice, &descSetAllocInfo, &descSet) == VK_SUCCESS ); + + VkImageViewCreateInfo imageViewCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + imageViewCreateInfo.image = m_Image; + imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.format = m_CreateInfo.format; + imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewCreateInfo.subresourceRange.layerCount = 1; + imageViewCreateInfo.subresourceRange.levelCount = 1; + + VkImageView imageView = nullptr; + TEST( vkCreateImageView(g_hDevice, &imageViewCreateInfo, nullptr, &imageView) == VK_SUCCESS ); + + VkDescriptorImageInfo descImageInfo = {}; + descImageInfo.imageView = imageView; + descImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkDescriptorBufferInfo descBufferInfo = {}; + descBufferInfo.buffer = dstBuf; + descBufferInfo.offset = 0; + descBufferInfo.range = VK_WHOLE_SIZE; + + VkWriteDescriptorSet descWrites[2] = {}; + descWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descWrites[0].dstSet = descSet; + descWrites[0].dstBinding = bindings[0].binding; + descWrites[0].dstArrayElement = 0; + descWrites[0].descriptorCount = 1; + descWrites[0].descriptorType = bindings[0].descriptorType; + descWrites[0].pImageInfo = &descImageInfo; + descWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descWrites[1].dstSet = descSet; + descWrites[1].dstBinding = bindings[1].binding; + descWrites[1].dstArrayElement = 0; + descWrites[1].descriptorCount = 1; + descWrites[1].descriptorType = bindings[1].descriptorType; + descWrites[1].pBufferInfo = &descBufferInfo; + vkUpdateDescriptorSets(g_hDevice, 2, descWrites, 0, nullptr); + + BeginSingleTimeCommands(); + vkCmdBindPipeline(g_hTemporaryCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + vkCmdBindDescriptorSets(g_hTemporaryCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descSet, 0, nullptr); + vkCmdDispatch(g_hTemporaryCommandBuffer, valueCount, 1, 1); + EndSingleTimeCommands(); + + // Validate dstBuf output data. + { + const uint32_t* dstBufContent = (const uint32_t*)dstBufAllocInfo.pMappedData; + for(uint32_t i = 0; i < valueCount; ++i) + { + const uint32_t x = dstBufContent[i * 3 ]; + const uint32_t y = dstBufContent[i * 3 + 1]; + const uint32_t color = dstBufContent[i * 3 + 2]; + const uint8_t a = (uint8_t)(color >> 24); + const uint8_t b = (uint8_t)(color >> 16); + const uint8_t g = (uint8_t)(color >> 8); + const uint8_t r = (uint8_t)color; + TEST(r == (uint8_t)x && g == (uint8_t)y && b == 13 && a == 25); + } + } + + vkDestroyImageView(g_hDevice, imageView, nullptr); + vkDestroyDescriptorPool(g_hDevice, descPool, nullptr); + vmaDestroyBuffer(g_hAllocator, dstBuf, dstBufAlloc); + vkDestroyPipeline(g_hDevice, pipeline, nullptr); + vkDestroyShaderModule(g_hDevice, shaderModule, nullptr); + vkDestroyPipelineLayout(g_hDevice, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(g_hDevice, descSetLayout, nullptr); + vkDestroySampler(g_hDevice, sampler, nullptr); +} + +//////////////////////////////////////////////////////////////////////////////// +// class TraditionalImage + +void TraditionalImage::Init(RandomNumberGenerator& rand) +{ + FillImageCreateInfo(rand); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + // Default BEST_FIT is clearly better. + //allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT; + + ERR_GUARD_VULKAN( vmaCreateImage(g_hAllocator, &m_CreateInfo, &allocCreateInfo, + &m_Image, &m_Allocation, nullptr) ); +} + +TraditionalImage::~TraditionalImage() +{ + if(m_Allocation) + { + vmaFreeMemory(g_hAllocator, m_Allocation); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// class SparseBindingImage + +void SparseBindingImage::Init(RandomNumberGenerator& rand) +{ + assert(g_SparseBindingEnabled && g_hSparseBindingQueue); + + // Create image. + FillImageCreateInfo(rand); + m_CreateInfo.flags |= VK_IMAGE_CREATE_SPARSE_BINDING_BIT; + ERR_GUARD_VULKAN( vkCreateImage(g_hDevice, &m_CreateInfo, nullptr, &m_Image) ); + + // Get memory requirements. + VkMemoryRequirements imageMemReq; + vkGetImageMemoryRequirements(g_hDevice, m_Image, &imageMemReq); + + // This is just to silence validation layer warning. + // But it doesn't help. Looks like a bug in Vulkan validation layers. + // See: https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/364 + uint32_t sparseMemReqCount = 0; + vkGetImageSparseMemoryRequirements(g_hDevice, m_Image, &sparseMemReqCount, nullptr); + TEST(sparseMemReqCount <= 8); + VkSparseImageMemoryRequirements sparseMemReq[8]; + vkGetImageSparseMemoryRequirements(g_hDevice, m_Image, &sparseMemReqCount, sparseMemReq); + + // According to Vulkan specification, for sparse resources memReq.alignment is also page size. + const VkDeviceSize pageSize = imageMemReq.alignment; + const uint32_t pageCount = (uint32_t)ceil_div<VkDeviceSize>(imageMemReq.size, pageSize); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VkMemoryRequirements pageMemReq = imageMemReq; + pageMemReq.size = pageSize; + + // Allocate and bind memory pages. + m_Allocations.resize(pageCount); + std::fill(m_Allocations.begin(), m_Allocations.end(), nullptr); + std::vector<VkSparseMemoryBind> binds{pageCount}; + std::vector<VmaAllocationInfo> allocInfo{pageCount}; + ERR_GUARD_VULKAN( vmaAllocateMemoryPages(g_hAllocator, &pageMemReq, &allocCreateInfo, pageCount, m_Allocations.data(), allocInfo.data()) ); + + for(uint32_t i = 0; i < pageCount; ++i) + { + binds[i] = {}; + binds[i].resourceOffset = pageSize * i; + binds[i].size = pageSize; + binds[i].memory = allocInfo[i].deviceMemory; + binds[i].memoryOffset = allocInfo[i].offset; + } + + VkSparseImageOpaqueMemoryBindInfo imageBindInfo; + imageBindInfo.image = m_Image; + imageBindInfo.bindCount = pageCount; + imageBindInfo.pBinds = binds.data(); + + VkBindSparseInfo bindSparseInfo = { VK_STRUCTURE_TYPE_BIND_SPARSE_INFO }; + bindSparseInfo.pImageOpaqueBinds = &imageBindInfo; + bindSparseInfo.imageOpaqueBindCount = 1; + + ERR_GUARD_VULKAN( vkResetFences(g_hDevice, 1, &g_ImmediateFence) ); + ERR_GUARD_VULKAN( vkQueueBindSparse(g_hSparseBindingQueue, 1, &bindSparseInfo, g_ImmediateFence) ); + ERR_GUARD_VULKAN( vkWaitForFences(g_hDevice, 1, &g_ImmediateFence, VK_TRUE, UINT64_MAX) ); +} + +SparseBindingImage::~SparseBindingImage() +{ + vmaFreeMemoryPages(g_hAllocator, m_Allocations.size(), m_Allocations.data()); +} + +//////////////////////////////////////////////////////////////////////////////// +// Private functions + +//////////////////////////////////////////////////////////////////////////////// +// Public functions + +void TestSparseBinding() +{ + wprintf(L"TESTING SPARSE BINDING:\n"); + + struct ImageInfo + { + std::unique_ptr<BaseImage> image; + uint32_t endFrame; + }; + std::vector<ImageInfo> images; + + constexpr uint32_t frameCount = 1000; + constexpr uint32_t imageLifeFramesMin = 1; + constexpr uint32_t imageLifeFramesMax = 400; + + RandomNumberGenerator rand(4652467); + + for(uint32_t frameIndex = 0; frameIndex < frameCount; ++frameIndex) + { + // Bump frame index. + ++g_FrameIndex; + vmaSetCurrentFrameIndex(g_hAllocator, g_FrameIndex); + + // Create one new, random image. + ImageInfo imageInfo; + //imageInfo.image = std::make_unique<TraditionalImage>(); + imageInfo.image = std::make_unique<SparseBindingImage>(); + imageInfo.image->Init(rand); + imageInfo.endFrame = g_FrameIndex + rand.Generate() % (imageLifeFramesMax - imageLifeFramesMin) + imageLifeFramesMin; + images.push_back(std::move(imageInfo)); + + // Delete all images that expired. + for(size_t imageIndex = images.size(); imageIndex--; ) + { + if(g_FrameIndex >= images[imageIndex].endFrame) + { + images.erase(images.begin() + imageIndex); + } + } + } + + SaveAllocatorStatsToFile(L"SparseBindingTest.json"); + + // Choose biggest image. Test uploading and sampling. + BaseImage* biggestImage = nullptr; + for(size_t i = 0, count = images.size(); i < count; ++i) + { + if(!biggestImage || + images[i].image->GetCreateInfo().extent.width * images[i].image->GetCreateInfo().extent.height > + biggestImage->GetCreateInfo().extent.width * biggestImage->GetCreateInfo().extent.height) + { + biggestImage = images[i].image.get(); + } + } + assert(biggestImage); + + biggestImage->TestContent(rand); + + // Free remaining images. + images.clear(); + + wprintf(L"Done.\n"); +} + +#endif // #ifdef _WIN32
diff --git a/src/SparseBindingTest.h b/src/SparseBindingTest.h index 98087fa..33dbd35 100644 --- a/src/SparseBindingTest.h +++ b/src/SparseBindingTest.h
@@ -1,29 +1,29 @@ -// -// Copyright (c) 2017-2021 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. -// - -#pragma once - -#ifdef _WIN32 - -void TestSparseBinding(); - -#endif // #ifdef _WIN32 +// +// Copyright (c) 2017-2021 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. +// + +#pragma once + +#ifdef _WIN32 + +void TestSparseBinding(); + +#endif // #ifdef _WIN32
diff --git a/src/Tests.cpp b/src/Tests.cpp index 6a02548..f0852f5 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp
@@ -1,6639 +1,6639 @@ -// -// Copyright (c) 2017-2021 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 "Tests.h" -#include "VmaUsage.h" -#include "Common.h" -#include <atomic> -#include <thread> -#include <mutex> -#include <functional> - -#ifdef _WIN32 - -static const char* CODE_DESCRIPTION = "Foo"; - -extern VkCommandBuffer g_hTemporaryCommandBuffer; -extern const VkAllocationCallbacks* g_Allocs; -extern bool VK_KHR_buffer_device_address_enabled; -extern bool VK_EXT_memory_priority_enabled; -extern PFN_vkGetBufferDeviceAddressKHR g_vkGetBufferDeviceAddressKHR; -void BeginSingleTimeCommands(); -void EndSingleTimeCommands(); -void SetDebugUtilsObjectName(VkObjectType type, uint64_t handle, const char* name); - -#ifndef VMA_DEBUG_MARGIN - #define VMA_DEBUG_MARGIN 0 -#endif - -enum CONFIG_TYPE { - CONFIG_TYPE_MINIMUM, - CONFIG_TYPE_SMALL, - CONFIG_TYPE_AVERAGE, - CONFIG_TYPE_LARGE, - CONFIG_TYPE_MAXIMUM, - CONFIG_TYPE_COUNT -}; - -static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_SMALL; -//static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_LARGE; - -enum class FREE_ORDER { FORWARD, BACKWARD, RANDOM, COUNT }; - -static const char* FREE_ORDER_NAMES[] = { - "FORWARD", - "BACKWARD", - "RANDOM", -}; - -// Copy of internal VmaAlgorithmToStr. -static const char* AlgorithmToStr(uint32_t algorithm) -{ - switch(algorithm) - { - case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT: - return "Linear"; - case VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT: - return "Buddy"; - case 0: - return "Default"; - default: - assert(0); - return ""; - } -} - -struct AllocationSize -{ - uint32_t Probability; - VkDeviceSize BufferSizeMin, BufferSizeMax; - uint32_t ImageSizeMin, ImageSizeMax; -}; - -struct Config -{ - uint32_t RandSeed; - VkDeviceSize BeginBytesToAllocate; - uint32_t AdditionalOperationCount; - VkDeviceSize MaxBytesToAllocate; - uint32_t MemUsageProbability[4]; // For VMA_MEMORY_USAGE_* - std::vector<AllocationSize> AllocationSizes; - uint32_t ThreadCount; - uint32_t ThreadsUsingCommonAllocationsProbabilityPercent; - FREE_ORDER FreeOrder; - VmaAllocationCreateFlags AllocationStrategy; // For VMA_ALLOCATION_CREATE_STRATEGY_* -}; - -struct Result -{ - duration TotalTime; - duration AllocationTimeMin, AllocationTimeAvg, AllocationTimeMax; - duration DeallocationTimeMin, DeallocationTimeAvg, DeallocationTimeMax; - VkDeviceSize TotalMemoryAllocated; - VkDeviceSize FreeRangeSizeAvg, FreeRangeSizeMax; -}; - -void TestDefragmentationSimple(); -void TestDefragmentationFull(); - -struct PoolTestConfig -{ - uint32_t RandSeed; - uint32_t ThreadCount; - VkDeviceSize PoolSize; - uint32_t FrameCount; - uint32_t TotalItemCount; - // Range for number of items used in each frame. - uint32_t UsedItemCountMin, UsedItemCountMax; - // Percent of items to make unused, and possibly make some others used in each frame. - uint32_t ItemsToMakeUnusedPercent; - std::vector<AllocationSize> AllocationSizes; - - VkDeviceSize CalcAvgResourceSize() const - { - uint32_t probabilitySum = 0; - VkDeviceSize sizeSum = 0; - for(size_t i = 0; i < AllocationSizes.size(); ++i) - { - const AllocationSize& allocSize = AllocationSizes[i]; - if(allocSize.BufferSizeMax > 0) - sizeSum += (allocSize.BufferSizeMin + allocSize.BufferSizeMax) / 2 * allocSize.Probability; - else - { - const VkDeviceSize avgDimension = (allocSize.ImageSizeMin + allocSize.ImageSizeMax) / 2; - sizeSum += avgDimension * avgDimension * 4 * allocSize.Probability; - } - probabilitySum += allocSize.Probability; - } - return sizeSum / probabilitySum; - } - - bool UsesBuffers() const - { - for(size_t i = 0; i < AllocationSizes.size(); ++i) - if(AllocationSizes[i].BufferSizeMax > 0) - return true; - return false; - } - - bool UsesImages() const - { - for(size_t i = 0; i < AllocationSizes.size(); ++i) - if(AllocationSizes[i].ImageSizeMax > 0) - return true; - return false; - } -}; - -struct PoolTestResult -{ - duration TotalTime; - duration AllocationTimeMin, AllocationTimeAvg, AllocationTimeMax; - duration DeallocationTimeMin, DeallocationTimeAvg, DeallocationTimeMax; - size_t LostAllocationCount, LostAllocationTotalSize; - size_t FailedAllocationCount, FailedAllocationTotalSize; -}; - -static const uint32_t IMAGE_BYTES_PER_PIXEL = 1; - -uint32_t g_FrameIndex = 0; - -struct BufferInfo -{ - VkBuffer Buffer = VK_NULL_HANDLE; - VmaAllocation Allocation = VK_NULL_HANDLE; -}; - -static uint32_t MemoryTypeToHeap(uint32_t memoryTypeIndex) -{ - const VkPhysicalDeviceMemoryProperties* props; - vmaGetMemoryProperties(g_hAllocator, &props); - return props->memoryTypes[memoryTypeIndex].heapIndex; -} - -static uint32_t GetAllocationStrategyCount() -{ - uint32_t strategyCount = 0; - switch(ConfigType) - { - case CONFIG_TYPE_MINIMUM: strategyCount = 1; break; - case CONFIG_TYPE_SMALL: strategyCount = 1; break; - case CONFIG_TYPE_AVERAGE: strategyCount = 2; break; - case CONFIG_TYPE_LARGE: strategyCount = 2; break; - case CONFIG_TYPE_MAXIMUM: strategyCount = 3; break; - default: assert(0); - } - return strategyCount; -} - -static const char* GetAllocationStrategyName(VmaAllocationCreateFlags allocStrategy) -{ - switch(allocStrategy) - { - case VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT: return "BEST_FIT"; break; - case VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT: return "WORST_FIT"; break; - case VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT: return "FIRST_FIT"; break; - case 0: return "Default"; break; - default: assert(0); return ""; - } -} - -static void InitResult(Result& outResult) -{ - outResult.TotalTime = duration::zero(); - outResult.AllocationTimeMin = duration::max(); - outResult.AllocationTimeAvg = duration::zero(); - outResult.AllocationTimeMax = duration::min(); - outResult.DeallocationTimeMin = duration::max(); - outResult.DeallocationTimeAvg = duration::zero(); - outResult.DeallocationTimeMax = duration::min(); - outResult.TotalMemoryAllocated = 0; - outResult.FreeRangeSizeAvg = 0; - outResult.FreeRangeSizeMax = 0; -} - -class TimeRegisterObj -{ -public: - TimeRegisterObj(duration& min, duration& sum, duration& max) : - m_Min(min), - m_Sum(sum), - m_Max(max), - m_TimeBeg(std::chrono::high_resolution_clock::now()) - { - } - - ~TimeRegisterObj() - { - duration d = std::chrono::high_resolution_clock::now() - m_TimeBeg; - m_Sum += d; - if(d < m_Min) m_Min = d; - if(d > m_Max) m_Max = d; - } - -private: - duration& m_Min; - duration& m_Sum; - duration& m_Max; - time_point m_TimeBeg; -}; - -struct PoolTestThreadResult -{ - duration AllocationTimeMin, AllocationTimeSum, AllocationTimeMax; - duration DeallocationTimeMin, DeallocationTimeSum, DeallocationTimeMax; - size_t AllocationCount, DeallocationCount; - size_t LostAllocationCount, LostAllocationTotalSize; - size_t FailedAllocationCount, FailedAllocationTotalSize; -}; - -class AllocationTimeRegisterObj : public TimeRegisterObj -{ -public: - AllocationTimeRegisterObj(Result& result) : - TimeRegisterObj(result.AllocationTimeMin, result.AllocationTimeAvg, result.AllocationTimeMax) - { - } -}; - -class DeallocationTimeRegisterObj : public TimeRegisterObj -{ -public: - DeallocationTimeRegisterObj(Result& result) : - TimeRegisterObj(result.DeallocationTimeMin, result.DeallocationTimeAvg, result.DeallocationTimeMax) - { - } -}; - -class PoolAllocationTimeRegisterObj : public TimeRegisterObj -{ -public: - PoolAllocationTimeRegisterObj(PoolTestThreadResult& result) : - TimeRegisterObj(result.AllocationTimeMin, result.AllocationTimeSum, result.AllocationTimeMax) - { - } -}; - -class PoolDeallocationTimeRegisterObj : public TimeRegisterObj -{ -public: - PoolDeallocationTimeRegisterObj(PoolTestThreadResult& result) : - TimeRegisterObj(result.DeallocationTimeMin, result.DeallocationTimeSum, result.DeallocationTimeMax) - { - } -}; - -static void CurrentTimeToStr(std::string& out) -{ - time_t rawTime; time(&rawTime); - struct tm timeInfo; localtime_s(&timeInfo, &rawTime); - char timeStr[128]; - strftime(timeStr, _countof(timeStr), "%c", &timeInfo); - out = timeStr; -} - -VkResult MainTest(Result& outResult, const Config& config) -{ - assert(config.ThreadCount > 0); - - InitResult(outResult); - - RandomNumberGenerator mainRand{config.RandSeed}; - - time_point timeBeg = std::chrono::high_resolution_clock::now(); - - std::atomic<size_t> allocationCount = 0; - VkResult res = VK_SUCCESS; - - uint32_t memUsageProbabilitySum = - config.MemUsageProbability[0] + config.MemUsageProbability[1] + - config.MemUsageProbability[2] + config.MemUsageProbability[3]; - assert(memUsageProbabilitySum > 0); - - uint32_t allocationSizeProbabilitySum = std::accumulate( - config.AllocationSizes.begin(), - config.AllocationSizes.end(), - 0u, - [](uint32_t sum, const AllocationSize& allocSize) { - return sum + allocSize.Probability; - }); - - struct Allocation - { - VkBuffer Buffer; - VkImage Image; - VmaAllocation Alloc; - }; - - std::vector<Allocation> commonAllocations; - std::mutex commonAllocationsMutex; - - auto Allocate = [&]( - VkDeviceSize bufferSize, - const VkExtent2D imageExtent, - RandomNumberGenerator& localRand, - VkDeviceSize& totalAllocatedBytes, - std::vector<Allocation>& allocations) -> VkResult - { - assert((bufferSize == 0) != (imageExtent.width == 0 && imageExtent.height == 0)); - - uint32_t memUsageIndex = 0; - uint32_t memUsageRand = localRand.Generate() % memUsageProbabilitySum; - while(memUsageRand >= config.MemUsageProbability[memUsageIndex]) - memUsageRand -= config.MemUsageProbability[memUsageIndex++]; - - VmaAllocationCreateInfo memReq = {}; - memReq.usage = (VmaMemoryUsage)(VMA_MEMORY_USAGE_GPU_ONLY + memUsageIndex); - memReq.flags |= config.AllocationStrategy; - - Allocation allocation = {}; - VmaAllocationInfo allocationInfo; - - // Buffer - if(bufferSize > 0) - { - assert(imageExtent.width == 0); - VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufferInfo.size = bufferSize; - bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - - { - AllocationTimeRegisterObj timeRegisterObj{outResult}; - res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &memReq, &allocation.Buffer, &allocation.Alloc, &allocationInfo); - } - } - // Image - else - { - VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.width = imageExtent.width; - imageInfo.extent.height = imageExtent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imageInfo.tiling = memReq.usage == VMA_MEMORY_USAGE_GPU_ONLY ? - VK_IMAGE_TILING_OPTIMAL : - VK_IMAGE_TILING_LINEAR; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; - switch(memReq.usage) - { - case VMA_MEMORY_USAGE_GPU_ONLY: - switch(localRand.Generate() % 3) - { - case 0: - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - break; - case 1: - imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - break; - case 2: - imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - break; - } - break; - case VMA_MEMORY_USAGE_CPU_ONLY: - case VMA_MEMORY_USAGE_CPU_TO_GPU: - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - break; - case VMA_MEMORY_USAGE_GPU_TO_CPU: - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; - break; - } - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.flags = 0; - - { - AllocationTimeRegisterObj timeRegisterObj{outResult}; - res = vmaCreateImage(g_hAllocator, &imageInfo, &memReq, &allocation.Image, &allocation.Alloc, &allocationInfo); - } - } - - if(res == VK_SUCCESS) - { - ++allocationCount; - totalAllocatedBytes += allocationInfo.size; - bool useCommonAllocations = localRand.Generate() % 100 < config.ThreadsUsingCommonAllocationsProbabilityPercent; - if(useCommonAllocations) - { - std::unique_lock<std::mutex> lock(commonAllocationsMutex); - commonAllocations.push_back(allocation); - } - else - allocations.push_back(allocation); - } - else - { - TEST(0); - } - return res; - }; - - auto GetNextAllocationSize = [&]( - VkDeviceSize& outBufSize, - VkExtent2D& outImageSize, - RandomNumberGenerator& localRand) - { - outBufSize = 0; - outImageSize = {0, 0}; - - uint32_t allocSizeIndex = 0; - uint32_t r = localRand.Generate() % allocationSizeProbabilitySum; - while(r >= config.AllocationSizes[allocSizeIndex].Probability) - r -= config.AllocationSizes[allocSizeIndex++].Probability; - - const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex]; - if(allocSize.BufferSizeMax > 0) - { - assert(allocSize.ImageSizeMax == 0); - if(allocSize.BufferSizeMax == allocSize.BufferSizeMin) - outBufSize = allocSize.BufferSizeMin; - else - { - outBufSize = allocSize.BufferSizeMin + localRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin); - outBufSize = outBufSize / 16 * 16; - } - } - else - { - if(allocSize.ImageSizeMax == allocSize.ImageSizeMin) - outImageSize.width = outImageSize.height = allocSize.ImageSizeMax; - else - { - outImageSize.width = allocSize.ImageSizeMin + localRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); - outImageSize.height = allocSize.ImageSizeMin + localRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); - } - } - }; - - std::atomic<uint32_t> numThreadsReachedMaxAllocations = 0; - HANDLE threadsFinishEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - auto ThreadProc = [&](uint32_t randSeed) -> void - { - RandomNumberGenerator threadRand(randSeed); - VkDeviceSize threadTotalAllocatedBytes = 0; - std::vector<Allocation> threadAllocations; - VkDeviceSize threadBeginBytesToAllocate = config.BeginBytesToAllocate / config.ThreadCount; - VkDeviceSize threadMaxBytesToAllocate = config.MaxBytesToAllocate / config.ThreadCount; - uint32_t threadAdditionalOperationCount = config.AdditionalOperationCount / config.ThreadCount; - - // BEGIN ALLOCATIONS - for(;;) - { - VkDeviceSize bufferSize = 0; - VkExtent2D imageExtent = {}; - GetNextAllocationSize(bufferSize, imageExtent, threadRand); - if(threadTotalAllocatedBytes + bufferSize + imageExtent.width * imageExtent.height * IMAGE_BYTES_PER_PIXEL < - threadBeginBytesToAllocate) - { - if(Allocate(bufferSize, imageExtent, threadRand, threadTotalAllocatedBytes, threadAllocations) != VK_SUCCESS) - break; - } - else - break; - } - - // ADDITIONAL ALLOCATIONS AND FREES - for(size_t i = 0; i < threadAdditionalOperationCount; ++i) - { - VkDeviceSize bufferSize = 0; - VkExtent2D imageExtent = {}; - GetNextAllocationSize(bufferSize, imageExtent, threadRand); - - // true = allocate, false = free - bool allocate = threadRand.Generate() % 2 != 0; - - if(allocate) - { - if(threadTotalAllocatedBytes + - bufferSize + - imageExtent.width * imageExtent.height * IMAGE_BYTES_PER_PIXEL < - threadMaxBytesToAllocate) - { - if(Allocate(bufferSize, imageExtent, threadRand, threadTotalAllocatedBytes, threadAllocations) != VK_SUCCESS) - break; - } - } - else - { - bool useCommonAllocations = threadRand.Generate() % 100 < config.ThreadsUsingCommonAllocationsProbabilityPercent; - if(useCommonAllocations) - { - std::unique_lock<std::mutex> lock(commonAllocationsMutex); - if(!commonAllocations.empty()) - { - size_t indexToFree = threadRand.Generate() % commonAllocations.size(); - VmaAllocationInfo allocationInfo; - vmaGetAllocationInfo(g_hAllocator, commonAllocations[indexToFree].Alloc, &allocationInfo); - if(threadTotalAllocatedBytes >= allocationInfo.size) - { - DeallocationTimeRegisterObj timeRegisterObj{outResult}; - if(commonAllocations[indexToFree].Buffer != VK_NULL_HANDLE) - vmaDestroyBuffer(g_hAllocator, commonAllocations[indexToFree].Buffer, commonAllocations[indexToFree].Alloc); - else - vmaDestroyImage(g_hAllocator, commonAllocations[indexToFree].Image, commonAllocations[indexToFree].Alloc); - threadTotalAllocatedBytes -= allocationInfo.size; - commonAllocations.erase(commonAllocations.begin() + indexToFree); - } - } - } - else - { - if(!threadAllocations.empty()) - { - size_t indexToFree = threadRand.Generate() % threadAllocations.size(); - VmaAllocationInfo allocationInfo; - vmaGetAllocationInfo(g_hAllocator, threadAllocations[indexToFree].Alloc, &allocationInfo); - if(threadTotalAllocatedBytes >= allocationInfo.size) - { - DeallocationTimeRegisterObj timeRegisterObj{outResult}; - if(threadAllocations[indexToFree].Buffer != VK_NULL_HANDLE) - vmaDestroyBuffer(g_hAllocator, threadAllocations[indexToFree].Buffer, threadAllocations[indexToFree].Alloc); - else - vmaDestroyImage(g_hAllocator, threadAllocations[indexToFree].Image, threadAllocations[indexToFree].Alloc); - threadTotalAllocatedBytes -= allocationInfo.size; - threadAllocations.erase(threadAllocations.begin() + indexToFree); - } - } - } - } - } - - ++numThreadsReachedMaxAllocations; - - WaitForSingleObject(threadsFinishEvent, INFINITE); - - // DEALLOCATION - while(!threadAllocations.empty()) - { - size_t indexToFree = 0; - switch(config.FreeOrder) - { - case FREE_ORDER::FORWARD: - indexToFree = 0; - break; - case FREE_ORDER::BACKWARD: - indexToFree = threadAllocations.size() - 1; - break; - case FREE_ORDER::RANDOM: - indexToFree = mainRand.Generate() % threadAllocations.size(); - break; - } - - { - DeallocationTimeRegisterObj timeRegisterObj{outResult}; - if(threadAllocations[indexToFree].Buffer != VK_NULL_HANDLE) - vmaDestroyBuffer(g_hAllocator, threadAllocations[indexToFree].Buffer, threadAllocations[indexToFree].Alloc); - else - vmaDestroyImage(g_hAllocator, threadAllocations[indexToFree].Image, threadAllocations[indexToFree].Alloc); - } - threadAllocations.erase(threadAllocations.begin() + indexToFree); - } - }; - - uint32_t threadRandSeed = mainRand.Generate(); - std::vector<std::thread> bkgThreads; - for(size_t i = 0; i < config.ThreadCount; ++i) - { - bkgThreads.emplace_back(std::bind(ThreadProc, threadRandSeed + (uint32_t)i)); - } - - // Wait for threads reached max allocations - while(numThreadsReachedMaxAllocations < config.ThreadCount) - Sleep(0); - - // CALCULATE MEMORY STATISTICS ON FINAL USAGE - VmaStats vmaStats = {}; - vmaCalculateStats(g_hAllocator, &vmaStats); - outResult.TotalMemoryAllocated = vmaStats.total.usedBytes + vmaStats.total.unusedBytes; - outResult.FreeRangeSizeMax = vmaStats.total.unusedRangeSizeMax; - outResult.FreeRangeSizeAvg = vmaStats.total.unusedRangeSizeAvg; - - // Signal threads to deallocate - SetEvent(threadsFinishEvent); - - // Wait for threads finished - for(size_t i = 0; i < bkgThreads.size(); ++i) - bkgThreads[i].join(); - bkgThreads.clear(); - - CloseHandle(threadsFinishEvent); - - // Deallocate remaining common resources - while(!commonAllocations.empty()) - { - size_t indexToFree = 0; - switch(config.FreeOrder) - { - case FREE_ORDER::FORWARD: - indexToFree = 0; - break; - case FREE_ORDER::BACKWARD: - indexToFree = commonAllocations.size() - 1; - break; - case FREE_ORDER::RANDOM: - indexToFree = mainRand.Generate() % commonAllocations.size(); - break; - } - - { - DeallocationTimeRegisterObj timeRegisterObj{outResult}; - if(commonAllocations[indexToFree].Buffer != VK_NULL_HANDLE) - vmaDestroyBuffer(g_hAllocator, commonAllocations[indexToFree].Buffer, commonAllocations[indexToFree].Alloc); - else - vmaDestroyImage(g_hAllocator, commonAllocations[indexToFree].Image, commonAllocations[indexToFree].Alloc); - } - commonAllocations.erase(commonAllocations.begin() + indexToFree); - } - - if(allocationCount) - { - outResult.AllocationTimeAvg /= allocationCount; - outResult.DeallocationTimeAvg /= allocationCount; - } - - outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg; - - return res; -} - -void SaveAllocatorStatsToFile(const wchar_t* filePath) -{ - wprintf(L"Saving JSON dump to file \"%s\"\n", filePath); - char* stats; - vmaBuildStatsString(g_hAllocator, &stats, VK_TRUE); - SaveFile(filePath, stats, strlen(stats)); - vmaFreeStatsString(g_hAllocator, stats); -} - -struct AllocInfo -{ - VmaAllocation m_Allocation = VK_NULL_HANDLE; - VkBuffer m_Buffer = VK_NULL_HANDLE; - VkImage m_Image = VK_NULL_HANDLE; - VkImageLayout m_ImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; - uint32_t m_StartValue = 0; - union - { - VkBufferCreateInfo m_BufferInfo; - VkImageCreateInfo m_ImageInfo; - }; - - // After defragmentation. - VkBuffer m_NewBuffer = VK_NULL_HANDLE; - VkImage m_NewImage = VK_NULL_HANDLE; - - void CreateBuffer( - const VkBufferCreateInfo& bufCreateInfo, - const VmaAllocationCreateInfo& allocCreateInfo); - void CreateImage( - const VkImageCreateInfo& imageCreateInfo, - const VmaAllocationCreateInfo& allocCreateInfo, - VkImageLayout layout); - void Destroy(); -}; - -void AllocInfo::CreateBuffer( - const VkBufferCreateInfo& bufCreateInfo, - const VmaAllocationCreateInfo& allocCreateInfo) -{ - m_BufferInfo = bufCreateInfo; - VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &m_Buffer, &m_Allocation, nullptr); - TEST(res == VK_SUCCESS); -} -void AllocInfo::CreateImage( - const VkImageCreateInfo& imageCreateInfo, - const VmaAllocationCreateInfo& allocCreateInfo, - VkImageLayout layout) -{ - m_ImageInfo = imageCreateInfo; - m_ImageLayout = layout; - VkResult res = vmaCreateImage(g_hAllocator, &imageCreateInfo, &allocCreateInfo, &m_Image, &m_Allocation, nullptr); - TEST(res == VK_SUCCESS); -} - -void AllocInfo::Destroy() -{ - if(m_Image) - { - assert(!m_Buffer); - vkDestroyImage(g_hDevice, m_Image, g_Allocs); - m_Image = VK_NULL_HANDLE; - } - if(m_Buffer) - { - assert(!m_Image); - vkDestroyBuffer(g_hDevice, m_Buffer, g_Allocs); - m_Buffer = VK_NULL_HANDLE; - } - if(m_Allocation) - { - vmaFreeMemory(g_hAllocator, m_Allocation); - m_Allocation = VK_NULL_HANDLE; - } -} - -class StagingBufferCollection -{ -public: - StagingBufferCollection() { } - ~StagingBufferCollection(); - // Returns false if maximum total size of buffers would be exceeded. - bool AcquireBuffer(VkDeviceSize size, VkBuffer& outBuffer, void*& outMappedPtr); - void ReleaseAllBuffers(); - -private: - static const VkDeviceSize MAX_TOTAL_SIZE = 256ull * 1024 * 1024; - struct BufInfo - { - VmaAllocation Allocation = VK_NULL_HANDLE; - VkBuffer Buffer = VK_NULL_HANDLE; - VkDeviceSize Size = VK_WHOLE_SIZE; - void* MappedPtr = nullptr; - bool Used = false; - }; - std::vector<BufInfo> m_Bufs; - // Including both used and unused. - VkDeviceSize m_TotalSize = 0; -}; - -StagingBufferCollection::~StagingBufferCollection() -{ - for(size_t i = m_Bufs.size(); i--; ) - { - vmaDestroyBuffer(g_hAllocator, m_Bufs[i].Buffer, m_Bufs[i].Allocation); - } -} - -bool StagingBufferCollection::AcquireBuffer(VkDeviceSize size, VkBuffer& outBuffer, void*& outMappedPtr) -{ - assert(size <= MAX_TOTAL_SIZE); - - // Try to find existing unused buffer with best size. - size_t bestIndex = SIZE_MAX; - for(size_t i = 0, count = m_Bufs.size(); i < count; ++i) - { - BufInfo& currBufInfo = m_Bufs[i]; - if(!currBufInfo.Used && currBufInfo.Size >= size && - (bestIndex == SIZE_MAX || currBufInfo.Size < m_Bufs[bestIndex].Size)) - { - bestIndex = i; - } - } - - if(bestIndex != SIZE_MAX) - { - m_Bufs[bestIndex].Used = true; - outBuffer = m_Bufs[bestIndex].Buffer; - outMappedPtr = m_Bufs[bestIndex].MappedPtr; - return true; - } - - // Allocate new buffer with requested size. - if(m_TotalSize + size <= MAX_TOTAL_SIZE) - { - BufInfo bufInfo; - bufInfo.Size = size; - bufInfo.Used = true; - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.size = size; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - - VmaAllocationInfo allocInfo; - VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo); - bufInfo.MappedPtr = allocInfo.pMappedData; - TEST(res == VK_SUCCESS && bufInfo.MappedPtr); - - outBuffer = bufInfo.Buffer; - outMappedPtr = bufInfo.MappedPtr; - - m_Bufs.push_back(std::move(bufInfo)); - - m_TotalSize += size; - - return true; - } - - // There are some unused but smaller buffers: Free them and try again. - bool hasUnused = false; - for(size_t i = 0, count = m_Bufs.size(); i < count; ++i) - { - if(!m_Bufs[i].Used) - { - hasUnused = true; - break; - } - } - if(hasUnused) - { - for(size_t i = m_Bufs.size(); i--; ) - { - if(!m_Bufs[i].Used) - { - m_TotalSize -= m_Bufs[i].Size; - vmaDestroyBuffer(g_hAllocator, m_Bufs[i].Buffer, m_Bufs[i].Allocation); - m_Bufs.erase(m_Bufs.begin() + i); - } - } - - return AcquireBuffer(size, outBuffer, outMappedPtr); - } - - return false; -} - -void StagingBufferCollection::ReleaseAllBuffers() -{ - for(size_t i = 0, count = m_Bufs.size(); i < count; ++i) - { - m_Bufs[i].Used = false; - } -} - -static void UploadGpuData(const AllocInfo* allocInfo, size_t allocInfoCount) -{ - StagingBufferCollection stagingBufs; - - bool cmdBufferStarted = false; - for(size_t allocInfoIndex = 0; allocInfoIndex < allocInfoCount; ++allocInfoIndex) - { - const AllocInfo& currAllocInfo = allocInfo[allocInfoIndex]; - if(currAllocInfo.m_Buffer) - { - const VkDeviceSize size = currAllocInfo.m_BufferInfo.size; - - VkBuffer stagingBuf = VK_NULL_HANDLE; - void* stagingBufMappedPtr = nullptr; - if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr)) - { - TEST(cmdBufferStarted); - EndSingleTimeCommands(); - stagingBufs.ReleaseAllBuffers(); - cmdBufferStarted = false; - - bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr); - TEST(ok); - } - - // Fill staging buffer. - { - assert(size % sizeof(uint32_t) == 0); - uint32_t* stagingValPtr = (uint32_t*)stagingBufMappedPtr; - uint32_t val = currAllocInfo.m_StartValue; - for(size_t i = 0; i < size / sizeof(uint32_t); ++i) - { - *stagingValPtr = val; - ++stagingValPtr; - ++val; - } - } - - // Issue copy command from staging buffer to destination buffer. - if(!cmdBufferStarted) - { - cmdBufferStarted = true; - BeginSingleTimeCommands(); - } - - VkBufferCopy copy = {}; - copy.srcOffset = 0; - copy.dstOffset = 0; - copy.size = size; - vkCmdCopyBuffer(g_hTemporaryCommandBuffer, stagingBuf, currAllocInfo.m_Buffer, 1, ©); - } - else - { - TEST(currAllocInfo.m_ImageInfo.format == VK_FORMAT_R8G8B8A8_UNORM && "Only RGBA8 images are currently supported."); - TEST(currAllocInfo.m_ImageInfo.mipLevels == 1 && "Only single mip images are currently supported."); - - const VkDeviceSize size = (VkDeviceSize)currAllocInfo.m_ImageInfo.extent.width * currAllocInfo.m_ImageInfo.extent.height * sizeof(uint32_t); - - VkBuffer stagingBuf = VK_NULL_HANDLE; - void* stagingBufMappedPtr = nullptr; - if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr)) - { - TEST(cmdBufferStarted); - EndSingleTimeCommands(); - stagingBufs.ReleaseAllBuffers(); - cmdBufferStarted = false; - - bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr); - TEST(ok); - } - - // Fill staging buffer. - { - assert(size % sizeof(uint32_t) == 0); - uint32_t *stagingValPtr = (uint32_t *)stagingBufMappedPtr; - uint32_t val = currAllocInfo.m_StartValue; - for(size_t i = 0; i < size / sizeof(uint32_t); ++i) - { - *stagingValPtr = val; - ++stagingValPtr; - ++val; - } - } - - // Issue copy command from staging buffer to destination buffer. - if(!cmdBufferStarted) - { - cmdBufferStarted = true; - BeginSingleTimeCommands(); - } - - - // Transfer to transfer dst layout - VkImageSubresourceRange subresourceRange = { - VK_IMAGE_ASPECT_COLOR_BIT, - 0, VK_REMAINING_MIP_LEVELS, - 0, VK_REMAINING_ARRAY_LAYERS - }; - - VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; - barrier.srcAccessMask = 0; - barrier.dstAccessMask = 0; - barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = currAllocInfo.m_Image; - barrier.subresourceRange = subresourceRange; - - vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, - 0, nullptr, - 0, nullptr, - 1, &barrier); - - // Copy image date - VkBufferImageCopy copy = {}; - copy.bufferOffset = 0; - copy.bufferRowLength = 0; - copy.bufferImageHeight = 0; - copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copy.imageSubresource.layerCount = 1; - copy.imageExtent = currAllocInfo.m_ImageInfo.extent; - - vkCmdCopyBufferToImage(g_hTemporaryCommandBuffer, stagingBuf, currAllocInfo.m_Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); - - // Transfer to desired layout - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; - barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.newLayout = currAllocInfo.m_ImageLayout; - - vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, - 0, nullptr, - 0, nullptr, - 1, &barrier); - } - } - - if(cmdBufferStarted) - { - EndSingleTimeCommands(); - stagingBufs.ReleaseAllBuffers(); - } -} - -static void ValidateGpuData(const AllocInfo* allocInfo, size_t allocInfoCount) -{ - StagingBufferCollection stagingBufs; - - bool cmdBufferStarted = false; - size_t validateAllocIndexOffset = 0; - std::vector<void*> validateStagingBuffers; - for(size_t allocInfoIndex = 0; allocInfoIndex < allocInfoCount; ++allocInfoIndex) - { - const AllocInfo& currAllocInfo = allocInfo[allocInfoIndex]; - if(currAllocInfo.m_Buffer) - { - const VkDeviceSize size = currAllocInfo.m_BufferInfo.size; - - VkBuffer stagingBuf = VK_NULL_HANDLE; - void* stagingBufMappedPtr = nullptr; - if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr)) - { - TEST(cmdBufferStarted); - EndSingleTimeCommands(); - cmdBufferStarted = false; - - for(size_t validateIndex = 0; - validateIndex < validateStagingBuffers.size(); - ++validateIndex) - { - const size_t validateAllocIndex = validateIndex + validateAllocIndexOffset; - const VkDeviceSize validateSize = allocInfo[validateAllocIndex].m_BufferInfo.size; - TEST(validateSize % sizeof(uint32_t) == 0); - const uint32_t* stagingValPtr = (const uint32_t*)validateStagingBuffers[validateIndex]; - uint32_t val = allocInfo[validateAllocIndex].m_StartValue; - bool valid = true; - for(size_t i = 0; i < validateSize / sizeof(uint32_t); ++i) - { - if(*stagingValPtr != val) - { - valid = false; - break; - } - ++stagingValPtr; - ++val; - } - TEST(valid); - } - - stagingBufs.ReleaseAllBuffers(); - - validateAllocIndexOffset = allocInfoIndex; - validateStagingBuffers.clear(); - - bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr); - TEST(ok); - } - - // Issue copy command from staging buffer to destination buffer. - if(!cmdBufferStarted) - { - cmdBufferStarted = true; - BeginSingleTimeCommands(); - } - - VkBufferCopy copy = {}; - copy.srcOffset = 0; - copy.dstOffset = 0; - copy.size = size; - vkCmdCopyBuffer(g_hTemporaryCommandBuffer, currAllocInfo.m_Buffer, stagingBuf, 1, ©); - - // Sava mapped pointer for later validation. - validateStagingBuffers.push_back(stagingBufMappedPtr); - } - else - { - TEST(0 && "Images not currently supported."); - } - } - - if(cmdBufferStarted) - { - EndSingleTimeCommands(); - - for(size_t validateIndex = 0; - validateIndex < validateStagingBuffers.size(); - ++validateIndex) - { - const size_t validateAllocIndex = validateIndex + validateAllocIndexOffset; - const VkDeviceSize validateSize = allocInfo[validateAllocIndex].m_BufferInfo.size; - TEST(validateSize % sizeof(uint32_t) == 0); - const uint32_t* stagingValPtr = (const uint32_t*)validateStagingBuffers[validateIndex]; - uint32_t val = allocInfo[validateAllocIndex].m_StartValue; - bool valid = true; - for(size_t i = 0; i < validateSize / sizeof(uint32_t); ++i) - { - if(*stagingValPtr != val) - { - valid = false; - break; - } - ++stagingValPtr; - ++val; - } - TEST(valid); - } - - stagingBufs.ReleaseAllBuffers(); - } -} - -static void GetMemReq(VmaAllocationCreateInfo& outMemReq) -{ - outMemReq = {}; - outMemReq.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; - //outMemReq.flags = VMA_ALLOCATION_CREATE_PERSISTENT_MAP_BIT; -} - -static void CreateBuffer( - VmaPool pool, - const VkBufferCreateInfo& bufCreateInfo, - bool persistentlyMapped, - AllocInfo& outAllocInfo) -{ - outAllocInfo = {}; - outAllocInfo.m_BufferInfo = bufCreateInfo; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.pool = pool; - if(persistentlyMapped) - allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - - VmaAllocationInfo vmaAllocInfo = {}; - ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &outAllocInfo.m_Buffer, &outAllocInfo.m_Allocation, &vmaAllocInfo) ); - - // Setup StartValue and fill. - { - outAllocInfo.m_StartValue = (uint32_t)rand(); - uint32_t* data = (uint32_t*)vmaAllocInfo.pMappedData; - TEST((data != nullptr) == persistentlyMapped); - if(!persistentlyMapped) - { - ERR_GUARD_VULKAN( vmaMapMemory(g_hAllocator, outAllocInfo.m_Allocation, (void**)&data) ); - } - - uint32_t value = outAllocInfo.m_StartValue; - TEST(bufCreateInfo.size % 4 == 0); - for(size_t i = 0; i < bufCreateInfo.size / sizeof(uint32_t); ++i) - data[i] = value++; - - if(!persistentlyMapped) - vmaUnmapMemory(g_hAllocator, outAllocInfo.m_Allocation); - } -} - -static void CreateAllocation(AllocInfo& outAllocation) -{ - outAllocation.m_Allocation = nullptr; - outAllocation.m_Buffer = nullptr; - outAllocation.m_Image = nullptr; - outAllocation.m_StartValue = (uint32_t)rand(); - - VmaAllocationCreateInfo vmaMemReq; - GetMemReq(vmaMemReq); - - VmaAllocationInfo allocInfo; - - const bool isBuffer = true;//(rand() & 0x1) != 0; - const bool isLarge = (rand() % 16) == 0; - if(isBuffer) - { - const uint32_t bufferSize = isLarge ? - (rand() % 10 + 1) * (1024 * 1024) : // 1 MB ... 10 MB - (rand() % 1024 + 1) * 1024; // 1 KB ... 1 MB - - VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufferInfo.size = bufferSize; - bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - VkResult res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &vmaMemReq, &outAllocation.m_Buffer, &outAllocation.m_Allocation, &allocInfo); - outAllocation.m_BufferInfo = bufferInfo; - TEST(res == VK_SUCCESS); - } - else - { - const uint32_t imageSizeX = isLarge ? - 1024 + rand() % (4096 - 1024) : // 1024 ... 4096 - rand() % 1024 + 1; // 1 ... 1024 - const uint32_t imageSizeY = isLarge ? - 1024 + rand() % (4096 - 1024) : // 1024 ... 4096 - rand() % 1024 + 1; // 1 ... 1024 - - VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imageInfo.extent.width = imageSizeX; - imageInfo.extent.height = imageSizeY; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - - VkResult res = vmaCreateImage(g_hAllocator, &imageInfo, &vmaMemReq, &outAllocation.m_Image, &outAllocation.m_Allocation, &allocInfo); - outAllocation.m_ImageInfo = imageInfo; - TEST(res == VK_SUCCESS); - } - - uint32_t* data = (uint32_t*)allocInfo.pMappedData; - if(allocInfo.pMappedData == nullptr) - { - VkResult res = vmaMapMemory(g_hAllocator, outAllocation.m_Allocation, (void**)&data); - TEST(res == VK_SUCCESS); - } - - uint32_t value = outAllocation.m_StartValue; - TEST(allocInfo.size % 4 == 0); - for(size_t i = 0; i < allocInfo.size / sizeof(uint32_t); ++i) - data[i] = value++; - - if(allocInfo.pMappedData == nullptr) - vmaUnmapMemory(g_hAllocator, outAllocation.m_Allocation); -} - -static void DestroyAllocation(const AllocInfo& allocation) -{ - if(allocation.m_Buffer) - vmaDestroyBuffer(g_hAllocator, allocation.m_Buffer, allocation.m_Allocation); - else - vmaDestroyImage(g_hAllocator, allocation.m_Image, allocation.m_Allocation); -} - -static void DestroyAllAllocations(std::vector<AllocInfo>& allocations) -{ - for(size_t i = allocations.size(); i--; ) - DestroyAllocation(allocations[i]); - allocations.clear(); -} - -static void ValidateAllocationData(const AllocInfo& allocation) -{ - VmaAllocationInfo allocInfo; - vmaGetAllocationInfo(g_hAllocator, allocation.m_Allocation, &allocInfo); - - uint32_t* data = (uint32_t*)allocInfo.pMappedData; - if(allocInfo.pMappedData == nullptr) - { - VkResult res = vmaMapMemory(g_hAllocator, allocation.m_Allocation, (void**)&data); - TEST(res == VK_SUCCESS); - } - - uint32_t value = allocation.m_StartValue; - bool ok = true; - size_t i; - TEST(allocInfo.size % 4 == 0); - for(i = 0; i < allocInfo.size / sizeof(uint32_t); ++i) - { - if(data[i] != value++) - { - ok = false; - break; - } - } - TEST(ok); - - if(allocInfo.pMappedData == nullptr) - vmaUnmapMemory(g_hAllocator, allocation.m_Allocation); -} - -static void RecreateAllocationResource(AllocInfo& allocation) -{ - VmaAllocationInfo allocInfo; - vmaGetAllocationInfo(g_hAllocator, allocation.m_Allocation, &allocInfo); - - if(allocation.m_Buffer) - { - vkDestroyBuffer(g_hDevice, allocation.m_Buffer, g_Allocs); - - VkResult res = vkCreateBuffer(g_hDevice, &allocation.m_BufferInfo, g_Allocs, &allocation.m_Buffer); - TEST(res == VK_SUCCESS); - - // Just to silence validation layer warnings. - VkMemoryRequirements vkMemReq; - vkGetBufferMemoryRequirements(g_hDevice, allocation.m_Buffer, &vkMemReq); - TEST(vkMemReq.size >= allocation.m_BufferInfo.size); - - res = vmaBindBufferMemory(g_hAllocator, allocation.m_Allocation, allocation.m_Buffer); - TEST(res == VK_SUCCESS); - } - else - { - vkDestroyImage(g_hDevice, allocation.m_Image, g_Allocs); - - VkResult res = vkCreateImage(g_hDevice, &allocation.m_ImageInfo, g_Allocs, &allocation.m_Image); - TEST(res == VK_SUCCESS); - - // Just to silence validation layer warnings. - VkMemoryRequirements vkMemReq; - vkGetImageMemoryRequirements(g_hDevice, allocation.m_Image, &vkMemReq); - - res = vmaBindImageMemory(g_hAllocator, allocation.m_Allocation, allocation.m_Image); - TEST(res == VK_SUCCESS); - } -} - -static void Defragment(AllocInfo* allocs, size_t allocCount, - const VmaDefragmentationInfo* defragmentationInfo = nullptr, - VmaDefragmentationStats* defragmentationStats = nullptr) -{ - std::vector<VmaAllocation> vmaAllocs(allocCount); - for(size_t i = 0; i < allocCount; ++i) - vmaAllocs[i] = allocs[i].m_Allocation; - - std::vector<VkBool32> allocChanged(allocCount); - - ERR_GUARD_VULKAN( vmaDefragment(g_hAllocator, vmaAllocs.data(), allocCount, allocChanged.data(), - defragmentationInfo, defragmentationStats) ); - - for(size_t i = 0; i < allocCount; ++i) - { - if(allocChanged[i]) - { - RecreateAllocationResource(allocs[i]); - } - } -} - -static void ValidateAllocationsData(const AllocInfo* allocs, size_t allocCount) -{ - std::for_each(allocs, allocs + allocCount, [](const AllocInfo& allocInfo) { - ValidateAllocationData(allocInfo); - }); -} - -void TestDefragmentationSimple() -{ - wprintf(L"Test defragmentation simple\n"); - - RandomNumberGenerator rand(667); - - const VkDeviceSize BUF_SIZE = 0x10000; - const VkDeviceSize BLOCK_SIZE = BUF_SIZE * 8; - - const VkDeviceSize MIN_BUF_SIZE = 32; - const VkDeviceSize MAX_BUF_SIZE = BUF_SIZE * 4; - auto RandomBufSize = [&]() -> VkDeviceSize { - return align_up<VkDeviceSize>(rand.Generate() % (MAX_BUF_SIZE - MIN_BUF_SIZE + 1) + MIN_BUF_SIZE, 32); - }; - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.size = BUF_SIZE; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - VmaAllocationCreateInfo exampleAllocCreateInfo = {}; - exampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - - uint32_t memTypeIndex = UINT32_MAX; - vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &exampleAllocCreateInfo, &memTypeIndex); - - VmaPoolCreateInfo poolCreateInfo = {}; - poolCreateInfo.blockSize = BLOCK_SIZE; - poolCreateInfo.memoryTypeIndex = memTypeIndex; - - VmaPool pool; - ERR_GUARD_VULKAN( vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) ); - - // Defragmentation of empty pool. - { - VmaDefragmentationInfo2 defragInfo = {}; - defragInfo.maxCpuBytesToMove = VK_WHOLE_SIZE; - defragInfo.maxCpuAllocationsToMove = UINT32_MAX; - defragInfo.poolCount = 1; - defragInfo.pPools = &pool; - - VmaDefragmentationStats defragStats = {}; - VmaDefragmentationContext defragCtx = nullptr; - VkResult res = vmaDefragmentationBegin(g_hAllocator, &defragInfo, &defragStats, &defragCtx); - TEST(res >= VK_SUCCESS); - vmaDefragmentationEnd(g_hAllocator, defragCtx); - TEST(defragStats.allocationsMoved == 0 && defragStats.bytesFreed == 0 && - defragStats.bytesMoved == 0 && defragStats.deviceMemoryBlocksFreed == 0); - } - - std::vector<AllocInfo> allocations; - - // persistentlyMappedOption = 0 - not persistently mapped. - // persistentlyMappedOption = 1 - persistently mapped. - for(uint32_t persistentlyMappedOption = 0; persistentlyMappedOption < 2; ++persistentlyMappedOption) - { - wprintf(L" Persistently mapped option = %u\n", persistentlyMappedOption); - const bool persistentlyMapped = persistentlyMappedOption != 0; - - // # Test 1 - // Buffers of fixed size. - // Fill 2 blocks. Remove odd buffers. Defragment everything. - // Expected result: at least 1 block freed. - { - for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i) - { - AllocInfo allocInfo; - CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo); - allocations.push_back(allocInfo); - } - - for(size_t i = 1; i < allocations.size(); ++i) - { - DestroyAllocation(allocations[i]); - allocations.erase(allocations.begin() + i); - } - - VmaDefragmentationStats defragStats; - Defragment(allocations.data(), allocations.size(), nullptr, &defragStats); - TEST(defragStats.allocationsMoved > 0 && defragStats.bytesMoved > 0); - TEST(defragStats.deviceMemoryBlocksFreed >= 1); - - ValidateAllocationsData(allocations.data(), allocations.size()); - - DestroyAllAllocations(allocations); - } - - // # Test 2 - // Buffers of fixed size. - // Fill 2 blocks. Remove odd buffers. Defragment one buffer at time. - // Expected result: Each of 4 interations makes some progress. - { - for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i) - { - AllocInfo allocInfo; - CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo); - allocations.push_back(allocInfo); - } - - for(size_t i = 1; i < allocations.size(); ++i) - { - DestroyAllocation(allocations[i]); - allocations.erase(allocations.begin() + i); - } - - VmaDefragmentationInfo defragInfo = {}; - defragInfo.maxAllocationsToMove = 1; - defragInfo.maxBytesToMove = BUF_SIZE; - - for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE / 2; ++i) - { - VmaDefragmentationStats defragStats; - Defragment(allocations.data(), allocations.size(), &defragInfo, &defragStats); - TEST(defragStats.allocationsMoved > 0 && defragStats.bytesMoved > 0); - } - - ValidateAllocationsData(allocations.data(), allocations.size()); - - DestroyAllAllocations(allocations); - } - - // # Test 3 - // Buffers of variable size. - // Create a number of buffers. Remove some percent of them. - // Defragment while having some percent of them unmovable. - // Expected result: Just simple validation. - { - for(size_t i = 0; i < 100; ++i) - { - VkBufferCreateInfo localBufCreateInfo = bufCreateInfo; - localBufCreateInfo.size = RandomBufSize(); - - AllocInfo allocInfo; - CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo); - allocations.push_back(allocInfo); - } - - const uint32_t percentToDelete = 60; - const size_t numberToDelete = allocations.size() * percentToDelete / 100; - for(size_t i = 0; i < numberToDelete; ++i) - { - size_t indexToDelete = rand.Generate() % (uint32_t)allocations.size(); - DestroyAllocation(allocations[indexToDelete]); - allocations.erase(allocations.begin() + indexToDelete); - } - - // Non-movable allocations will be at the beginning of allocations array. - const uint32_t percentNonMovable = 20; - const size_t numberNonMovable = allocations.size() * percentNonMovable / 100; - for(size_t i = 0; i < numberNonMovable; ++i) - { - size_t indexNonMovable = i + rand.Generate() % (uint32_t)(allocations.size() - i); - if(indexNonMovable != i) - std::swap(allocations[i], allocations[indexNonMovable]); - } - - VmaDefragmentationStats defragStats; - Defragment( - allocations.data() + numberNonMovable, - allocations.size() - numberNonMovable, - nullptr, &defragStats); - - ValidateAllocationsData(allocations.data(), allocations.size()); - - DestroyAllAllocations(allocations); - } - } - - /* - Allocation that must be move to an overlapping place using memmove(). - Create 2 buffers, second slightly bigger than the first. Delete first. Then defragment. - */ - if(VMA_DEBUG_MARGIN == 0) // FAST algorithm works only when DEBUG_MARGIN disabled. - { - AllocInfo allocInfo[2]; - - bufCreateInfo.size = BUF_SIZE; - CreateBuffer(pool, bufCreateInfo, false, allocInfo[0]); - const VkDeviceSize biggerBufSize = BUF_SIZE + BUF_SIZE / 256; - bufCreateInfo.size = biggerBufSize; - CreateBuffer(pool, bufCreateInfo, false, allocInfo[1]); - - DestroyAllocation(allocInfo[0]); - - VmaDefragmentationStats defragStats; - Defragment(&allocInfo[1], 1, nullptr, &defragStats); - // If this fails, it means we couldn't do memmove with overlapping regions. - TEST(defragStats.allocationsMoved == 1 && defragStats.bytesMoved > 0); - - ValidateAllocationsData(&allocInfo[1], 1); - DestroyAllocation(allocInfo[1]); - } - - vmaDestroyPool(g_hAllocator, pool); -} - -void TestDefragmentationWholePool() -{ - wprintf(L"Test defragmentation whole pool\n"); - - RandomNumberGenerator rand(668); - - const VkDeviceSize BUF_SIZE = 0x10000; - const VkDeviceSize BLOCK_SIZE = BUF_SIZE * 8; - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.size = BUF_SIZE; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - VmaAllocationCreateInfo exampleAllocCreateInfo = {}; - exampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - - uint32_t memTypeIndex = UINT32_MAX; - vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &exampleAllocCreateInfo, &memTypeIndex); - - VmaPoolCreateInfo poolCreateInfo = {}; - poolCreateInfo.blockSize = BLOCK_SIZE; - poolCreateInfo.memoryTypeIndex = memTypeIndex; - - VmaDefragmentationStats defragStats[2]; - for(size_t caseIndex = 0; caseIndex < 2; ++caseIndex) - { - VmaPool pool; - ERR_GUARD_VULKAN( vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) ); - - std::vector<AllocInfo> allocations; - - // Buffers of fixed size. - // Fill 2 blocks. Remove odd buffers. Defragment all of them. - for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i) - { - AllocInfo allocInfo; - CreateBuffer(pool, bufCreateInfo, false, allocInfo); - allocations.push_back(allocInfo); - } - - for(size_t i = 1; i < allocations.size(); ++i) - { - DestroyAllocation(allocations[i]); - allocations.erase(allocations.begin() + i); - } - - VmaDefragmentationInfo2 defragInfo = {}; - defragInfo.maxCpuAllocationsToMove = UINT32_MAX; - defragInfo.maxCpuBytesToMove = VK_WHOLE_SIZE; - std::vector<VmaAllocation> allocationsToDefrag; - if(caseIndex == 0) - { - defragInfo.poolCount = 1; - defragInfo.pPools = &pool; - } - else - { - const size_t allocCount = allocations.size(); - allocationsToDefrag.resize(allocCount); - std::transform( - allocations.begin(), allocations.end(), - allocationsToDefrag.begin(), - [](const AllocInfo& allocInfo) { return allocInfo.m_Allocation; }); - defragInfo.allocationCount = (uint32_t)allocCount; - defragInfo.pAllocations = allocationsToDefrag.data(); - } - - VmaDefragmentationContext defragCtx = VK_NULL_HANDLE; - VkResult res = vmaDefragmentationBegin(g_hAllocator, &defragInfo, &defragStats[caseIndex], &defragCtx); - TEST(res >= VK_SUCCESS); - vmaDefragmentationEnd(g_hAllocator, defragCtx); - - TEST(defragStats[caseIndex].allocationsMoved > 0 && defragStats[caseIndex].bytesMoved > 0); - - ValidateAllocationsData(allocations.data(), allocations.size()); - - DestroyAllAllocations(allocations); - - vmaDestroyPool(g_hAllocator, pool); - } - - TEST(defragStats[0].bytesMoved == defragStats[1].bytesMoved); - TEST(defragStats[0].allocationsMoved == defragStats[1].allocationsMoved); - TEST(defragStats[0].bytesFreed == defragStats[1].bytesFreed); - TEST(defragStats[0].deviceMemoryBlocksFreed == defragStats[1].deviceMemoryBlocksFreed); -} - -void TestDefragmentationFull() -{ - std::vector<AllocInfo> allocations; - - // Create initial allocations. - for(size_t i = 0; i < 400; ++i) - { - AllocInfo allocation; - CreateAllocation(allocation); - allocations.push_back(allocation); - } - - // Delete random allocations - const size_t allocationsToDeletePercent = 80; - size_t allocationsToDelete = allocations.size() * allocationsToDeletePercent / 100; - for(size_t i = 0; i < allocationsToDelete; ++i) - { - size_t index = (size_t)rand() % allocations.size(); - DestroyAllocation(allocations[index]); - allocations.erase(allocations.begin() + index); - } - - for(size_t i = 0; i < allocations.size(); ++i) - ValidateAllocationData(allocations[i]); - - //SaveAllocatorStatsToFile(L"Before.csv"); - - { - std::vector<VmaAllocation> vmaAllocations(allocations.size()); - for(size_t i = 0; i < allocations.size(); ++i) - vmaAllocations[i] = allocations[i].m_Allocation; - - const size_t nonMovablePercent = 0; - size_t nonMovableCount = vmaAllocations.size() * nonMovablePercent / 100; - for(size_t i = 0; i < nonMovableCount; ++i) - { - size_t index = (size_t)rand() % vmaAllocations.size(); - vmaAllocations.erase(vmaAllocations.begin() + index); - } - - const uint32_t defragCount = 1; - for(uint32_t defragIndex = 0; defragIndex < defragCount; ++defragIndex) - { - std::vector<VkBool32> allocationsChanged(vmaAllocations.size()); - - VmaDefragmentationInfo defragmentationInfo; - defragmentationInfo.maxAllocationsToMove = UINT_MAX; - defragmentationInfo.maxBytesToMove = SIZE_MAX; - - wprintf(L"Defragmentation #%u\n", defragIndex); - - time_point begTime = std::chrono::high_resolution_clock::now(); - - VmaDefragmentationStats stats; - VkResult res = vmaDefragment(g_hAllocator, vmaAllocations.data(), vmaAllocations.size(), allocationsChanged.data(), &defragmentationInfo, &stats); - TEST(res >= 0); - - float defragmentDuration = ToFloatSeconds(std::chrono::high_resolution_clock::now() - begTime); - - wprintf(L"Moved allocations %u, bytes %llu\n", stats.allocationsMoved, stats.bytesMoved); - wprintf(L"Freed blocks %u, bytes %llu\n", stats.deviceMemoryBlocksFreed, stats.bytesFreed); - wprintf(L"Time: %.2f s\n", defragmentDuration); - - for(size_t i = 0; i < vmaAllocations.size(); ++i) - { - if(allocationsChanged[i]) - { - RecreateAllocationResource(allocations[i]); - } - } - - for(size_t i = 0; i < allocations.size(); ++i) - ValidateAllocationData(allocations[i]); - - //wchar_t fileName[MAX_PATH]; - //swprintf(fileName, MAX_PATH, L"After_%02u.csv", defragIndex); - //SaveAllocatorStatsToFile(fileName); - } - } - - // Destroy all remaining allocations. - DestroyAllAllocations(allocations); -} - -static void TestDefragmentationGpu() -{ - wprintf(L"Test defragmentation GPU\n"); - - std::vector<AllocInfo> allocations; - - // Create that many allocations to surely fill 3 new blocks of 256 MB. - const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024; - const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024; - const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024; - const size_t bufCount = (size_t)(totalSize / bufSizeMin); - const size_t percentToLeave = 30; - const size_t percentNonMovable = 3; - RandomNumberGenerator rand = { 234522 }; - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - allocCreateInfo.flags = 0; - - // Create all intended buffers. - for(size_t i = 0; i < bufCount; ++i) - { - bufCreateInfo.size = align_up(rand.Generate() % (bufSizeMax - bufSizeMin) + bufSizeMin, 32ull); - - if(rand.Generate() % 100 < percentNonMovable) - { - bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | - VK_BUFFER_USAGE_TRANSFER_DST_BIT | - VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - allocCreateInfo.pUserData = (void*)(uintptr_t)2; - } - else - { - // Different usage just to see different color in output from VmaDumpVis. - bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | - VK_BUFFER_USAGE_TRANSFER_DST_BIT | - VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - // And in JSON dump. - allocCreateInfo.pUserData = (void*)(uintptr_t)1; - } - - AllocInfo alloc; - alloc.CreateBuffer(bufCreateInfo, allocCreateInfo); - alloc.m_StartValue = rand.Generate(); - allocations.push_back(alloc); - } - - // Destroy some percentage of them. - { - const size_t buffersToDestroy = round_div<size_t>(bufCount * (100 - percentToLeave), 100); - for(size_t i = 0; i < buffersToDestroy; ++i) - { - const size_t index = rand.Generate() % allocations.size(); - allocations[index].Destroy(); - allocations.erase(allocations.begin() + index); - } - } - - // Fill them with meaningful data. - UploadGpuData(allocations.data(), allocations.size()); - - wchar_t fileName[MAX_PATH]; - swprintf_s(fileName, L"GPU_defragmentation_A_before.json"); - SaveAllocatorStatsToFile(fileName); - - // Defragment using GPU only. - { - const size_t allocCount = allocations.size(); - - std::vector<VmaAllocation> allocationPtrs; - std::vector<VkBool32> allocationChanged; - std::vector<size_t> allocationOriginalIndex; - - for(size_t i = 0; i < allocCount; ++i) - { - VmaAllocationInfo allocInfo = {}; - vmaGetAllocationInfo(g_hAllocator, allocations[i].m_Allocation, &allocInfo); - if((uintptr_t)allocInfo.pUserData == 1) // Movable - { - allocationPtrs.push_back(allocations[i].m_Allocation); - allocationChanged.push_back(VK_FALSE); - allocationOriginalIndex.push_back(i); - } - } - - const size_t movableAllocCount = allocationPtrs.size(); - - BeginSingleTimeCommands(); - - VmaDefragmentationInfo2 defragInfo = {}; - defragInfo.flags = 0; - defragInfo.allocationCount = (uint32_t)movableAllocCount; - defragInfo.pAllocations = allocationPtrs.data(); - defragInfo.pAllocationsChanged = allocationChanged.data(); - defragInfo.maxGpuBytesToMove = VK_WHOLE_SIZE; - defragInfo.maxGpuAllocationsToMove = UINT32_MAX; - defragInfo.commandBuffer = g_hTemporaryCommandBuffer; - - VmaDefragmentationStats stats = {}; - VmaDefragmentationContext ctx = VK_NULL_HANDLE; - VkResult res = vmaDefragmentationBegin(g_hAllocator, &defragInfo, &stats, &ctx); - TEST(res >= VK_SUCCESS); - - EndSingleTimeCommands(); - - vmaDefragmentationEnd(g_hAllocator, ctx); - - for(size_t i = 0; i < movableAllocCount; ++i) - { - if(allocationChanged[i]) - { - const size_t origAllocIndex = allocationOriginalIndex[i]; - RecreateAllocationResource(allocations[origAllocIndex]); - } - } - - // If corruption detection is enabled, GPU defragmentation may not work on - // memory types that have this detection active, e.g. on Intel. - #if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0 - TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0); - TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0); - #endif - } - - ValidateGpuData(allocations.data(), allocations.size()); - - swprintf_s(fileName, L"GPU_defragmentation_B_after.json"); - SaveAllocatorStatsToFile(fileName); - - // Destroy all remaining buffers. - for(size_t i = allocations.size(); i--; ) - { - allocations[i].Destroy(); - } -} - -static void ProcessDefragmentationStepInfo(VmaDefragmentationPassInfo &stepInfo) -{ - std::vector<VkImageMemoryBarrier> beginImageBarriers; - std::vector<VkImageMemoryBarrier> finalizeImageBarriers; - - VkPipelineStageFlags beginSrcStageMask = 0; - VkPipelineStageFlags beginDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; - - VkPipelineStageFlags finalizeSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; - VkPipelineStageFlags finalizeDstStageMask = 0; - - bool wantsMemoryBarrier = false; - - VkMemoryBarrier beginMemoryBarrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; - VkMemoryBarrier finalizeMemoryBarrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; - - for(uint32_t i = 0; i < stepInfo.moveCount; ++i) - { - VmaAllocationInfo info; - vmaGetAllocationInfo(g_hAllocator, stepInfo.pMoves[i].allocation, &info); - - AllocInfo *allocInfo = (AllocInfo *)info.pUserData; - - if(allocInfo->m_Image) - { - VkImage newImage; - - const VkResult result = vkCreateImage(g_hDevice, &allocInfo->m_ImageInfo, g_Allocs, &newImage); - TEST(result >= VK_SUCCESS); - - vkBindImageMemory(g_hDevice, newImage, stepInfo.pMoves[i].memory, stepInfo.pMoves[i].offset); - allocInfo->m_NewImage = newImage; - - // Keep track of our pipeline stages that we need to wait/signal on - beginSrcStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - finalizeDstStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - - // We need one pipeline barrier and two image layout transitions here - // First we'll have to turn our newly created image into VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL - // And the second one is turning the old image into VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL - - VkImageSubresourceRange subresourceRange = { - VK_IMAGE_ASPECT_COLOR_BIT, - 0, VK_REMAINING_MIP_LEVELS, - 0, VK_REMAINING_ARRAY_LAYERS - }; - - VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = newImage; - barrier.subresourceRange = subresourceRange; - - beginImageBarriers.push_back(barrier); - - // Second barrier to convert the existing image. This one actually needs a real barrier - barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - barrier.oldLayout = allocInfo->m_ImageLayout; - barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - barrier.image = allocInfo->m_Image; - - beginImageBarriers.push_back(barrier); - - // And lastly we need a barrier that turns our new image into the layout of the old one - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; - barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.newLayout = allocInfo->m_ImageLayout; - barrier.image = newImage; - - finalizeImageBarriers.push_back(barrier); - } - else if(allocInfo->m_Buffer) - { - VkBuffer newBuffer; - - const VkResult result = vkCreateBuffer(g_hDevice, &allocInfo->m_BufferInfo, g_Allocs, &newBuffer); - TEST(result >= VK_SUCCESS); - - vkBindBufferMemory(g_hDevice, newBuffer, stepInfo.pMoves[i].memory, stepInfo.pMoves[i].offset); - allocInfo->m_NewBuffer = newBuffer; - - // Keep track of our pipeline stages that we need to wait/signal on - beginSrcStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - finalizeDstStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - - beginMemoryBarrier.srcAccessMask |= VK_ACCESS_MEMORY_WRITE_BIT; - beginMemoryBarrier.dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; - - finalizeMemoryBarrier.srcAccessMask |= VK_ACCESS_TRANSFER_WRITE_BIT; - finalizeMemoryBarrier.dstAccessMask |= VK_ACCESS_MEMORY_READ_BIT; - - wantsMemoryBarrier = true; - } - } - - if(!beginImageBarriers.empty() || wantsMemoryBarrier) - { - const uint32_t memoryBarrierCount = wantsMemoryBarrier ? 1 : 0; - - vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, beginSrcStageMask, beginDstStageMask, 0, - memoryBarrierCount, &beginMemoryBarrier, - 0, nullptr, - (uint32_t)beginImageBarriers.size(), beginImageBarriers.data()); - } - - for(uint32_t i = 0; i < stepInfo.moveCount; ++ i) - { - VmaAllocationInfo info; - vmaGetAllocationInfo(g_hAllocator, stepInfo.pMoves[i].allocation, &info); - - AllocInfo *allocInfo = (AllocInfo *)info.pUserData; - - if(allocInfo->m_Image) - { - std::vector<VkImageCopy> imageCopies; - - // Copy all mips of the source image into the target image - VkOffset3D offset = { 0, 0, 0 }; - VkExtent3D extent = allocInfo->m_ImageInfo.extent; - - VkImageSubresourceLayers subresourceLayers = { - VK_IMAGE_ASPECT_COLOR_BIT, - 0, - 0, 1 - }; - - for(uint32_t mip = 0; mip < allocInfo->m_ImageInfo.mipLevels; ++ mip) - { - subresourceLayers.mipLevel = mip; - - VkImageCopy imageCopy{ - subresourceLayers, - offset, - subresourceLayers, - offset, - extent - }; - - imageCopies.push_back(imageCopy); - - extent.width = std::max(uint32_t(1), extent.width >> 1); - extent.height = std::max(uint32_t(1), extent.height >> 1); - extent.depth = std::max(uint32_t(1), extent.depth >> 1); - } - - vkCmdCopyImage( - g_hTemporaryCommandBuffer, - allocInfo->m_Image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - allocInfo->m_NewImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - (uint32_t)imageCopies.size(), imageCopies.data()); - } - else if(allocInfo->m_Buffer) - { - VkBufferCopy region = { - 0, - 0, - allocInfo->m_BufferInfo.size }; - - vkCmdCopyBuffer(g_hTemporaryCommandBuffer, - allocInfo->m_Buffer, allocInfo->m_NewBuffer, - 1, ®ion); - } - } - - if(!finalizeImageBarriers.empty() || wantsMemoryBarrier) - { - const uint32_t memoryBarrierCount = wantsMemoryBarrier ? 1 : 0; - - vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, finalizeSrcStageMask, finalizeDstStageMask, 0, - memoryBarrierCount, &finalizeMemoryBarrier, - 0, nullptr, - (uint32_t)finalizeImageBarriers.size(), finalizeImageBarriers.data()); - } -} - - -static void TestDefragmentationIncrementalBasic() -{ - wprintf(L"Test defragmentation incremental basic\n"); - - std::vector<AllocInfo> allocations; - - // Create that many allocations to surely fill 3 new blocks of 256 MB. - const std::array<uint32_t, 3> imageSizes = { 256, 512, 1024 }; - const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024; - const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024; - const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024; - const size_t imageCount = totalSize / ((size_t)imageSizes[0] * imageSizes[0] * 4) / 2; - const size_t bufCount = (size_t)(totalSize / bufSizeMin) / 2; - const size_t percentToLeave = 30; - RandomNumberGenerator rand = { 234522 }; - - VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - allocCreateInfo.flags = 0; - - // Create all intended images. - for(size_t i = 0; i < imageCount; ++i) - { - const uint32_t size = imageSizes[rand.Generate() % 3]; - - imageInfo.extent.width = size; - imageInfo.extent.height = size; - - AllocInfo alloc; - alloc.CreateImage(imageInfo, allocCreateInfo, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - alloc.m_StartValue = 0; - - allocations.push_back(alloc); - } - - // And all buffers - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - - for(size_t i = 0; i < bufCount; ++i) - { - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); - bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - AllocInfo alloc; - alloc.CreateBuffer(bufCreateInfo, allocCreateInfo); - alloc.m_StartValue = 0; - - allocations.push_back(alloc); - } - - // Destroy some percentage of them. - { - const size_t allocationsToDestroy = round_div<size_t>((imageCount + bufCount) * (100 - percentToLeave), 100); - for(size_t i = 0; i < allocationsToDestroy; ++i) - { - const size_t index = rand.Generate() % allocations.size(); - allocations[index].Destroy(); - allocations.erase(allocations.begin() + index); - } - } - - { - // Set our user data pointers. A real application should probably be more clever here - const size_t allocationCount = allocations.size(); - for(size_t i = 0; i < allocationCount; ++i) - { - AllocInfo &alloc = allocations[i]; - vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc); - } - } - - // Fill them with meaningful data. - UploadGpuData(allocations.data(), allocations.size()); - - wchar_t fileName[MAX_PATH]; - swprintf_s(fileName, L"GPU_defragmentation_incremental_basic_A_before.json"); - SaveAllocatorStatsToFile(fileName); - - // Defragment using GPU only. - { - const size_t allocCount = allocations.size(); - - std::vector<VmaAllocation> allocationPtrs; - - for(size_t i = 0; i < allocCount; ++i) - { - allocationPtrs.push_back(allocations[i].m_Allocation); - } - - const size_t movableAllocCount = allocationPtrs.size(); - - VmaDefragmentationInfo2 defragInfo = {}; - defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_INCREMENTAL; - defragInfo.allocationCount = (uint32_t)movableAllocCount; - defragInfo.pAllocations = allocationPtrs.data(); - defragInfo.maxGpuBytesToMove = VK_WHOLE_SIZE; - defragInfo.maxGpuAllocationsToMove = UINT32_MAX; - - VmaDefragmentationStats stats = {}; - VmaDefragmentationContext ctx = VK_NULL_HANDLE; - VkResult res = vmaDefragmentationBegin(g_hAllocator, &defragInfo, &stats, &ctx); - TEST(res >= VK_SUCCESS); - - res = VK_NOT_READY; - - std::vector<VmaDefragmentationPassMoveInfo> moveInfo; - moveInfo.resize(movableAllocCount); - - while(res == VK_NOT_READY) - { - VmaDefragmentationPassInfo stepInfo = {}; - stepInfo.pMoves = moveInfo.data(); - stepInfo.moveCount = (uint32_t)moveInfo.size(); - - res = vmaBeginDefragmentationPass(g_hAllocator, ctx, &stepInfo); - TEST(res >= VK_SUCCESS); - - BeginSingleTimeCommands(); - std::vector<void*> newHandles; - ProcessDefragmentationStepInfo(stepInfo); - EndSingleTimeCommands(); - - res = vmaEndDefragmentationPass(g_hAllocator, ctx); - - // Destroy old buffers/images and replace them with new handles. - for(size_t i = 0; i < stepInfo.moveCount; ++i) - { - VmaAllocation const alloc = stepInfo.pMoves[i].allocation; - VmaAllocationInfo vmaAllocInfo; - vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo); - AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData; - if(allocInfo->m_Buffer) - { - assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage); - vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs); - allocInfo->m_Buffer = allocInfo->m_NewBuffer; - allocInfo->m_NewBuffer = VK_NULL_HANDLE; - } - else if(allocInfo->m_Image) - { - assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer); - vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs); - allocInfo->m_Image = allocInfo->m_NewImage; - allocInfo->m_NewImage = VK_NULL_HANDLE; - } - else - assert(0); - } - } - - TEST(res >= VK_SUCCESS); - vmaDefragmentationEnd(g_hAllocator, ctx); - - // If corruption detection is enabled, GPU defragmentation may not work on - // memory types that have this detection active, e.g. on Intel. -#if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0 - TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0); - TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0); -#endif - } - - //ValidateGpuData(allocations.data(), allocations.size()); - - swprintf_s(fileName, L"GPU_defragmentation_incremental_basic_B_after.json"); - SaveAllocatorStatsToFile(fileName); - - // Destroy all remaining buffers and images. - for(size_t i = allocations.size(); i--; ) - { - allocations[i].Destroy(); - } -} - -void TestDefragmentationIncrementalComplex() -{ - wprintf(L"Test defragmentation incremental complex\n"); - - std::vector<AllocInfo> allocations; - - // Create that many allocations to surely fill 3 new blocks of 256 MB. - const std::array<uint32_t, 3> imageSizes = { 256, 512, 1024 }; - const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024; - const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024; - const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024; - const size_t imageCount = (size_t)(totalSize / (imageSizes[0] * imageSizes[0] * 4)) / 2; - const size_t bufCount = (size_t)(totalSize / bufSizeMin) / 2; - const size_t percentToLeave = 30; - RandomNumberGenerator rand = { 234522 }; - - VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - allocCreateInfo.flags = 0; - - // Create all intended images. - for(size_t i = 0; i < imageCount; ++i) - { - const uint32_t size = imageSizes[rand.Generate() % 3]; - - imageInfo.extent.width = size; - imageInfo.extent.height = size; - - AllocInfo alloc; - alloc.CreateImage(imageInfo, allocCreateInfo, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - alloc.m_StartValue = 0; - - allocations.push_back(alloc); - } - - // And all buffers - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - - for(size_t i = 0; i < bufCount; ++i) - { - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); - bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - AllocInfo alloc; - alloc.CreateBuffer(bufCreateInfo, allocCreateInfo); - alloc.m_StartValue = 0; - - allocations.push_back(alloc); - } - - // Destroy some percentage of them. - { - const size_t allocationsToDestroy = round_div<size_t>((imageCount + bufCount) * (100 - percentToLeave), 100); - for(size_t i = 0; i < allocationsToDestroy; ++i) - { - const size_t index = rand.Generate() % allocations.size(); - allocations[index].Destroy(); - allocations.erase(allocations.begin() + index); - } - } - - { - // Set our user data pointers. A real application should probably be more clever here - const size_t allocationCount = allocations.size(); - for(size_t i = 0; i < allocationCount; ++i) - { - AllocInfo &alloc = allocations[i]; - vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc); - } - } - - // Fill them with meaningful data. - UploadGpuData(allocations.data(), allocations.size()); - - wchar_t fileName[MAX_PATH]; - swprintf_s(fileName, L"GPU_defragmentation_incremental_complex_A_before.json"); - SaveAllocatorStatsToFile(fileName); - - std::vector<AllocInfo> additionalAllocations; - -#define MakeAdditionalAllocation() \ - do { \ - { \ - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); \ - bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; \ - \ - AllocInfo alloc; \ - alloc.CreateBuffer(bufCreateInfo, allocCreateInfo); \ - \ - additionalAllocations.push_back(alloc); \ - } \ - } while(0) - - // Defragment using GPU only. - { - const size_t allocCount = allocations.size(); - - std::vector<VmaAllocation> allocationPtrs; - - for(size_t i = 0; i < allocCount; ++i) - { - VmaAllocationInfo allocInfo = {}; - vmaGetAllocationInfo(g_hAllocator, allocations[i].m_Allocation, &allocInfo); - - allocationPtrs.push_back(allocations[i].m_Allocation); - } - - const size_t movableAllocCount = allocationPtrs.size(); - - VmaDefragmentationInfo2 defragInfo = {}; - defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_INCREMENTAL; - defragInfo.allocationCount = (uint32_t)movableAllocCount; - defragInfo.pAllocations = allocationPtrs.data(); - defragInfo.maxGpuBytesToMove = VK_WHOLE_SIZE; - defragInfo.maxGpuAllocationsToMove = UINT32_MAX; - - VmaDefragmentationStats stats = {}; - VmaDefragmentationContext ctx = VK_NULL_HANDLE; - VkResult res = vmaDefragmentationBegin(g_hAllocator, &defragInfo, &stats, &ctx); - TEST(res >= VK_SUCCESS); - - res = VK_NOT_READY; - - std::vector<VmaDefragmentationPassMoveInfo> moveInfo; - moveInfo.resize(movableAllocCount); - - MakeAdditionalAllocation(); - - while(res == VK_NOT_READY) - { - VmaDefragmentationPassInfo stepInfo = {}; - stepInfo.pMoves = moveInfo.data(); - stepInfo.moveCount = (uint32_t)moveInfo.size(); - - res = vmaBeginDefragmentationPass(g_hAllocator, ctx, &stepInfo); - TEST(res >= VK_SUCCESS); - - MakeAdditionalAllocation(); - - BeginSingleTimeCommands(); - ProcessDefragmentationStepInfo(stepInfo); - EndSingleTimeCommands(); - - res = vmaEndDefragmentationPass(g_hAllocator, ctx); - - // Destroy old buffers/images and replace them with new handles. - for(size_t i = 0; i < stepInfo.moveCount; ++i) - { - VmaAllocation const alloc = stepInfo.pMoves[i].allocation; - VmaAllocationInfo vmaAllocInfo; - vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo); - AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData; - if(allocInfo->m_Buffer) - { - assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage); - vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs); - allocInfo->m_Buffer = allocInfo->m_NewBuffer; - allocInfo->m_NewBuffer = VK_NULL_HANDLE; - } - else if(allocInfo->m_Image) - { - assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer); - vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs); - allocInfo->m_Image = allocInfo->m_NewImage; - allocInfo->m_NewImage = VK_NULL_HANDLE; - } - else - assert(0); - } - - MakeAdditionalAllocation(); - } - - TEST(res >= VK_SUCCESS); - vmaDefragmentationEnd(g_hAllocator, ctx); - - // If corruption detection is enabled, GPU defragmentation may not work on - // memory types that have this detection active, e.g. on Intel. -#if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0 - TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0); - TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0); -#endif - } - - //ValidateGpuData(allocations.data(), allocations.size()); - - swprintf_s(fileName, L"GPU_defragmentation_incremental_complex_B_after.json"); - SaveAllocatorStatsToFile(fileName); - - // Destroy all remaining buffers. - for(size_t i = allocations.size(); i--; ) - { - allocations[i].Destroy(); - } - - for(size_t i = additionalAllocations.size(); i--; ) - { - additionalAllocations[i].Destroy(); - } -} - - -static void TestUserData() -{ - VkResult res; - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT; - bufCreateInfo.size = 0x10000; - - for(uint32_t testIndex = 0; testIndex < 2; ++testIndex) - { - // Opaque pointer - { - - void* numberAsPointer = (void*)(size_t)0xC2501FF3u; - void* pointerToSomething = &res; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - allocCreateInfo.pUserData = numberAsPointer; - if(testIndex == 1) - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - - VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); - TEST(res == VK_SUCCESS); - TEST(allocInfo.pUserData = numberAsPointer); - - vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); - TEST(allocInfo.pUserData == numberAsPointer); - - vmaSetAllocationUserData(g_hAllocator, alloc, pointerToSomething); - vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); - TEST(allocInfo.pUserData == pointerToSomething); - - vmaDestroyBuffer(g_hAllocator, buf, alloc); - } - - // String - { - const char* name1 = "Buffer name \\\"\'<>&% \nSecond line .,;="; - const char* name2 = "2"; - const size_t name1Len = strlen(name1); - - char* name1Buf = new char[name1Len + 1]; - strcpy_s(name1Buf, name1Len + 1, name1); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - allocCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT; - allocCreateInfo.pUserData = name1Buf; - if(testIndex == 1) - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - - VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); - TEST(res == VK_SUCCESS); - TEST(allocInfo.pUserData != nullptr && allocInfo.pUserData != name1Buf); - TEST(strcmp(name1, (const char*)allocInfo.pUserData) == 0); - - delete[] name1Buf; - - vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); - TEST(strcmp(name1, (const char*)allocInfo.pUserData) == 0); - - vmaSetAllocationUserData(g_hAllocator, alloc, (void*)name2); - vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); - TEST(strcmp(name2, (const char*)allocInfo.pUserData) == 0); - - vmaSetAllocationUserData(g_hAllocator, alloc, nullptr); - vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); - TEST(allocInfo.pUserData == nullptr); - - vmaDestroyBuffer(g_hAllocator, buf, alloc); - } - } -} - -static void TestInvalidAllocations() -{ - VkResult res; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - - // Try to allocate 0 bytes. - { - VkMemoryRequirements memReq = {}; - memReq.size = 0; // !!! - memReq.alignment = 4; - memReq.memoryTypeBits = UINT32_MAX; - VmaAllocation alloc = VK_NULL_HANDLE; - res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr); - TEST(res == VK_ERROR_VALIDATION_FAILED_EXT && alloc == VK_NULL_HANDLE); - } - - // Try to create buffer with size = 0. - { - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - bufCreateInfo.size = 0; // !!! - VkBuffer buf = VK_NULL_HANDLE; - VmaAllocation alloc = VK_NULL_HANDLE; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, nullptr); - TEST(res == VK_ERROR_VALIDATION_FAILED_EXT && buf == VK_NULL_HANDLE && alloc == VK_NULL_HANDLE); - } - - // Try to create image with one dimension = 0. - { - VkImageCreateInfo imageCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imageCreateInfo.format = VK_FORMAT_B8G8R8A8_UNORM; - imageCreateInfo.extent.width = 128; - imageCreateInfo.extent.height = 0; // !!! - imageCreateInfo.extent.depth = 1; - imageCreateInfo.mipLevels = 1; - imageCreateInfo.arrayLayers = 1; - imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR; - imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; - VkImage image = VK_NULL_HANDLE; - VmaAllocation alloc = VK_NULL_HANDLE; - res = vmaCreateImage(g_hAllocator, &imageCreateInfo, &allocCreateInfo, &image, &alloc, nullptr); - TEST(res == VK_ERROR_VALIDATION_FAILED_EXT && image == VK_NULL_HANDLE && alloc == VK_NULL_HANDLE); - } -} - -static void TestMemoryRequirements() -{ - VkResult res; - VkBuffer buf; - VmaAllocation alloc; - VmaAllocationInfo allocInfo; - - const VkPhysicalDeviceMemoryProperties* memProps; - vmaGetMemoryProperties(g_hAllocator, &memProps); - - VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - bufInfo.size = 128; - - VmaAllocationCreateInfo allocCreateInfo = {}; - - // No requirements. - res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); - TEST(res == VK_SUCCESS); - vmaDestroyBuffer(g_hAllocator, buf, alloc); - - // Usage. - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - allocCreateInfo.requiredFlags = 0; - allocCreateInfo.preferredFlags = 0; - allocCreateInfo.memoryTypeBits = UINT32_MAX; - - res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); - TEST(res == VK_SUCCESS); - TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); - vmaDestroyBuffer(g_hAllocator, buf, alloc); - - // Required flags, preferred flags. - allocCreateInfo.usage = VMA_MEMORY_USAGE_UNKNOWN; - allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; - allocCreateInfo.memoryTypeBits = 0; - - res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); - TEST(res == VK_SUCCESS); - TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); - TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - vmaDestroyBuffer(g_hAllocator, buf, alloc); - - // memoryTypeBits. - const uint32_t memType = allocInfo.memoryType; - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - allocCreateInfo.requiredFlags = 0; - allocCreateInfo.preferredFlags = 0; - allocCreateInfo.memoryTypeBits = 1u << memType; - - res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); - TEST(res == VK_SUCCESS); - TEST(allocInfo.memoryType == memType); - vmaDestroyBuffer(g_hAllocator, buf, alloc); - -} - -static void TestGetAllocatorInfo() -{ - wprintf(L"Test vnaGetAllocatorInfo\n"); - - VmaAllocatorInfo allocInfo = {}; - vmaGetAllocatorInfo(g_hAllocator, &allocInfo); - TEST(allocInfo.instance == g_hVulkanInstance); - TEST(allocInfo.physicalDevice == g_hPhysicalDevice); - TEST(allocInfo.device == g_hDevice); -} - -static void TestBasics() -{ - wprintf(L"Test basics\n"); - - VkResult res; - - TestGetAllocatorInfo(); - - TestMemoryRequirements(); - - // Lost allocation - { - VmaAllocation alloc = VK_NULL_HANDLE; - vmaCreateLostAllocation(g_hAllocator, &alloc); - TEST(alloc != VK_NULL_HANDLE); - - VmaAllocationInfo allocInfo; - vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); - TEST(allocInfo.deviceMemory == VK_NULL_HANDLE); - TEST(allocInfo.size == 0); - - vmaFreeMemory(g_hAllocator, alloc); - } - - // Allocation that is MAPPED and not necessarily HOST_VISIBLE. - { - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT; - bufCreateInfo.size = 128; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - - VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); - TEST(res == VK_SUCCESS); - - vmaDestroyBuffer(g_hAllocator, buf, alloc); - - // Same with OWN_MEMORY. - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); - TEST(res == VK_SUCCESS); - - vmaDestroyBuffer(g_hAllocator, buf, alloc); - } - - TestUserData(); - - TestInvalidAllocations(); -} - -static void TestAllocationVersusResourceSize() -{ - wprintf(L"Test allocation versus resource size\n"); - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.size = 22921; // Prime number - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - - for(uint32_t i = 0; i < 2; ++i) - { - allocCreateInfo.flags = (i == 1) ? VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT : 0; - - AllocInfo info; - info.CreateBuffer(bufCreateInfo, allocCreateInfo); - - VmaAllocationInfo allocInfo = {}; - vmaGetAllocationInfo(g_hAllocator, info.m_Allocation, &allocInfo); - //wprintf(L" Buffer size = %llu, allocation size = %llu\n", bufCreateInfo.size, allocInfo.size); - - // Map and test accessing entire area of the allocation, not only the buffer. - void* mappedPtr = nullptr; - VkResult res = vmaMapMemory(g_hAllocator, info.m_Allocation, &mappedPtr); - TEST(res == VK_SUCCESS); - - memset(mappedPtr, 0xCC, (size_t)allocInfo.size); - - vmaUnmapMemory(g_hAllocator, info.m_Allocation); - - info.Destroy(); - } -} - -static void TestPool_MinBlockCount() -{ -#if defined(VMA_DEBUG_MARGIN) && VMA_DEBUG_MARGIN > 0 - return; -#endif - - wprintf(L"Test Pool MinBlockCount\n"); - VkResult res; - - static const VkDeviceSize ALLOC_SIZE = 512ull * 1024; - static const VkDeviceSize BLOCK_SIZE = ALLOC_SIZE * 2; // Each block can fit 2 allocations. - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_COPY; - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - bufCreateInfo.size = ALLOC_SIZE; - - VmaPoolCreateInfo poolCreateInfo = {}; - poolCreateInfo.blockSize = BLOCK_SIZE; - poolCreateInfo.minBlockCount = 2; // At least 2 blocks always present. - res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex); - TEST(res == VK_SUCCESS); - - VmaPool pool = VK_NULL_HANDLE; - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - TEST(res == VK_SUCCESS && pool != VK_NULL_HANDLE); - - // Check that there are 2 blocks preallocated as requested. - VmaPoolStats begPoolStats = {}; - vmaGetPoolStats(g_hAllocator, pool, &begPoolStats); - TEST(begPoolStats.blockCount == 2 && begPoolStats.allocationCount == 0 && begPoolStats.size == BLOCK_SIZE * 2); - - // Allocate 5 buffers to create 3 blocks. - static const uint32_t BUF_COUNT = 5; - allocCreateInfo.pool = pool; - std::vector<AllocInfo> allocs(BUF_COUNT); - for(uint32_t i = 0; i < BUF_COUNT; ++i) - { - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &allocs[i].m_Buffer, &allocs[i].m_Allocation, nullptr); - TEST(res == VK_SUCCESS && allocs[i].m_Buffer != VK_NULL_HANDLE && allocs[i].m_Allocation != VK_NULL_HANDLE); - } - - // Check that there are really 3 blocks. - VmaPoolStats poolStats2 = {}; - vmaGetPoolStats(g_hAllocator, pool, &poolStats2); - TEST(poolStats2.blockCount == 3 && poolStats2.allocationCount == BUF_COUNT && poolStats2.size == BLOCK_SIZE * 3); - - // Free two first allocations to make one block empty. - allocs[0].Destroy(); - allocs[1].Destroy(); - - // Check that there are still 3 blocks due to hysteresis. - VmaPoolStats poolStats3 = {}; - vmaGetPoolStats(g_hAllocator, pool, &poolStats3); - TEST(poolStats3.blockCount == 3 && poolStats3.allocationCount == BUF_COUNT - 2 && poolStats2.size == BLOCK_SIZE * 3); - - // Free the last allocation to make second block empty. - allocs[BUF_COUNT - 1].Destroy(); - - // Check that there are now 2 blocks only. - VmaPoolStats poolStats4 = {}; - vmaGetPoolStats(g_hAllocator, pool, &poolStats4); - TEST(poolStats4.blockCount == 2 && poolStats4.allocationCount == BUF_COUNT - 3 && poolStats4.size == BLOCK_SIZE * 2); - - // Cleanup. - for(size_t i = allocs.size(); i--; ) - { - allocs[i].Destroy(); - } - vmaDestroyPool(g_hAllocator, pool); -} - -static void TestPool_MinAllocationAlignment() -{ - wprintf(L"Test Pool MinAllocationAlignment\n"); - VkResult res; - - static const VkDeviceSize ALLOC_SIZE = 32; - static const VkDeviceSize BLOCK_SIZE = 1024 * 1024; - static const VkDeviceSize MIN_ALLOCATION_ALIGNMENT = 64 * 1024; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_COPY; - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - bufCreateInfo.size = ALLOC_SIZE; - - VmaPoolCreateInfo poolCreateInfo = {}; - poolCreateInfo.blockSize = BLOCK_SIZE; - poolCreateInfo.minAllocationAlignment = MIN_ALLOCATION_ALIGNMENT; - res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex); - TEST(res == VK_SUCCESS); - - VmaPool pool = VK_NULL_HANDLE; - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - TEST(res == VK_SUCCESS && pool != VK_NULL_HANDLE); - - static const uint32_t BUF_COUNT = 4; - allocCreateInfo = {}; - allocCreateInfo.pool = pool; - std::vector<AllocInfo> allocs(BUF_COUNT); - for(uint32_t i = 0; i < BUF_COUNT; ++i) - { - VmaAllocationInfo allocInfo = {}; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &allocs[i].m_Buffer, &allocs[i].m_Allocation, &allocInfo); - TEST(res == VK_SUCCESS && allocs[i].m_Buffer != VK_NULL_HANDLE && allocs[i].m_Allocation != VK_NULL_HANDLE); - TEST(allocInfo.offset % MIN_ALLOCATION_ALIGNMENT == 0); - } - - // Cleanup. - for(size_t i = allocs.size(); i--; ) - { - allocs[i].Destroy(); - } - vmaDestroyPool(g_hAllocator, pool); -} - -void TestHeapSizeLimit() -{ - const VkDeviceSize HEAP_SIZE_LIMIT = 100ull * 1024 * 1024; // 100 MB - const VkDeviceSize BLOCK_SIZE = 10ull * 1024 * 1024; // 10 MB - - VkDeviceSize heapSizeLimit[VK_MAX_MEMORY_HEAPS]; - for(uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i) - { - heapSizeLimit[i] = HEAP_SIZE_LIMIT; - } - - VmaAllocatorCreateInfo allocatorCreateInfo = {}; - allocatorCreateInfo.physicalDevice = g_hPhysicalDevice; - allocatorCreateInfo.device = g_hDevice; - allocatorCreateInfo.instance = g_hVulkanInstance; - allocatorCreateInfo.pHeapSizeLimit = heapSizeLimit; - - VmaAllocator hAllocator; - VkResult res = vmaCreateAllocator(&allocatorCreateInfo, &hAllocator); - TEST(res == VK_SUCCESS); - - struct Item - { - VkBuffer hBuf; - VmaAllocation hAlloc; - }; - std::vector<Item> items; - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - - // 1. Allocate two blocks of dedicated memory, half the size of BLOCK_SIZE. - VmaAllocationInfo dedicatedAllocInfo; - { - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - - bufCreateInfo.size = BLOCK_SIZE / 2; - - for(size_t i = 0; i < 2; ++i) - { - Item item; - res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, &dedicatedAllocInfo); - TEST(res == VK_SUCCESS); - items.push_back(item); - } - } - - // Create pool to make sure allocations must be out of this memory type. - VmaPoolCreateInfo poolCreateInfo = {}; - poolCreateInfo.memoryTypeIndex = dedicatedAllocInfo.memoryType; - poolCreateInfo.blockSize = BLOCK_SIZE; - - VmaPool hPool; - res = vmaCreatePool(hAllocator, &poolCreateInfo, &hPool); - TEST(res == VK_SUCCESS); - - // 2. Allocate normal buffers from all the remaining memory. - { - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.pool = hPool; - - bufCreateInfo.size = BLOCK_SIZE / 2; - - const size_t bufCount = ((HEAP_SIZE_LIMIT / BLOCK_SIZE) - 1) * 2; - for(size_t i = 0; i < bufCount; ++i) - { - Item item; - res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, nullptr); - TEST(res == VK_SUCCESS); - items.push_back(item); - } - } - - // 3. Allocation of one more (even small) buffer should fail. - { - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.pool = hPool; - - bufCreateInfo.size = 128; - - VkBuffer hBuf; - VmaAllocation hAlloc; - res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &hBuf, &hAlloc, nullptr); - TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY); - } - - // Destroy everything. - for(size_t i = items.size(); i--; ) - { - vmaDestroyBuffer(hAllocator, items[i].hBuf, items[i].hAlloc); - } - - vmaDestroyPool(hAllocator, hPool); - - vmaDestroyAllocator(hAllocator); -} - -#if VMA_DEBUG_MARGIN -static void TestDebugMargin() -{ - if(VMA_DEBUG_MARGIN == 0) - { - return; - } - - VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - - // Create few buffers of different size. - const size_t BUF_COUNT = 10; - BufferInfo buffers[BUF_COUNT]; - VmaAllocationInfo allocInfo[BUF_COUNT]; - for(size_t i = 0; i < 10; ++i) - { - bufInfo.size = (VkDeviceSize)(i + 1) * 64; - // Last one will be mapped. - allocCreateInfo.flags = (i == BUF_COUNT - 1) ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0; - - VkResult res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buffers[i].Buffer, &buffers[i].Allocation, &allocInfo[i]); - TEST(res == VK_SUCCESS); - // Margin is preserved also at the beginning of a block. - TEST(allocInfo[i].offset >= VMA_DEBUG_MARGIN); - - if(i == BUF_COUNT - 1) - { - // Fill with data. - TEST(allocInfo[i].pMappedData != nullptr); - // Uncomment this "+ 1" to overwrite past end of allocation and check corruption detection. - memset(allocInfo[i].pMappedData, 0xFF, bufInfo.size /* + 1 */); - } - } - - // Check if their offsets preserve margin between them. - std::sort(allocInfo, allocInfo + BUF_COUNT, [](const VmaAllocationInfo& lhs, const VmaAllocationInfo& rhs) -> bool - { - if(lhs.deviceMemory != rhs.deviceMemory) - { - return lhs.deviceMemory < rhs.deviceMemory; - } - return lhs.offset < rhs.offset; - }); - for(size_t i = 1; i < BUF_COUNT; ++i) - { - if(allocInfo[i].deviceMemory == allocInfo[i - 1].deviceMemory) - { - TEST(allocInfo[i].offset >= allocInfo[i - 1].offset + VMA_DEBUG_MARGIN); - } - } - - VkResult res = vmaCheckCorruption(g_hAllocator, UINT32_MAX); - TEST(res == VK_SUCCESS); - - // Destroy all buffers. - for(size_t i = BUF_COUNT; i--; ) - { - vmaDestroyBuffer(g_hAllocator, buffers[i].Buffer, buffers[i].Allocation); - } -} -#endif - -static void TestLinearAllocator() -{ - wprintf(L"Test linear allocator\n"); - - RandomNumberGenerator rand{645332}; - - VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - sampleBufCreateInfo.size = 1024; // Whatever. - sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - - VmaAllocationCreateInfo sampleAllocCreateInfo = {}; - sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - VmaPoolCreateInfo poolCreateInfo = {}; - VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); - TEST(res == VK_SUCCESS); - - poolCreateInfo.blockSize = 1024 * 300; - poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT; - poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; - - VmaPool pool = nullptr; - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - TEST(res == VK_SUCCESS); - - VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.pool = pool; - - constexpr size_t maxBufCount = 100; - std::vector<BufferInfo> bufInfo; - - constexpr VkDeviceSize bufSizeMin = 16; - constexpr VkDeviceSize bufSizeMax = 1024; - VmaAllocationInfo allocInfo; - VkDeviceSize prevOffset = 0; - - // Test one-time free. - for(size_t i = 0; i < 2; ++i) - { - // Allocate number of buffers of varying size that surely fit into this block. - VkDeviceSize bufSumSize = 0; - for(size_t i = 0; i < maxBufCount; ++i) - { - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - TEST(i == 0 || allocInfo.offset > prevOffset); - bufInfo.push_back(newBufInfo); - prevOffset = allocInfo.offset; - bufSumSize += bufCreateInfo.size; - } - - // Validate pool stats. - VmaPoolStats stats; - vmaGetPoolStats(g_hAllocator, pool, &stats); - TEST(stats.size == poolCreateInfo.blockSize); - TEST(stats.unusedSize = poolCreateInfo.blockSize - bufSumSize); - TEST(stats.allocationCount == bufInfo.size()); - - // Destroy the buffers in random order. - while(!bufInfo.empty()) - { - const size_t indexToDestroy = rand.Generate() % bufInfo.size(); - const BufferInfo& currBufInfo = bufInfo[indexToDestroy]; - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.erase(bufInfo.begin() + indexToDestroy); - } - } - - // Test stack. - { - // Allocate number of buffers of varying size that surely fit into this block. - for(size_t i = 0; i < maxBufCount; ++i) - { - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - TEST(i == 0 || allocInfo.offset > prevOffset); - bufInfo.push_back(newBufInfo); - prevOffset = allocInfo.offset; - } - - // Destroy few buffers from top of the stack. - for(size_t i = 0; i < maxBufCount / 5; ++i) - { - const BufferInfo& currBufInfo = bufInfo.back(); - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.pop_back(); - } - - // Create some more - for(size_t i = 0; i < maxBufCount / 5; ++i) - { - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - TEST(i == 0 || allocInfo.offset > prevOffset); - bufInfo.push_back(newBufInfo); - prevOffset = allocInfo.offset; - } - - // Destroy the buffers in reverse order. - while(!bufInfo.empty()) - { - const BufferInfo& currBufInfo = bufInfo.back(); - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.pop_back(); - } - } - - // Test ring buffer. - { - // Allocate number of buffers that surely fit into this block. - bufCreateInfo.size = bufSizeMax; - for(size_t i = 0; i < maxBufCount; ++i) - { - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - TEST(i == 0 || allocInfo.offset > prevOffset); - bufInfo.push_back(newBufInfo); - prevOffset = allocInfo.offset; - } - - // Free and allocate new buffers so many times that we make sure we wrap-around at least once. - const size_t buffersPerIter = maxBufCount / 10 - 1; - const size_t iterCount = poolCreateInfo.blockSize / bufCreateInfo.size / buffersPerIter * 2; - for(size_t iter = 0; iter < iterCount; ++iter) - { - for(size_t bufPerIter = 0; bufPerIter < buffersPerIter; ++bufPerIter) - { - const BufferInfo& currBufInfo = bufInfo.front(); - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.erase(bufInfo.begin()); - } - for(size_t bufPerIter = 0; bufPerIter < buffersPerIter; ++bufPerIter) - { - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - } - } - - // Allocate buffers until we reach out-of-memory. - uint32_t debugIndex = 0; - while(res == VK_SUCCESS) - { - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - if(res == VK_SUCCESS) - { - bufInfo.push_back(newBufInfo); - } - else - { - TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY); - } - ++debugIndex; - } - - // Destroy the buffers in random order. - while(!bufInfo.empty()) - { - const size_t indexToDestroy = rand.Generate() % bufInfo.size(); - const BufferInfo& currBufInfo = bufInfo[indexToDestroy]; - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.erase(bufInfo.begin() + indexToDestroy); - } - } - - // Test double stack. - { - // Allocate number of buffers of varying size that surely fit into this block, alternate from bottom/top. - VkDeviceSize prevOffsetLower = 0; - VkDeviceSize prevOffsetUpper = poolCreateInfo.blockSize; - for(size_t i = 0; i < maxBufCount; ++i) - { - const bool upperAddress = (i % 2) != 0; - if(upperAddress) - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; - else - allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - if(upperAddress) - { - TEST(allocInfo.offset < prevOffsetUpper); - prevOffsetUpper = allocInfo.offset; - } - else - { - TEST(allocInfo.offset >= prevOffsetLower); - prevOffsetLower = allocInfo.offset; - } - TEST(prevOffsetLower < prevOffsetUpper); - bufInfo.push_back(newBufInfo); - } - - // Destroy few buffers from top of the stack. - for(size_t i = 0; i < maxBufCount / 5; ++i) - { - const BufferInfo& currBufInfo = bufInfo.back(); - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.pop_back(); - } - - // Create some more - for(size_t i = 0; i < maxBufCount / 5; ++i) - { - const bool upperAddress = (i % 2) != 0; - if(upperAddress) - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; - else - allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - } - - // Destroy the buffers in reverse order. - while(!bufInfo.empty()) - { - const BufferInfo& currBufInfo = bufInfo.back(); - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.pop_back(); - } - - // Create buffers on both sides until we reach out of memory. - prevOffsetLower = 0; - prevOffsetUpper = poolCreateInfo.blockSize; - res = VK_SUCCESS; - for(size_t i = 0; res == VK_SUCCESS; ++i) - { - const bool upperAddress = (i % 2) != 0; - if(upperAddress) - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; - else - allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - if(res == VK_SUCCESS) - { - if(upperAddress) - { - TEST(allocInfo.offset < prevOffsetUpper); - prevOffsetUpper = allocInfo.offset; - } - else - { - TEST(allocInfo.offset >= prevOffsetLower); - prevOffsetLower = allocInfo.offset; - } - TEST(prevOffsetLower < prevOffsetUpper); - bufInfo.push_back(newBufInfo); - } - } - - // Destroy the buffers in random order. - while(!bufInfo.empty()) - { - const size_t indexToDestroy = rand.Generate() % bufInfo.size(); - const BufferInfo& currBufInfo = bufInfo[indexToDestroy]; - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.erase(bufInfo.begin() + indexToDestroy); - } - - // Create buffers on upper side only, constant size, until we reach out of memory. - prevOffsetUpper = poolCreateInfo.blockSize; - res = VK_SUCCESS; - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; - bufCreateInfo.size = bufSizeMax; - for(size_t i = 0; res == VK_SUCCESS; ++i) - { - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - if(res == VK_SUCCESS) - { - TEST(allocInfo.offset < prevOffsetUpper); - prevOffsetUpper = allocInfo.offset; - bufInfo.push_back(newBufInfo); - } - } - - // Destroy the buffers in reverse order. - while(!bufInfo.empty()) - { - const BufferInfo& currBufInfo = bufInfo.back(); - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.pop_back(); - } - } - - // Test ring buffer with lost allocations. - { - // Allocate number of buffers until pool is full. - // Notice CAN_BECOME_LOST flag and call to vmaSetCurrentFrameIndex. - allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT; - res = VK_SUCCESS; - for(size_t i = 0; res == VK_SUCCESS; ++i) - { - vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); - - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); - - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - if(res == VK_SUCCESS) - bufInfo.push_back(newBufInfo); - } - - // Free first half of it. - { - const size_t buffersToDelete = bufInfo.size() / 2; - for(size_t i = 0; i < buffersToDelete; ++i) - { - vmaDestroyBuffer(g_hAllocator, bufInfo[i].Buffer, bufInfo[i].Allocation); - } - bufInfo.erase(bufInfo.begin(), bufInfo.begin() + buffersToDelete); - } - - // Allocate number of buffers until pool is full again. - // This way we make sure ring buffers wraps around, front in in the middle. - res = VK_SUCCESS; - for(size_t i = 0; res == VK_SUCCESS; ++i) - { - vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); - - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); - - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - if(res == VK_SUCCESS) - bufInfo.push_back(newBufInfo); - } - - VkDeviceSize firstNewOffset; - { - vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); - - // Allocate a large buffer with CAN_MAKE_OTHER_LOST. - allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; - bufCreateInfo.size = bufSizeMax; - - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - firstNewOffset = allocInfo.offset; - - // Make sure at least one buffer from the beginning became lost. - vmaGetAllocationInfo(g_hAllocator, bufInfo[0].Allocation, &allocInfo); - TEST(allocInfo.deviceMemory == VK_NULL_HANDLE); - } - -#if 0 // TODO Fix and uncomment. Failing on Intel. - // Allocate more buffers that CAN_MAKE_OTHER_LOST until we wrap-around with this. - size_t newCount = 1; - for(;;) - { - vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); - - bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); - - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - ++newCount; - if(allocInfo.offset < firstNewOffset) - break; - } -#endif - - // Delete buffers that are lost. - for(size_t i = bufInfo.size(); i--; ) - { - vmaGetAllocationInfo(g_hAllocator, bufInfo[i].Allocation, &allocInfo); - if(allocInfo.deviceMemory == VK_NULL_HANDLE) - { - vmaDestroyBuffer(g_hAllocator, bufInfo[i].Buffer, bufInfo[i].Allocation); - bufInfo.erase(bufInfo.begin() + i); - } - } - - // Test vmaMakePoolAllocationsLost - { - vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); - - size_t lostAllocCount = 0; - vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostAllocCount); - TEST(lostAllocCount > 0); - - size_t realLostAllocCount = 0; - for(size_t i = 0; i < bufInfo.size(); ++i) - { - vmaGetAllocationInfo(g_hAllocator, bufInfo[i].Allocation, &allocInfo); - if(allocInfo.deviceMemory == VK_NULL_HANDLE) - ++realLostAllocCount; - } - TEST(realLostAllocCount == lostAllocCount); - } - - // Destroy all the buffers in forward order. - for(size_t i = 0; i < bufInfo.size(); ++i) - vmaDestroyBuffer(g_hAllocator, bufInfo[i].Buffer, bufInfo[i].Allocation); - bufInfo.clear(); - } - - vmaDestroyPool(g_hAllocator, pool); -} - -static void TestLinearAllocatorMultiBlock() -{ - wprintf(L"Test linear allocator multi block\n"); - - RandomNumberGenerator rand{345673}; - - VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - sampleBufCreateInfo.size = 1024 * 1024; - sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - VmaAllocationCreateInfo sampleAllocCreateInfo = {}; - sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - - VmaPoolCreateInfo poolCreateInfo = {}; - poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT; - VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); - TEST(res == VK_SUCCESS); - - VmaPool pool = nullptr; - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - TEST(res == VK_SUCCESS); - - VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.pool = pool; - - std::vector<BufferInfo> bufInfo; - VmaAllocationInfo allocInfo; - - // Test one-time free. - { - // Allocate buffers until we move to a second block. - VkDeviceMemory lastMem = VK_NULL_HANDLE; - for(uint32_t i = 0; ; ++i) - { - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - if(lastMem && allocInfo.deviceMemory != lastMem) - { - break; - } - lastMem = allocInfo.deviceMemory; - } - - TEST(bufInfo.size() > 2); - - // Make sure that pool has now two blocks. - VmaPoolStats poolStats = {}; - vmaGetPoolStats(g_hAllocator, pool, &poolStats); - TEST(poolStats.blockCount == 2); - - // Destroy all the buffers in random order. - while(!bufInfo.empty()) - { - const size_t indexToDestroy = rand.Generate() % bufInfo.size(); - const BufferInfo& currBufInfo = bufInfo[indexToDestroy]; - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.erase(bufInfo.begin() + indexToDestroy); - } - - // Make sure that pool has now at most one block. - vmaGetPoolStats(g_hAllocator, pool, &poolStats); - TEST(poolStats.blockCount <= 1); - } - - // Test stack. - { - // Allocate buffers until we move to a second block. - VkDeviceMemory lastMem = VK_NULL_HANDLE; - for(uint32_t i = 0; ; ++i) - { - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - if(lastMem && allocInfo.deviceMemory != lastMem) - { - break; - } - lastMem = allocInfo.deviceMemory; - } - - TEST(bufInfo.size() > 2); - - // Add few more buffers. - for(uint32_t i = 0; i < 5; ++i) - { - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - } - - // Make sure that pool has now two blocks. - VmaPoolStats poolStats = {}; - vmaGetPoolStats(g_hAllocator, pool, &poolStats); - TEST(poolStats.blockCount == 2); - - // Delete half of buffers, LIFO. - for(size_t i = 0, countToDelete = bufInfo.size() / 2; i < countToDelete; ++i) - { - const BufferInfo& currBufInfo = bufInfo.back(); - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.pop_back(); - } - - // Add one more buffer. - BufferInfo newBufInfo; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - - // Make sure that pool has now one block. - vmaGetPoolStats(g_hAllocator, pool, &poolStats); - TEST(poolStats.blockCount == 1); - - // Delete all the remaining buffers, LIFO. - while(!bufInfo.empty()) - { - const BufferInfo& currBufInfo = bufInfo.back(); - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.pop_back(); - } - } - - vmaDestroyPool(g_hAllocator, pool); -} - -static void ManuallyTestLinearAllocator() -{ - VmaStats origStats; - vmaCalculateStats(g_hAllocator, &origStats); - - wprintf(L"Manually test linear allocator\n"); - - RandomNumberGenerator rand{645332}; - - VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - sampleBufCreateInfo.size = 1024; // Whatever. - sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - - VmaAllocationCreateInfo sampleAllocCreateInfo = {}; - sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - VmaPoolCreateInfo poolCreateInfo = {}; - VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); - TEST(res == VK_SUCCESS); - - poolCreateInfo.blockSize = 10 * 1024; - poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT; - poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; - - VmaPool pool = nullptr; - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - TEST(res == VK_SUCCESS); - - VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.pool = pool; - - std::vector<BufferInfo> bufInfo; - VmaAllocationInfo allocInfo; - BufferInfo newBufInfo; - - // Test double stack. - { - /* - Lower: Buffer 32 B, Buffer 1024 B, Buffer 32 B - Upper: Buffer 16 B, Buffer 1024 B, Buffer 128 B - - Totally: - 1 block allocated - 10240 Vulkan bytes - 6 new allocations - 2256 bytes in allocations - */ - - bufCreateInfo.size = 32; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - - bufCreateInfo.size = 1024; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - - bufCreateInfo.size = 32; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; - - bufCreateInfo.size = 128; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - - bufCreateInfo.size = 1024; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - - bufCreateInfo.size = 16; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - - VmaStats currStats; - vmaCalculateStats(g_hAllocator, &currStats); - VmaPoolStats poolStats; - vmaGetPoolStats(g_hAllocator, pool, &poolStats); - - char* statsStr = nullptr; - vmaBuildStatsString(g_hAllocator, &statsStr, VK_TRUE); - - // PUT BREAKPOINT HERE TO CHECK. - // Inspect: currStats versus origStats, poolStats, statsStr. - int I = 0; - - vmaFreeStatsString(g_hAllocator, statsStr); - - // Destroy the buffers in reverse order. - while(!bufInfo.empty()) - { - const BufferInfo& currBufInfo = bufInfo.back(); - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.pop_back(); - } - } - - vmaDestroyPool(g_hAllocator, pool); -} - -static void BenchmarkAlgorithmsCase(FILE* file, - uint32_t algorithm, - bool empty, - VmaAllocationCreateFlags allocStrategy, - FREE_ORDER freeOrder) -{ - RandomNumberGenerator rand{16223}; - - const VkDeviceSize bufSizeMin = 32; - const VkDeviceSize bufSizeMax = 1024; - const size_t maxBufCapacity = 10000; - const uint32_t iterationCount = 10; - - VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - sampleBufCreateInfo.size = bufSizeMax; - sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - - VmaAllocationCreateInfo sampleAllocCreateInfo = {}; - sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - VmaPoolCreateInfo poolCreateInfo = {}; - VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); - TEST(res == VK_SUCCESS); - - poolCreateInfo.blockSize = bufSizeMax * maxBufCapacity; - poolCreateInfo.flags |= algorithm; - poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; - - VmaPool pool = nullptr; - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - TEST(res == VK_SUCCESS); - - // Buffer created just to get memory requirements. Never bound to any memory. - VkBuffer dummyBuffer = VK_NULL_HANDLE; - res = vkCreateBuffer(g_hDevice, &sampleBufCreateInfo, g_Allocs, &dummyBuffer); - TEST(res == VK_SUCCESS && dummyBuffer); - - VkMemoryRequirements memReq = {}; - vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq); - - vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.pool = pool; - allocCreateInfo.flags = allocStrategy; - - VmaAllocation alloc; - std::vector<VmaAllocation> baseAllocations; - - if(!empty) - { - // Make allocations up to 1/3 of pool size. - VkDeviceSize totalSize = 0; - while(totalSize < poolCreateInfo.blockSize / 3) - { - // This test intentionally allows sizes that are aligned to 4 or 16 bytes. - // This is theoretically allowed and already uncovered one bug. - memReq.size = bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin); - res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr); - TEST(res == VK_SUCCESS); - baseAllocations.push_back(alloc); - totalSize += memReq.size; - } - - // Delete half of them, choose randomly. - size_t allocsToDelete = baseAllocations.size() / 2; - for(size_t i = 0; i < allocsToDelete; ++i) - { - const size_t index = (size_t)rand.Generate() % baseAllocations.size(); - vmaFreeMemory(g_hAllocator, baseAllocations[index]); - baseAllocations.erase(baseAllocations.begin() + index); - } - } - - // BENCHMARK - const size_t allocCount = maxBufCapacity / 3; - std::vector<VmaAllocation> testAllocations; - testAllocations.reserve(allocCount); - duration allocTotalDuration = duration::zero(); - duration freeTotalDuration = duration::zero(); - for(uint32_t iterationIndex = 0; iterationIndex < iterationCount; ++iterationIndex) - { - // Allocations - time_point allocTimeBeg = std::chrono::high_resolution_clock::now(); - for(size_t i = 0; i < allocCount; ++i) - { - memReq.size = bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin); - res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr); - TEST(res == VK_SUCCESS); - testAllocations.push_back(alloc); - } - allocTotalDuration += std::chrono::high_resolution_clock::now() - allocTimeBeg; - - // Deallocations - switch(freeOrder) - { - case FREE_ORDER::FORWARD: - // Leave testAllocations unchanged. - break; - case FREE_ORDER::BACKWARD: - std::reverse(testAllocations.begin(), testAllocations.end()); - break; - case FREE_ORDER::RANDOM: - std::shuffle(testAllocations.begin(), testAllocations.end(), MyUniformRandomNumberGenerator(rand)); - break; - default: assert(0); - } - - time_point freeTimeBeg = std::chrono::high_resolution_clock::now(); - for(size_t i = 0; i < allocCount; ++i) - vmaFreeMemory(g_hAllocator, testAllocations[i]); - freeTotalDuration += std::chrono::high_resolution_clock::now() - freeTimeBeg; - - testAllocations.clear(); - } - - // Delete baseAllocations - while(!baseAllocations.empty()) - { - vmaFreeMemory(g_hAllocator, baseAllocations.back()); - baseAllocations.pop_back(); - } - - vmaDestroyPool(g_hAllocator, pool); - - const float allocTotalSeconds = ToFloatSeconds(allocTotalDuration); - const float freeTotalSeconds = ToFloatSeconds(freeTotalDuration); - - printf(" Algorithm=%s %s Allocation=%s FreeOrder=%s: allocations %g s, free %g s\n", - AlgorithmToStr(algorithm), - empty ? "Empty" : "Not empty", - GetAllocationStrategyName(allocStrategy), - FREE_ORDER_NAMES[(size_t)freeOrder], - allocTotalSeconds, - freeTotalSeconds); - - if(file) - { - std::string currTime; - CurrentTimeToStr(currTime); - - fprintf(file, "%s,%s,%s,%u,%s,%s,%g,%g\n", - CODE_DESCRIPTION, currTime.c_str(), - AlgorithmToStr(algorithm), - empty ? 1 : 0, - GetAllocationStrategyName(allocStrategy), - FREE_ORDER_NAMES[(uint32_t)freeOrder], - allocTotalSeconds, - freeTotalSeconds); - } -} - -static void TestBufferDeviceAddress() -{ - wprintf(L"Test buffer device address\n"); - - assert(VK_KHR_buffer_device_address_enabled); - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.size = 0x10000; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | - VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; // !!! - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - for(uint32_t testIndex = 0; testIndex < 2; ++testIndex) - { - // 1st is placed, 2nd is dedicated. - if(testIndex == 1) - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - - BufferInfo bufInfo = {}; - VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &bufInfo.Buffer, &bufInfo.Allocation, nullptr); - TEST(res == VK_SUCCESS); - - VkBufferDeviceAddressInfoEXT bufferDeviceAddressInfo = { VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_EXT }; - bufferDeviceAddressInfo.buffer = bufInfo.Buffer; - TEST(g_vkGetBufferDeviceAddressKHR != nullptr); - VkDeviceAddress addr = g_vkGetBufferDeviceAddressKHR(g_hDevice, &bufferDeviceAddressInfo); - TEST(addr != 0); - - vmaDestroyBuffer(g_hAllocator, bufInfo.Buffer, bufInfo.Allocation); - } -} - -static void TestMemoryPriority() -{ - wprintf(L"Test memory priority\n"); - - assert(VK_EXT_memory_priority_enabled); - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.size = 0x10000; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - allocCreateInfo.priority = 1.f; - - for(uint32_t testIndex = 0; testIndex < 2; ++testIndex) - { - // 1st is placed, 2nd is dedicated. - if(testIndex == 1) - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - - BufferInfo bufInfo = {}; - VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &bufInfo.Buffer, &bufInfo.Allocation, nullptr); - TEST(res == VK_SUCCESS); - - // There is nothing we can do to validate the priority. - - vmaDestroyBuffer(g_hAllocator, bufInfo.Buffer, bufInfo.Allocation); - } -} - -static void BenchmarkAlgorithms(FILE* file) -{ - wprintf(L"Benchmark algorithms\n"); - - if(file) - { - fprintf(file, - "Code,Time," - "Algorithm,Empty,Allocation strategy,Free order," - "Allocation time (s),Deallocation time (s)\n"); - } - - uint32_t freeOrderCount = 1; - if(ConfigType >= CONFIG_TYPE::CONFIG_TYPE_LARGE) - freeOrderCount = 3; - else if(ConfigType >= CONFIG_TYPE::CONFIG_TYPE_SMALL) - freeOrderCount = 2; - - const uint32_t emptyCount = ConfigType >= CONFIG_TYPE::CONFIG_TYPE_SMALL ? 2 : 1; - const uint32_t allocStrategyCount = GetAllocationStrategyCount(); - - for(uint32_t freeOrderIndex = 0; freeOrderIndex < freeOrderCount; ++freeOrderIndex) - { - FREE_ORDER freeOrder = FREE_ORDER::COUNT; - switch(freeOrderIndex) - { - case 0: freeOrder = FREE_ORDER::BACKWARD; break; - case 1: freeOrder = FREE_ORDER::FORWARD; break; - case 2: freeOrder = FREE_ORDER::RANDOM; break; - default: assert(0); - } - - for(uint32_t emptyIndex = 0; emptyIndex < emptyCount; ++emptyIndex) - { - for(uint32_t algorithmIndex = 0; algorithmIndex < 3; ++algorithmIndex) - { - uint32_t algorithm = 0; - switch(algorithmIndex) - { - case 0: - break; - case 1: - algorithm = VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT; - break; - case 2: - algorithm = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT; - break; - default: - assert(0); - } - - uint32_t currAllocStrategyCount = algorithm != 0 ? 1 : allocStrategyCount; - for(uint32_t allocStrategyIndex = 0; allocStrategyIndex < currAllocStrategyCount; ++allocStrategyIndex) - { - VmaAllocatorCreateFlags strategy = 0; - if(currAllocStrategyCount > 1) - { - switch(allocStrategyIndex) - { - case 0: strategy = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT; break; - case 1: strategy = VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT; break; - case 2: strategy = VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT; break; - default: assert(0); - } - } - - BenchmarkAlgorithmsCase( - file, - algorithm, - (emptyIndex == 0), // empty - strategy, - freeOrder); // freeOrder - } - } - } - } -} - -static void TestPool_SameSize() -{ - const VkDeviceSize BUF_SIZE = 1024 * 1024; - const size_t BUF_COUNT = 100; - VkResult res; - - RandomNumberGenerator rand{123}; - - VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufferInfo.size = BUF_SIZE; - bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - - uint32_t memoryTypeBits = UINT32_MAX; - { - VkBuffer dummyBuffer; - res = vkCreateBuffer(g_hDevice, &bufferInfo, g_Allocs, &dummyBuffer); - TEST(res == VK_SUCCESS); - - VkMemoryRequirements memReq; - vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq); - memoryTypeBits = memReq.memoryTypeBits; - - vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs); - } - - VmaAllocationCreateInfo poolAllocInfo = {}; - poolAllocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - uint32_t memTypeIndex; - res = vmaFindMemoryTypeIndex( - g_hAllocator, - memoryTypeBits, - &poolAllocInfo, - &memTypeIndex); - - VmaPoolCreateInfo poolCreateInfo = {}; - poolCreateInfo.memoryTypeIndex = memTypeIndex; - poolCreateInfo.blockSize = BUF_SIZE * BUF_COUNT / 4; - poolCreateInfo.minBlockCount = 1; - poolCreateInfo.maxBlockCount = 4; - poolCreateInfo.frameInUseCount = 0; - - VmaPool pool; - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - TEST(res == VK_SUCCESS); - - // Test pool name - { - static const char* const POOL_NAME = "Pool name"; - vmaSetPoolName(g_hAllocator, pool, POOL_NAME); - - const char* fetchedPoolName = nullptr; - vmaGetPoolName(g_hAllocator, pool, &fetchedPoolName); - TEST(strcmp(fetchedPoolName, POOL_NAME) == 0); - - vmaSetPoolName(g_hAllocator, pool, nullptr); - } - - vmaSetCurrentFrameIndex(g_hAllocator, 1); - - VmaAllocationCreateInfo allocInfo = {}; - allocInfo.pool = pool; - allocInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT | - VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; - - struct BufItem - { - VkBuffer Buf; - VmaAllocation Alloc; - }; - std::vector<BufItem> items; - - // Fill entire pool. - for(size_t i = 0; i < BUF_COUNT; ++i) - { - BufItem item; - res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); - TEST(res == VK_SUCCESS); - items.push_back(item); - } - - // Make sure that another allocation would fail. - { - BufItem item; - res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); - TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY); - } - - // Validate that no buffer is lost. Also check that they are not mapped. - for(size_t i = 0; i < items.size(); ++i) - { - VmaAllocationInfo allocInfo; - vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); - TEST(allocInfo.deviceMemory != VK_NULL_HANDLE); - TEST(allocInfo.pMappedData == nullptr); - } - - // Free some percent of random items. - { - const size_t PERCENT_TO_FREE = 10; - size_t itemsToFree = items.size() * PERCENT_TO_FREE / 100; - for(size_t i = 0; i < itemsToFree; ++i) - { - size_t index = (size_t)rand.Generate() % items.size(); - vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc); - items.erase(items.begin() + index); - } - } - - // Randomly allocate and free items. - { - const size_t OPERATION_COUNT = BUF_COUNT; - for(size_t i = 0; i < OPERATION_COUNT; ++i) - { - bool allocate = rand.Generate() % 2 != 0; - if(allocate) - { - if(items.size() < BUF_COUNT) - { - BufItem item; - res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); - TEST(res == VK_SUCCESS); - items.push_back(item); - } - } - else // Free - { - if(!items.empty()) - { - size_t index = (size_t)rand.Generate() % items.size(); - vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc); - items.erase(items.begin() + index); - } - } - } - } - - // Allocate up to maximum. - while(items.size() < BUF_COUNT) - { - BufItem item; - res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); - TEST(res == VK_SUCCESS); - items.push_back(item); - } - - // Validate that no buffer is lost. - for(size_t i = 0; i < items.size(); ++i) - { - VmaAllocationInfo allocInfo; - vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); - TEST(allocInfo.deviceMemory != VK_NULL_HANDLE); - } - - // Next frame. - vmaSetCurrentFrameIndex(g_hAllocator, 2); - - // Allocate another BUF_COUNT buffers. - for(size_t i = 0; i < BUF_COUNT; ++i) - { - BufItem item; - res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); - TEST(res == VK_SUCCESS); - items.push_back(item); - } - - // Make sure the first BUF_COUNT is lost. Delete them. - for(size_t i = 0; i < BUF_COUNT; ++i) - { - VmaAllocationInfo allocInfo; - vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); - TEST(allocInfo.deviceMemory == VK_NULL_HANDLE); - vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); - } - items.erase(items.begin(), items.begin() + BUF_COUNT); - - // Validate that no buffer is lost. - for(size_t i = 0; i < items.size(); ++i) - { - VmaAllocationInfo allocInfo; - vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); - TEST(allocInfo.deviceMemory != VK_NULL_HANDLE); - } - - // Free one item. - vmaDestroyBuffer(g_hAllocator, items.back().Buf, items.back().Alloc); - items.pop_back(); - - // Validate statistics. - { - VmaPoolStats poolStats = {}; - vmaGetPoolStats(g_hAllocator, pool, &poolStats); - TEST(poolStats.allocationCount == items.size()); - TEST(poolStats.size = BUF_COUNT * BUF_SIZE); - TEST(poolStats.unusedRangeCount == 1); - TEST(poolStats.unusedRangeSizeMax == BUF_SIZE); - TEST(poolStats.unusedSize == BUF_SIZE); - } - - // Free all remaining items. - for(size_t i = items.size(); i--; ) - vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); - items.clear(); - - // Allocate maximum items again. - for(size_t i = 0; i < BUF_COUNT; ++i) - { - BufItem item; - res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); - TEST(res == VK_SUCCESS); - items.push_back(item); - } - - // Delete every other item. - for(size_t i = 0; i < BUF_COUNT / 2; ++i) - { - vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); - items.erase(items.begin() + i); - } - - // Defragment! - { - std::vector<VmaAllocation> allocationsToDefragment(items.size()); - for(size_t i = 0; i < items.size(); ++i) - allocationsToDefragment[i] = items[i].Alloc; - - VmaDefragmentationStats defragmentationStats; - res = vmaDefragment(g_hAllocator, allocationsToDefragment.data(), items.size(), nullptr, nullptr, &defragmentationStats); - TEST(res == VK_SUCCESS); - TEST(defragmentationStats.deviceMemoryBlocksFreed == 2); - } - - // Free all remaining items. - for(size_t i = items.size(); i--; ) - vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); - items.clear(); - - //////////////////////////////////////////////////////////////////////////////// - // Test for vmaMakePoolAllocationsLost - - // Allocate 4 buffers on frame 10. - vmaSetCurrentFrameIndex(g_hAllocator, 10); - for(size_t i = 0; i < 4; ++i) - { - BufItem item; - res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); - TEST(res == VK_SUCCESS); - items.push_back(item); - } - - // Touch first 2 of them on frame 11. - vmaSetCurrentFrameIndex(g_hAllocator, 11); - for(size_t i = 0; i < 2; ++i) - { - VmaAllocationInfo allocInfo; - vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); - } - - // vmaMakePoolAllocationsLost. Only remaining 2 should be lost. - size_t lostCount = 0xDEADC0DE; - vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount); - TEST(lostCount == 2); - - // Make another call. Now 0 should be lost. - vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount); - TEST(lostCount == 0); - - // Make another call, with null count. Should not crash. - vmaMakePoolAllocationsLost(g_hAllocator, pool, nullptr); - - // END: Free all remaining items. - for(size_t i = items.size(); i--; ) - vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); - - items.clear(); - - //////////////////////////////////////////////////////////////////////////////// - // Test for allocation too large for pool - - { - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.pool = pool; - - VkMemoryRequirements memReq; - memReq.memoryTypeBits = UINT32_MAX; - memReq.alignment = 1; - memReq.size = poolCreateInfo.blockSize + 4; - - VmaAllocation alloc = nullptr; - res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr); - TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY && alloc == nullptr); - } - - vmaDestroyPool(g_hAllocator, pool); -} - -static bool ValidatePattern(const void* pMemory, size_t size, uint8_t pattern) -{ - const uint8_t* pBytes = (const uint8_t*)pMemory; - for(size_t i = 0; i < size; ++i) - { - if(pBytes[i] != pattern) - { - return false; - } - } - return true; -} - -static void TestAllocationsInitialization() -{ - VkResult res; - - const size_t BUF_SIZE = 1024; - - // Create pool. - - VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufInfo.size = BUF_SIZE; - bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - VmaAllocationCreateInfo dummyBufAllocCreateInfo = {}; - dummyBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - - VmaPoolCreateInfo poolCreateInfo = {}; - poolCreateInfo.blockSize = BUF_SIZE * 10; - poolCreateInfo.minBlockCount = 1; // To keep memory alive while pool exists. - poolCreateInfo.maxBlockCount = 1; - res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufInfo, &dummyBufAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); - TEST(res == VK_SUCCESS); - - VmaAllocationCreateInfo bufAllocCreateInfo = {}; - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &bufAllocCreateInfo.pool); - TEST(res == VK_SUCCESS); - - // Create one persistently mapped buffer to keep memory of this block mapped, - // so that pointer to mapped data will remain (more or less...) valid even - // after destruction of other allocations. - - bufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - VkBuffer firstBuf; - VmaAllocation firstAlloc; - res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &firstBuf, &firstAlloc, nullptr); - TEST(res == VK_SUCCESS); - - // Test buffers. - - for(uint32_t i = 0; i < 2; ++i) - { - const bool persistentlyMapped = i == 0; - bufAllocCreateInfo.flags = persistentlyMapped ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0; - VkBuffer buf; - VmaAllocation alloc; - VmaAllocationInfo allocInfo; - res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &buf, &alloc, &allocInfo); - TEST(res == VK_SUCCESS); - - void* pMappedData; - if(!persistentlyMapped) - { - res = vmaMapMemory(g_hAllocator, alloc, &pMappedData); - TEST(res == VK_SUCCESS); - } - else - { - pMappedData = allocInfo.pMappedData; - } - - // Validate initialized content - bool valid = ValidatePattern(pMappedData, BUF_SIZE, 0xDC); - TEST(valid); - - if(!persistentlyMapped) - { - vmaUnmapMemory(g_hAllocator, alloc); - } - - vmaDestroyBuffer(g_hAllocator, buf, alloc); - - // Validate freed content - valid = ValidatePattern(pMappedData, BUF_SIZE, 0xEF); - TEST(valid); - } - - vmaDestroyBuffer(g_hAllocator, firstBuf, firstAlloc); - vmaDestroyPool(g_hAllocator, bufAllocCreateInfo.pool); -} - -static void TestPool_Benchmark( - PoolTestResult& outResult, - const PoolTestConfig& config) -{ - TEST(config.ThreadCount > 0); - - RandomNumberGenerator mainRand{config.RandSeed}; - - uint32_t allocationSizeProbabilitySum = std::accumulate( - config.AllocationSizes.begin(), - config.AllocationSizes.end(), - 0u, - [](uint32_t sum, const AllocationSize& allocSize) { - return sum + allocSize.Probability; - }); - - VkBufferCreateInfo bufferTemplateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufferTemplateInfo.size = 256; // Whatever. - bufferTemplateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - - VkImageCreateInfo imageTemplateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imageTemplateInfo.imageType = VK_IMAGE_TYPE_2D; - imageTemplateInfo.extent.width = 256; // Whatever. - imageTemplateInfo.extent.height = 256; // Whatever. - imageTemplateInfo.extent.depth = 1; - imageTemplateInfo.mipLevels = 1; - imageTemplateInfo.arrayLayers = 1; - imageTemplateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imageTemplateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // LINEAR if CPU memory. - imageTemplateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; - imageTemplateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // TRANSFER_SRC if CPU memory. - imageTemplateInfo.samples = VK_SAMPLE_COUNT_1_BIT; - - uint32_t bufferMemoryTypeBits = UINT32_MAX; - { - VkBuffer dummyBuffer; - VkResult res = vkCreateBuffer(g_hDevice, &bufferTemplateInfo, g_Allocs, &dummyBuffer); - TEST(res == VK_SUCCESS); - - VkMemoryRequirements memReq; - vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq); - bufferMemoryTypeBits = memReq.memoryTypeBits; - - vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs); - } - - uint32_t imageMemoryTypeBits = UINT32_MAX; - { - VkImage dummyImage; - VkResult res = vkCreateImage(g_hDevice, &imageTemplateInfo, g_Allocs, &dummyImage); - TEST(res == VK_SUCCESS); - - VkMemoryRequirements memReq; - vkGetImageMemoryRequirements(g_hDevice, dummyImage, &memReq); - imageMemoryTypeBits = memReq.memoryTypeBits; - - vkDestroyImage(g_hDevice, dummyImage, g_Allocs); - } - - uint32_t memoryTypeBits = 0; - if(config.UsesBuffers() && config.UsesImages()) - { - memoryTypeBits = bufferMemoryTypeBits & imageMemoryTypeBits; - if(memoryTypeBits == 0) - { - PrintWarning(L"Cannot test buffers + images in the same memory pool on this GPU."); - return; - } - } - else if(config.UsesBuffers()) - memoryTypeBits = bufferMemoryTypeBits; - else if(config.UsesImages()) - memoryTypeBits = imageMemoryTypeBits; - else - TEST(0); - - VmaPoolCreateInfo poolCreateInfo = {}; - poolCreateInfo.minBlockCount = 1; - poolCreateInfo.maxBlockCount = 1; - poolCreateInfo.blockSize = config.PoolSize; - poolCreateInfo.frameInUseCount = 1; - - const VkPhysicalDeviceMemoryProperties* memProps = nullptr; - vmaGetMemoryProperties(g_hAllocator, &memProps); - - VmaPool pool = VK_NULL_HANDLE; - VkResult res; - // Loop over memory types because we sometimes allocate a big block here, - // while the most eligible DEVICE_LOCAL heap may be only 256 MB on some GPUs. - while(memoryTypeBits) - { - VmaAllocationCreateInfo dummyAllocCreateInfo = {}; - dummyAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - vmaFindMemoryTypeIndex(g_hAllocator, memoryTypeBits, &dummyAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); - - const uint32_t heapIndex = memProps->memoryTypes[poolCreateInfo.memoryTypeIndex].heapIndex; - // Protection against validation layer error when trying to allocate a block larger than entire heap size, - // which may be only 256 MB on some platforms. - if(poolCreateInfo.blockSize * poolCreateInfo.minBlockCount < memProps->memoryHeaps[heapIndex].size) - { - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - if(res == VK_SUCCESS) - break; - } - memoryTypeBits &= ~(1u << poolCreateInfo.memoryTypeIndex); - } - TEST(pool); - - // Start time measurement - after creating pool and initializing data structures. - time_point timeBeg = std::chrono::high_resolution_clock::now(); - - //////////////////////////////////////////////////////////////////////////////// - // ThreadProc - auto ThreadProc = [&config, allocationSizeProbabilitySum, pool]( - PoolTestThreadResult* outThreadResult, - uint32_t randSeed, - HANDLE frameStartEvent, - HANDLE frameEndEvent) -> void - { - RandomNumberGenerator threadRand{randSeed}; - VkResult res = VK_SUCCESS; - - VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufferInfo.size = 256; // Whatever. - bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - - VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.width = 256; // Whatever. - imageInfo.extent.height = 256; // Whatever. - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // LINEAR if CPU memory. - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // TRANSFER_SRC if CPU memory. - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - - outThreadResult->AllocationTimeMin = duration::max(); - outThreadResult->AllocationTimeSum = duration::zero(); - outThreadResult->AllocationTimeMax = duration::min(); - outThreadResult->DeallocationTimeMin = duration::max(); - outThreadResult->DeallocationTimeSum = duration::zero(); - outThreadResult->DeallocationTimeMax = duration::min(); - outThreadResult->AllocationCount = 0; - outThreadResult->DeallocationCount = 0; - outThreadResult->LostAllocationCount = 0; - outThreadResult->LostAllocationTotalSize = 0; - outThreadResult->FailedAllocationCount = 0; - outThreadResult->FailedAllocationTotalSize = 0; - - struct Item - { - VkDeviceSize BufferSize = 0; - VkExtent2D ImageSize = { 0, 0 }; - VkBuffer Buf = VK_NULL_HANDLE; - VkImage Image = VK_NULL_HANDLE; - VmaAllocation Alloc = VK_NULL_HANDLE; - - Item() { } - Item(Item&& src) : - BufferSize(src.BufferSize), ImageSize(src.ImageSize), Buf(src.Buf), Image(src.Image), Alloc(src.Alloc) - { - src.BufferSize = 0; - src.ImageSize = {0, 0}; - src.Buf = VK_NULL_HANDLE; - src.Image = VK_NULL_HANDLE; - src.Alloc = VK_NULL_HANDLE; - } - Item(const Item& src) = delete; - ~Item() - { - DestroyResources(); - } - Item& operator=(Item&& src) - { - if(&src != this) - { - DestroyResources(); - BufferSize = src.BufferSize; ImageSize = src.ImageSize; - Buf = src.Buf; Image = src.Image; Alloc = src.Alloc; - src.BufferSize = 0; - src.ImageSize = {0, 0}; - src.Buf = VK_NULL_HANDLE; - src.Image = VK_NULL_HANDLE; - src.Alloc = VK_NULL_HANDLE; - } - return *this; - } - Item& operator=(const Item& src) = delete; - void DestroyResources() - { - if(Buf) - { - assert(Image == VK_NULL_HANDLE); - vmaDestroyBuffer(g_hAllocator, Buf, Alloc); - Buf = VK_NULL_HANDLE; - } - else - { - vmaDestroyImage(g_hAllocator, Image, Alloc); - Image = VK_NULL_HANDLE; - } - Alloc = VK_NULL_HANDLE; - } - VkDeviceSize CalcSizeBytes() const - { - return BufferSize + - 4ull * ImageSize.width * ImageSize.height; - } - }; - std::vector<Item> unusedItems, usedItems; - - const size_t threadTotalItemCount = config.TotalItemCount / config.ThreadCount; - - // Create all items - all unused, not yet allocated. - for(size_t i = 0; i < threadTotalItemCount; ++i) - { - Item item = {}; - - uint32_t allocSizeIndex = 0; - uint32_t r = threadRand.Generate() % allocationSizeProbabilitySum; - while(r >= config.AllocationSizes[allocSizeIndex].Probability) - r -= config.AllocationSizes[allocSizeIndex++].Probability; - - const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex]; - if(allocSize.BufferSizeMax > 0) - { - TEST(allocSize.BufferSizeMin > 0); - TEST(allocSize.ImageSizeMin == 0 && allocSize.ImageSizeMax == 0); - if(allocSize.BufferSizeMax == allocSize.BufferSizeMin) - item.BufferSize = allocSize.BufferSizeMin; - else - { - item.BufferSize = allocSize.BufferSizeMin + threadRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin); - item.BufferSize = item.BufferSize / 16 * 16; - } - } - else - { - TEST(allocSize.ImageSizeMin > 0 && allocSize.ImageSizeMax > 0); - if(allocSize.ImageSizeMax == allocSize.ImageSizeMin) - item.ImageSize.width = item.ImageSize.height = allocSize.ImageSizeMax; - else - { - item.ImageSize.width = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); - item.ImageSize.height = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); - } - } - - unusedItems.push_back(std::move(item)); - } - - auto Allocate = [&](Item& item) -> VkResult - { - assert(item.Buf == VK_NULL_HANDLE && item.Image == VK_NULL_HANDLE && item.Alloc == VK_NULL_HANDLE); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.pool = pool; - allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT | - VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; - - if(item.BufferSize) - { - bufferInfo.size = item.BufferSize; - VkResult res = VK_SUCCESS; - { - PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult); - res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocCreateInfo, &item.Buf, &item.Alloc, nullptr); - } - if(res == VK_SUCCESS) - SetDebugUtilsObjectName(VK_OBJECT_TYPE_BUFFER, (uint64_t)item.Buf, "TestPool_Benchmark_Buffer"); - return res; - } - else - { - TEST(item.ImageSize.width && item.ImageSize.height); - - imageInfo.extent.width = item.ImageSize.width; - imageInfo.extent.height = item.ImageSize.height; - VkResult res = VK_SUCCESS; - { - PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult); - res = vmaCreateImage(g_hAllocator, &imageInfo, &allocCreateInfo, &item.Image, &item.Alloc, nullptr); - } - if(res == VK_SUCCESS) - SetDebugUtilsObjectName(VK_OBJECT_TYPE_IMAGE, (uint64_t)item.Image, "TestPool_Benchmark_Image"); - return res; - } - }; - - //////////////////////////////////////////////////////////////////////////////// - // Frames - for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex) - { - WaitForSingleObject(frameStartEvent, INFINITE); - - // Always make some percent of used bufs unused, to choose different used ones. - const size_t bufsToMakeUnused = usedItems.size() * config.ItemsToMakeUnusedPercent / 100; - for(size_t i = 0; i < bufsToMakeUnused; ++i) - { - size_t index = threadRand.Generate() % usedItems.size(); - auto it = usedItems.begin() + index; - Item item = std::move(*it); - usedItems.erase(it); - unusedItems.push_back(std::move(item)); - } - - // Determine which bufs we want to use in this frame. - const size_t usedBufCount = (threadRand.Generate() % (config.UsedItemCountMax - config.UsedItemCountMin) + config.UsedItemCountMin) - / config.ThreadCount; - TEST(usedBufCount < usedItems.size() + unusedItems.size()); - // Move some used to unused. - while(usedBufCount < usedItems.size()) - { - size_t index = threadRand.Generate() % usedItems.size(); - auto it = usedItems.begin() + index; - Item item = std::move(*it); - usedItems.erase(it); - unusedItems.push_back(std::move(item)); - } - // Move some unused to used. - while(usedBufCount > usedItems.size()) - { - size_t index = threadRand.Generate() % unusedItems.size(); - auto it = unusedItems.begin() + index; - Item item = std::move(*it); - unusedItems.erase(it); - usedItems.push_back(std::move(item)); - } - - uint32_t touchExistingCount = 0; - uint32_t touchLostCount = 0; - uint32_t createSucceededCount = 0; - uint32_t createFailedCount = 0; - - // Touch all used bufs. If not created or lost, allocate. - for(size_t i = 0; i < usedItems.size(); ++i) - { - Item& item = usedItems[i]; - // Not yet created. - if(item.Alloc == VK_NULL_HANDLE) - { - res = Allocate(item); - ++outThreadResult->AllocationCount; - if(res != VK_SUCCESS) - { - assert(item.Alloc == VK_NULL_HANDLE && item.Buf == VK_NULL_HANDLE && item.Image == VK_NULL_HANDLE); - ++outThreadResult->FailedAllocationCount; - outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes(); - ++createFailedCount; - } - else - ++createSucceededCount; - } - else - { - // Touch. - VmaAllocationInfo allocInfo; - vmaGetAllocationInfo(g_hAllocator, item.Alloc, &allocInfo); - // Lost. - if(allocInfo.deviceMemory == VK_NULL_HANDLE) - { - ++touchLostCount; - - // Destroy. - { - PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult); - item.DestroyResources(); - ++outThreadResult->DeallocationCount; - } - - ++outThreadResult->LostAllocationCount; - outThreadResult->LostAllocationTotalSize += item.CalcSizeBytes(); - - // Recreate. - res = Allocate(item); - ++outThreadResult->AllocationCount; - // Creation failed. - if(res != VK_SUCCESS) - { - TEST(item.Alloc == VK_NULL_HANDLE && item.Buf == VK_NULL_HANDLE && item.Image == VK_NULL_HANDLE); - ++outThreadResult->FailedAllocationCount; - outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes(); - ++createFailedCount; - } - else - ++createSucceededCount; - } - else - ++touchExistingCount; - } - } - - /* - printf("Thread %u frame %u: Touch existing %u lost %u, create succeeded %u failed %u\n", - randSeed, frameIndex, - touchExistingCount, touchLostCount, - createSucceededCount, createFailedCount); - */ - - SetEvent(frameEndEvent); - } - - // Free all remaining items. - for(size_t i = usedItems.size(); i--; ) - { - PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult); - usedItems[i].DestroyResources(); - ++outThreadResult->DeallocationCount; - } - for(size_t i = unusedItems.size(); i--; ) - { - PoolDeallocationTimeRegisterObj timeRegisterOb(*outThreadResult); - unusedItems[i].DestroyResources(); - ++outThreadResult->DeallocationCount; - } - }; - - // Launch threads. - uint32_t threadRandSeed = mainRand.Generate(); - std::vector<HANDLE> frameStartEvents{config.ThreadCount}; - std::vector<HANDLE> frameEndEvents{config.ThreadCount}; - std::vector<std::thread> bkgThreads; - std::vector<PoolTestThreadResult> threadResults{config.ThreadCount}; - for(uint32_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex) - { - frameStartEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL); - frameEndEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL); - bkgThreads.emplace_back(std::bind( - ThreadProc, - &threadResults[threadIndex], - threadRandSeed + threadIndex, - frameStartEvents[threadIndex], - frameEndEvents[threadIndex])); - } - - // Execute frames. - TEST(config.ThreadCount <= MAXIMUM_WAIT_OBJECTS); - for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex) - { - vmaSetCurrentFrameIndex(g_hAllocator, frameIndex); - for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex) - SetEvent(frameStartEvents[threadIndex]); - WaitForMultipleObjects(config.ThreadCount, &frameEndEvents[0], TRUE, INFINITE); - } - - // Wait for threads finished - for(size_t i = 0; i < bkgThreads.size(); ++i) - { - bkgThreads[i].join(); - CloseHandle(frameEndEvents[i]); - CloseHandle(frameStartEvents[i]); - } - bkgThreads.clear(); - - // Finish time measurement - before destroying pool. - outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg; - - vmaDestroyPool(g_hAllocator, pool); - - outResult.AllocationTimeMin = duration::max(); - outResult.AllocationTimeAvg = duration::zero(); - outResult.AllocationTimeMax = duration::min(); - outResult.DeallocationTimeMin = duration::max(); - outResult.DeallocationTimeAvg = duration::zero(); - outResult.DeallocationTimeMax = duration::min(); - outResult.LostAllocationCount = 0; - outResult.LostAllocationTotalSize = 0; - outResult.FailedAllocationCount = 0; - outResult.FailedAllocationTotalSize = 0; - size_t allocationCount = 0; - size_t deallocationCount = 0; - for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex) - { - const PoolTestThreadResult& threadResult = threadResults[threadIndex]; - outResult.AllocationTimeMin = std::min(outResult.AllocationTimeMin, threadResult.AllocationTimeMin); - outResult.AllocationTimeMax = std::max(outResult.AllocationTimeMax, threadResult.AllocationTimeMax); - outResult.AllocationTimeAvg += threadResult.AllocationTimeSum; - outResult.DeallocationTimeMin = std::min(outResult.DeallocationTimeMin, threadResult.DeallocationTimeMin); - outResult.DeallocationTimeMax = std::max(outResult.DeallocationTimeMax, threadResult.DeallocationTimeMax); - outResult.DeallocationTimeAvg += threadResult.DeallocationTimeSum; - allocationCount += threadResult.AllocationCount; - deallocationCount += threadResult.DeallocationCount; - outResult.FailedAllocationCount += threadResult.FailedAllocationCount; - outResult.FailedAllocationTotalSize += threadResult.FailedAllocationTotalSize; - outResult.LostAllocationCount += threadResult.LostAllocationCount; - outResult.LostAllocationTotalSize += threadResult.LostAllocationTotalSize; - } - if(allocationCount) - outResult.AllocationTimeAvg /= allocationCount; - if(deallocationCount) - outResult.DeallocationTimeAvg /= deallocationCount; -} - -static inline bool MemoryRegionsOverlap(char* ptr1, size_t size1, char* ptr2, size_t size2) -{ - if(ptr1 < ptr2) - return ptr1 + size1 > ptr2; - else if(ptr2 < ptr1) - return ptr2 + size2 > ptr1; - else - return true; -} - -static void TestMemoryUsage() -{ - wprintf(L"Testing memory usage:\n"); - - static const VmaMemoryUsage lastUsage = VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED; - for(uint32_t usage = 0; usage <= lastUsage; ++usage) - { - switch(usage) - { - case VMA_MEMORY_USAGE_UNKNOWN: printf(" VMA_MEMORY_USAGE_UNKNOWN:\n"); break; - case VMA_MEMORY_USAGE_GPU_ONLY: printf(" VMA_MEMORY_USAGE_GPU_ONLY:\n"); break; - case VMA_MEMORY_USAGE_CPU_ONLY: printf(" VMA_MEMORY_USAGE_CPU_ONLY:\n"); break; - case VMA_MEMORY_USAGE_CPU_TO_GPU: printf(" VMA_MEMORY_USAGE_CPU_TO_GPU:\n"); break; - case VMA_MEMORY_USAGE_GPU_TO_CPU: printf(" VMA_MEMORY_USAGE_GPU_TO_CPU:\n"); break; - case VMA_MEMORY_USAGE_CPU_COPY: printf(" VMA_MEMORY_USAGE_CPU_COPY:\n"); break; - case VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED: printf(" VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED:\n"); break; - default: assert(0); - } - - auto printResult = [](const char* testName, VkResult res, uint32_t memoryTypeBits, uint32_t memoryTypeIndex) - { - if(res == VK_SUCCESS) - printf(" %s: memoryTypeBits=0x%X, memoryTypeIndex=%u\n", testName, memoryTypeBits, memoryTypeIndex); - else - printf(" %s: memoryTypeBits=0x%X, FAILED with res=%d\n", testName, memoryTypeBits, (int32_t)res); - }; - - // 1: Buffer for copy - { - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.size = 65536; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - VkBuffer buf = VK_NULL_HANDLE; - VkResult res = vkCreateBuffer(g_hDevice, &bufCreateInfo, g_Allocs, &buf); - TEST(res == VK_SUCCESS && buf != VK_NULL_HANDLE); - - VkMemoryRequirements memReq = {}; - vkGetBufferMemoryRequirements(g_hDevice, buf, &memReq); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = (VmaMemoryUsage)usage; - VmaAllocation alloc = VK_NULL_HANDLE; - VmaAllocationInfo allocInfo = {}; - res = vmaAllocateMemoryForBuffer(g_hAllocator, buf, &allocCreateInfo, &alloc, &allocInfo); - if(res == VK_SUCCESS) - { - TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0); - res = vkBindBufferMemory(g_hDevice, buf, allocInfo.deviceMemory, allocInfo.offset); - TEST(res == VK_SUCCESS); - } - printResult("Buffer TRANSFER_DST + TRANSFER_SRC", res, memReq.memoryTypeBits, allocInfo.memoryType); - vmaDestroyBuffer(g_hAllocator, buf, alloc); - } - - // 2: Vertex buffer - { - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.size = 65536; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - - VkBuffer buf = VK_NULL_HANDLE; - VkResult res = vkCreateBuffer(g_hDevice, &bufCreateInfo, g_Allocs, &buf); - TEST(res == VK_SUCCESS && buf != VK_NULL_HANDLE); - - VkMemoryRequirements memReq = {}; - vkGetBufferMemoryRequirements(g_hDevice, buf, &memReq); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = (VmaMemoryUsage)usage; - VmaAllocation alloc = VK_NULL_HANDLE; - VmaAllocationInfo allocInfo = {}; - res = vmaAllocateMemoryForBuffer(g_hAllocator, buf, &allocCreateInfo, &alloc, &allocInfo); - if(res == VK_SUCCESS) - { - TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0); - res = vkBindBufferMemory(g_hDevice, buf, allocInfo.deviceMemory, allocInfo.offset); - TEST(res == VK_SUCCESS); - } - printResult("Buffer TRANSFER_DST + VERTEX_BUFFER", res, memReq.memoryTypeBits, allocInfo.memoryType); - vmaDestroyBuffer(g_hAllocator, buf, alloc); - } - - // 3: Image for copy, OPTIMAL - { - VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imgCreateInfo.extent.width = 256; - imgCreateInfo.extent.height = 256; - imgCreateInfo.extent.depth = 1; - imgCreateInfo.mipLevels = 1; - imgCreateInfo.arrayLayers = 1; - imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; - - VkImage img = VK_NULL_HANDLE; - VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img); - TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE); - - VkMemoryRequirements memReq = {}; - vkGetImageMemoryRequirements(g_hDevice, img, &memReq); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = (VmaMemoryUsage)usage; - VmaAllocation alloc = VK_NULL_HANDLE; - VmaAllocationInfo allocInfo = {}; - res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo); - if(res == VK_SUCCESS) - { - TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0); - res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset); - TEST(res == VK_SUCCESS); - } - printResult("Image OPTIMAL TRANSFER_DST + TRANSFER_SRC", res, memReq.memoryTypeBits, allocInfo.memoryType); - - vmaDestroyImage(g_hAllocator, img, alloc); - } - - // 4: Image SAMPLED, OPTIMAL - { - VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imgCreateInfo.extent.width = 256; - imgCreateInfo.extent.height = 256; - imgCreateInfo.extent.depth = 1; - imgCreateInfo.mipLevels = 1; - imgCreateInfo.arrayLayers = 1; - imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; - - VkImage img = VK_NULL_HANDLE; - VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img); - TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE); - - VkMemoryRequirements memReq = {}; - vkGetImageMemoryRequirements(g_hDevice, img, &memReq); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = (VmaMemoryUsage)usage; - VmaAllocation alloc = VK_NULL_HANDLE; - VmaAllocationInfo allocInfo = {}; - res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo); - if(res == VK_SUCCESS) - { - TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0); - res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset); - TEST(res == VK_SUCCESS); - } - printResult("Image OPTIMAL TRANSFER_DST + SAMPLED", res, memReq.memoryTypeBits, allocInfo.memoryType); - vmaDestroyImage(g_hAllocator, img, alloc); - } - - // 5: Image COLOR_ATTACHMENT, OPTIMAL - { - VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imgCreateInfo.extent.width = 256; - imgCreateInfo.extent.height = 256; - imgCreateInfo.extent.depth = 1; - imgCreateInfo.mipLevels = 1; - imgCreateInfo.arrayLayers = 1; - imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imgCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; - - VkImage img = VK_NULL_HANDLE; - VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img); - TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE); - - VkMemoryRequirements memReq = {}; - vkGetImageMemoryRequirements(g_hDevice, img, &memReq); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = (VmaMemoryUsage)usage; - VmaAllocation alloc = VK_NULL_HANDLE; - VmaAllocationInfo allocInfo = {}; - res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo); - if(res == VK_SUCCESS) - { - TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0); - res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset); - TEST(res == VK_SUCCESS); - } - printResult("Image OPTIMAL SAMPLED + COLOR_ATTACHMENT", res, memReq.memoryTypeBits, allocInfo.memoryType); - vmaDestroyImage(g_hAllocator, img, alloc); - } - } -} - -static uint32_t FindDeviceCoherentMemoryTypeBits() -{ - VkPhysicalDeviceMemoryProperties memProps; - vkGetPhysicalDeviceMemoryProperties(g_hPhysicalDevice, &memProps); - - uint32_t memTypeBits = 0; - for(uint32_t i = 0; i < memProps.memoryTypeCount; ++i) - { - if(memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD) - memTypeBits |= 1u << i; - } - return memTypeBits; -} - -static void TestDeviceCoherentMemory() -{ - if(!VK_AMD_device_coherent_memory_enabled) - return; - - uint32_t deviceCoherentMemoryTypeBits = FindDeviceCoherentMemoryTypeBits(); - // Extension is enabled, feature is enabled, and the device still doesn't support any such memory type? - // OK then, so it's just fake! - if(deviceCoherentMemoryTypeBits == 0) - return; - - wprintf(L"Testing device coherent memory...\n"); - - // 1. Try to allocate buffer from a memory type that is DEVICE_COHERENT. - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.size = 0x10000; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD; - - AllocInfo alloc = {}; - VmaAllocationInfo allocInfo = {}; - VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &alloc.m_Buffer, &alloc.m_Allocation, &allocInfo); - - // Make sure it succeeded and was really created in such memory type. - TEST(res == VK_SUCCESS); - TEST((1u << allocInfo.memoryType) & deviceCoherentMemoryTypeBits); - - alloc.Destroy(); - - // 2. Try to create a pool in such memory type. - { - VmaPoolCreateInfo poolCreateInfo = {}; - - res = vmaFindMemoryTypeIndex(g_hAllocator, UINT32_MAX, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex); - TEST(res == VK_SUCCESS); - TEST((1u << poolCreateInfo.memoryTypeIndex) & deviceCoherentMemoryTypeBits); - - VmaPool pool = VK_NULL_HANDLE; - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - TEST(res == VK_SUCCESS); - - vmaDestroyPool(g_hAllocator, pool); - } - - // 3. Try the same with a local allocator created without VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT. - - VmaAllocatorCreateInfo allocatorCreateInfo = {}; - SetAllocatorCreateInfo(allocatorCreateInfo); - allocatorCreateInfo.flags &= ~VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT; - - VmaAllocator localAllocator = VK_NULL_HANDLE; - res = vmaCreateAllocator(&allocatorCreateInfo, &localAllocator); - TEST(res == VK_SUCCESS && localAllocator); - - res = vmaCreateBuffer(localAllocator, &bufCreateInfo, &allocCreateInfo, &alloc.m_Buffer, &alloc.m_Allocation, &allocInfo); - - // Make sure it failed. - TEST(res != VK_SUCCESS && !alloc.m_Buffer && !alloc.m_Allocation); - - // 4. Try to find memory type. - { - uint32_t memTypeIndex = UINT_MAX; - res = vmaFindMemoryTypeIndex(localAllocator, UINT32_MAX, &allocCreateInfo, &memTypeIndex); - TEST(res != VK_SUCCESS); - } - - vmaDestroyAllocator(localAllocator); -} - -static void TestBudget() -{ - wprintf(L"Testing budget...\n"); - - static const VkDeviceSize BUF_SIZE = 10ull * 1024 * 1024; - static const uint32_t BUF_COUNT = 4; - - const VkPhysicalDeviceMemoryProperties* memProps = {}; - vmaGetMemoryProperties(g_hAllocator, &memProps); - - for(uint32_t testIndex = 0; testIndex < 2; ++testIndex) - { - vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); - - VmaBudget budgetBeg[VK_MAX_MEMORY_HEAPS] = {}; - vmaGetBudget(g_hAllocator, budgetBeg); - - for(uint32_t i = 0; i < memProps->memoryHeapCount; ++i) - { - TEST(budgetBeg[i].budget > 0); - TEST(budgetBeg[i].budget <= memProps->memoryHeaps[i].size); - TEST(budgetBeg[i].allocationBytes <= budgetBeg[i].blockBytes); - } - - VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufInfo.size = BUF_SIZE; - bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - if(testIndex == 0) - { - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - } - - // CREATE BUFFERS - uint32_t heapIndex = 0; - BufferInfo bufInfos[BUF_COUNT] = {}; - for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex) - { - VmaAllocationInfo allocInfo; - VkResult res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, - &bufInfos[bufIndex].Buffer, &bufInfos[bufIndex].Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - if(bufIndex == 0) - { - heapIndex = MemoryTypeToHeap(allocInfo.memoryType); - } - else - { - // All buffers need to fall into the same heap. - TEST(MemoryTypeToHeap(allocInfo.memoryType) == heapIndex); - } - } - - VmaBudget budgetWithBufs[VK_MAX_MEMORY_HEAPS] = {}; - vmaGetBudget(g_hAllocator, budgetWithBufs); - - // DESTROY BUFFERS - for(size_t bufIndex = BUF_COUNT; bufIndex--; ) - { - vmaDestroyBuffer(g_hAllocator, bufInfos[bufIndex].Buffer, bufInfos[bufIndex].Allocation); - } - - VmaBudget budgetEnd[VK_MAX_MEMORY_HEAPS] = {}; - vmaGetBudget(g_hAllocator, budgetEnd); - - // CHECK - for(uint32_t i = 0; i < memProps->memoryHeapCount; ++i) - { - TEST(budgetEnd[i].allocationBytes <= budgetEnd[i].blockBytes); - if(i == heapIndex) - { - TEST(budgetEnd[i].allocationBytes == budgetBeg[i].allocationBytes); - TEST(budgetWithBufs[i].allocationBytes == budgetBeg[i].allocationBytes + BUF_SIZE * BUF_COUNT); - TEST(budgetWithBufs[i].blockBytes >= budgetEnd[i].blockBytes); - } - else - { - TEST(budgetEnd[i].allocationBytes == budgetEnd[i].allocationBytes && - budgetEnd[i].allocationBytes == budgetWithBufs[i].allocationBytes); - TEST(budgetEnd[i].blockBytes == budgetEnd[i].blockBytes && - budgetEnd[i].blockBytes == budgetWithBufs[i].blockBytes); - } - } - } -} - -static void TestAliasing() -{ - wprintf(L"Testing aliasing...\n"); - - /* - This is just a simple test, more like a code sample to demonstrate it's possible. - */ - - // A 512x512 texture to be sampled. - VkImageCreateInfo img1CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - img1CreateInfo.imageType = VK_IMAGE_TYPE_2D; - img1CreateInfo.extent.width = 512; - img1CreateInfo.extent.height = 512; - img1CreateInfo.extent.depth = 1; - img1CreateInfo.mipLevels = 10; - img1CreateInfo.arrayLayers = 1; - img1CreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; - img1CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - img1CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - img1CreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - img1CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; - - // A full screen texture to be used as color attachment. - VkImageCreateInfo img2CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - img2CreateInfo.imageType = VK_IMAGE_TYPE_2D; - img2CreateInfo.extent.width = 1920; - img2CreateInfo.extent.height = 1080; - img2CreateInfo.extent.depth = 1; - img2CreateInfo.mipLevels = 1; - img2CreateInfo.arrayLayers = 1; - img2CreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - img2CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - img2CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - img2CreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - img2CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; - - VkImage img1 = VK_NULL_HANDLE; - ERR_GUARD_VULKAN(vkCreateImage(g_hDevice, &img1CreateInfo, g_Allocs, &img1)); - VkImage img2 = VK_NULL_HANDLE; - ERR_GUARD_VULKAN(vkCreateImage(g_hDevice, &img2CreateInfo, g_Allocs, &img2)); - - VkMemoryRequirements img1MemReq = {}; - vkGetImageMemoryRequirements(g_hDevice, img1, &img1MemReq); - VkMemoryRequirements img2MemReq = {}; - vkGetImageMemoryRequirements(g_hDevice, img2, &img2MemReq); - - VkMemoryRequirements finalMemReq = {}; - finalMemReq.size = std::max(img1MemReq.size, img2MemReq.size); - finalMemReq.alignment = std::max(img1MemReq.alignment, img2MemReq.alignment); - finalMemReq.memoryTypeBits = img1MemReq.memoryTypeBits & img2MemReq.memoryTypeBits; - if(finalMemReq.memoryTypeBits != 0) - { - wprintf(L" size: max(%llu, %llu) = %llu\n", - img1MemReq.size, img2MemReq.size, finalMemReq.size); - wprintf(L" alignment: max(%llu, %llu) = %llu\n", - img1MemReq.alignment, img2MemReq.alignment, finalMemReq.alignment); - wprintf(L" memoryTypeBits: %u & %u = %u\n", - img1MemReq.memoryTypeBits, img2MemReq.memoryTypeBits, finalMemReq.memoryTypeBits); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - VmaAllocation alloc = VK_NULL_HANDLE; - ERR_GUARD_VULKAN(vmaAllocateMemory(g_hAllocator, &finalMemReq, &allocCreateInfo, &alloc, nullptr)); - - ERR_GUARD_VULKAN(vmaBindImageMemory(g_hAllocator, alloc, img1)); - ERR_GUARD_VULKAN(vmaBindImageMemory(g_hAllocator, alloc, img2)); - - // You can use img1, img2 here, but not at the same time! - - vmaFreeMemory(g_hAllocator, alloc); - } - else - { - wprintf(L" Textures cannot alias!\n"); - } - - vkDestroyImage(g_hDevice, img2, g_Allocs); - vkDestroyImage(g_hDevice, img1, g_Allocs); -} - -static void TestMapping() -{ - wprintf(L"Testing mapping...\n"); - - VkResult res; - uint32_t memTypeIndex = UINT32_MAX; - - enum TEST - { - TEST_NORMAL, - TEST_POOL, - TEST_DEDICATED, - TEST_COUNT - }; - for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex) - { - VmaPool pool = nullptr; - if(testIndex == TEST_POOL) - { - TEST(memTypeIndex != UINT32_MAX); - VmaPoolCreateInfo poolInfo = {}; - poolInfo.memoryTypeIndex = memTypeIndex; - res = vmaCreatePool(g_hAllocator, &poolInfo, &pool); - TEST(res == VK_SUCCESS); - } - - VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufInfo.size = 0x10000; - bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - allocCreateInfo.pool = pool; - if(testIndex == TEST_DEDICATED) - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - - VmaAllocationInfo allocInfo; - - // Mapped manually - - // Create 2 buffers. - BufferInfo bufferInfos[3]; - for(size_t i = 0; i < 2; ++i) - { - res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, - &bufferInfos[i].Buffer, &bufferInfos[i].Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - TEST(allocInfo.pMappedData == nullptr); - memTypeIndex = allocInfo.memoryType; - } - - // Map buffer 0. - char* data00 = nullptr; - res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data00); - TEST(res == VK_SUCCESS && data00 != nullptr); - data00[0xFFFF] = data00[0]; - - // Map buffer 0 second time. - char* data01 = nullptr; - res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data01); - TEST(res == VK_SUCCESS && data01 == data00); - - // Map buffer 1. - char* data1 = nullptr; - res = vmaMapMemory(g_hAllocator, bufferInfos[1].Allocation, (void**)&data1); - TEST(res == VK_SUCCESS && data1 != nullptr); - TEST(!MemoryRegionsOverlap(data00, (size_t)bufInfo.size, data1, (size_t)bufInfo.size)); - data1[0xFFFF] = data1[0]; - - // Unmap buffer 0 two times. - vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation); - vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation); - vmaGetAllocationInfo(g_hAllocator, bufferInfos[0].Allocation, &allocInfo); - TEST(allocInfo.pMappedData == nullptr); - - // Unmap buffer 1. - vmaUnmapMemory(g_hAllocator, bufferInfos[1].Allocation); - vmaGetAllocationInfo(g_hAllocator, bufferInfos[1].Allocation, &allocInfo); - TEST(allocInfo.pMappedData == nullptr); - - // Create 3rd buffer - persistently mapped. - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT; - res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, - &bufferInfos[2].Buffer, &bufferInfos[2].Allocation, &allocInfo); - TEST(res == VK_SUCCESS && allocInfo.pMappedData != nullptr); - - // Map buffer 2. - char* data2 = nullptr; - res = vmaMapMemory(g_hAllocator, bufferInfos[2].Allocation, (void**)&data2); - TEST(res == VK_SUCCESS && data2 == allocInfo.pMappedData); - data2[0xFFFF] = data2[0]; - - // Unmap buffer 2. - vmaUnmapMemory(g_hAllocator, bufferInfos[2].Allocation); - vmaGetAllocationInfo(g_hAllocator, bufferInfos[2].Allocation, &allocInfo); - TEST(allocInfo.pMappedData == data2); - - // Destroy all buffers. - for(size_t i = 3; i--; ) - vmaDestroyBuffer(g_hAllocator, bufferInfos[i].Buffer, bufferInfos[i].Allocation); - - vmaDestroyPool(g_hAllocator, pool); - } -} - -// Test CREATE_MAPPED with required DEVICE_LOCAL. There was a bug with it. -static void TestDeviceLocalMapped() -{ - VkResult res; - - for(uint32_t testIndex = 0; testIndex < 3; ++testIndex) - { - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; - bufCreateInfo.size = 4096; - - VmaPool pool = VK_NULL_HANDLE; - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - if(testIndex == 2) - { - VmaPoolCreateInfo poolCreateInfo = {}; - res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex); - TEST(res == VK_SUCCESS); - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - TEST(res == VK_SUCCESS); - allocCreateInfo.pool = pool; - } - else if(testIndex == 1) - { - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; - } - - VkBuffer buf = VK_NULL_HANDLE; - VmaAllocation alloc = VK_NULL_HANDLE; - VmaAllocationInfo allocInfo = {}; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); - TEST(res == VK_SUCCESS && alloc); - - VkMemoryPropertyFlags memTypeFlags = 0; - vmaGetMemoryTypeProperties(g_hAllocator, allocInfo.memoryType, &memTypeFlags); - const bool shouldBeMapped = (memTypeFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0; - TEST((allocInfo.pMappedData != nullptr) == shouldBeMapped); - - vmaDestroyBuffer(g_hAllocator, buf, alloc); - vmaDestroyPool(g_hAllocator, pool); - } -} - -static void TestMappingMultithreaded() -{ - wprintf(L"Testing mapping multithreaded...\n"); - - static const uint32_t threadCount = 16; - static const uint32_t bufferCount = 1024; - static const uint32_t threadBufferCount = bufferCount / threadCount; - - VkResult res; - volatile uint32_t memTypeIndex = UINT32_MAX; - - enum TEST - { - TEST_NORMAL, - TEST_POOL, - TEST_DEDICATED, - TEST_COUNT - }; - for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex) - { - VmaPool pool = nullptr; - if(testIndex == TEST_POOL) - { - TEST(memTypeIndex != UINT32_MAX); - VmaPoolCreateInfo poolInfo = {}; - poolInfo.memoryTypeIndex = memTypeIndex; - res = vmaCreatePool(g_hAllocator, &poolInfo, &pool); - TEST(res == VK_SUCCESS); - } - - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.size = 0x10000; - bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - allocCreateInfo.pool = pool; - if(testIndex == TEST_DEDICATED) - allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - - std::thread threads[threadCount]; - for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex) - { - threads[threadIndex] = std::thread([=, &memTypeIndex](){ - // ======== THREAD FUNCTION ======== - - RandomNumberGenerator rand{threadIndex}; - - enum class MODE - { - // Don't map this buffer at all. - DONT_MAP, - // Map and quickly unmap. - MAP_FOR_MOMENT, - // Map and unmap before destruction. - MAP_FOR_LONGER, - // Map two times. Quickly unmap, second unmap before destruction. - MAP_TWO_TIMES, - // Create this buffer as persistently mapped. - PERSISTENTLY_MAPPED, - COUNT - }; - std::vector<BufferInfo> bufInfos{threadBufferCount}; - std::vector<MODE> bufModes{threadBufferCount}; - - for(uint32_t bufferIndex = 0; bufferIndex < threadBufferCount; ++bufferIndex) - { - BufferInfo& bufInfo = bufInfos[bufferIndex]; - const MODE mode = (MODE)(rand.Generate() % (uint32_t)MODE::COUNT); - bufModes[bufferIndex] = mode; - - VmaAllocationCreateInfo localAllocCreateInfo = allocCreateInfo; - if(mode == MODE::PERSISTENTLY_MAPPED) - localAllocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT; - - VmaAllocationInfo allocInfo; - VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &localAllocCreateInfo, - &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - - if(memTypeIndex == UINT32_MAX) - memTypeIndex = allocInfo.memoryType; - - char* data = nullptr; - - if(mode == MODE::PERSISTENTLY_MAPPED) - { - data = (char*)allocInfo.pMappedData; - TEST(data != nullptr); - } - else if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_FOR_LONGER || - mode == MODE::MAP_TWO_TIMES) - { - TEST(data == nullptr); - res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data); - TEST(res == VK_SUCCESS && data != nullptr); - - if(mode == MODE::MAP_TWO_TIMES) - { - char* data2 = nullptr; - res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data2); - TEST(res == VK_SUCCESS && data2 == data); - } - } - else if(mode == MODE::DONT_MAP) - { - TEST(allocInfo.pMappedData == nullptr); - } - else - TEST(0); - - // Test if reading and writing from the beginning and end of mapped memory doesn't crash. - if(data) - data[0xFFFF] = data[0]; - - if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_TWO_TIMES) - { - vmaUnmapMemory(g_hAllocator, bufInfo.Allocation); - - VmaAllocationInfo allocInfo; - vmaGetAllocationInfo(g_hAllocator, bufInfo.Allocation, &allocInfo); - if(mode == MODE::MAP_FOR_MOMENT) - TEST(allocInfo.pMappedData == nullptr); - else - TEST(allocInfo.pMappedData == data); - } - - switch(rand.Generate() % 3) - { - case 0: Sleep(0); break; // Yield. - case 1: Sleep(10); break; // 10 ms - // default: No sleep. - } - - // Test if reading and writing from the beginning and end of mapped memory doesn't crash. - if(data) - data[0xFFFF] = data[0]; - } - - for(size_t bufferIndex = threadBufferCount; bufferIndex--; ) - { - if(bufModes[bufferIndex] == MODE::MAP_FOR_LONGER || - bufModes[bufferIndex] == MODE::MAP_TWO_TIMES) - { - vmaUnmapMemory(g_hAllocator, bufInfos[bufferIndex].Allocation); - - VmaAllocationInfo allocInfo; - vmaGetAllocationInfo(g_hAllocator, bufInfos[bufferIndex].Allocation, &allocInfo); - TEST(allocInfo.pMappedData == nullptr); - } - - vmaDestroyBuffer(g_hAllocator, bufInfos[bufferIndex].Buffer, bufInfos[bufferIndex].Allocation); - } - }); - } - - for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex) - threads[threadIndex].join(); - - vmaDestroyPool(g_hAllocator, pool); - } -} - -static void WriteMainTestResultHeader(FILE* file) -{ - fprintf(file, - "Code,Time," - "Threads,Buffers and images,Sizes,Operations,Allocation strategy,Free order," - "Total Time (us)," - "Allocation Time Min (us)," - "Allocation Time Avg (us)," - "Allocation Time Max (us)," - "Deallocation Time Min (us)," - "Deallocation Time Avg (us)," - "Deallocation Time Max (us)," - "Total Memory Allocated (B)," - "Free Range Size Avg (B)," - "Free Range Size Max (B)\n"); -} - -static void WriteMainTestResult( - FILE* file, - const char* codeDescription, - const char* testDescription, - const Config& config, const Result& result) -{ - float totalTimeSeconds = ToFloatSeconds(result.TotalTime); - float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin); - float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg); - float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax); - float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin); - float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg); - float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax); - - std::string currTime; - CurrentTimeToStr(currTime); - - fprintf(file, - "%s,%s,%s," - "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n", - codeDescription, - currTime.c_str(), - testDescription, - totalTimeSeconds * 1e6f, - allocationTimeMinSeconds * 1e6f, - allocationTimeAvgSeconds * 1e6f, - allocationTimeMaxSeconds * 1e6f, - deallocationTimeMinSeconds * 1e6f, - deallocationTimeAvgSeconds * 1e6f, - deallocationTimeMaxSeconds * 1e6f, - result.TotalMemoryAllocated, - result.FreeRangeSizeAvg, - result.FreeRangeSizeMax); -} - -static void WritePoolTestResultHeader(FILE* file) -{ - fprintf(file, - "Code,Test,Time," - "Config," - "Total Time (us)," - "Allocation Time Min (us)," - "Allocation Time Avg (us)," - "Allocation Time Max (us)," - "Deallocation Time Min (us)," - "Deallocation Time Avg (us)," - "Deallocation Time Max (us)," - "Lost Allocation Count," - "Lost Allocation Total Size (B)," - "Failed Allocation Count," - "Failed Allocation Total Size (B)\n"); -} - -static void WritePoolTestResult( - FILE* file, - const char* codeDescription, - const char* testDescription, - const PoolTestConfig& config, - const PoolTestResult& result) -{ - float totalTimeSeconds = ToFloatSeconds(result.TotalTime); - float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin); - float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg); - float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax); - float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin); - float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg); - float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax); - - std::string currTime; - CurrentTimeToStr(currTime); - - fprintf(file, - "%s,%s,%s," - "ThreadCount=%u PoolSize=%llu FrameCount=%u TotalItemCount=%u UsedItemCount=%u...%u ItemsToMakeUnusedPercent=%u," - "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u,%I64u\n", - // General - codeDescription, - testDescription, - currTime.c_str(), - // Config - config.ThreadCount, - (unsigned long long)config.PoolSize, - config.FrameCount, - config.TotalItemCount, - config.UsedItemCountMin, - config.UsedItemCountMax, - config.ItemsToMakeUnusedPercent, - // Results - totalTimeSeconds * 1e6f, - allocationTimeMinSeconds * 1e6f, - allocationTimeAvgSeconds * 1e6f, - allocationTimeMaxSeconds * 1e6f, - deallocationTimeMinSeconds * 1e6f, - deallocationTimeAvgSeconds * 1e6f, - deallocationTimeMaxSeconds * 1e6f, - result.LostAllocationCount, - result.LostAllocationTotalSize, - result.FailedAllocationCount, - result.FailedAllocationTotalSize); -} - -static void PerformCustomMainTest(FILE* file) -{ - Config config{}; - config.RandSeed = 65735476; - //config.MaxBytesToAllocate = 4ull * 1024 * 1024; // 4 MB - config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB - config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY - config.FreeOrder = FREE_ORDER::FORWARD; - config.ThreadCount = 16; - config.ThreadsUsingCommonAllocationsProbabilityPercent = 50; - config.AllocationStrategy = 0; - - // Buffers - //config.AllocationSizes.push_back({4, 16, 1024}); - config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB - - // Images - //config.AllocationSizes.push_back({4, 0, 0, 4, 32}); - //config.AllocationSizes.push_back({4, 0, 0, 256, 2048}); - - config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100; - config.AdditionalOperationCount = 1024; - - Result result{}; - VkResult res = MainTest(result, config); - TEST(res == VK_SUCCESS); - WriteMainTestResult(file, "Foo", "CustomTest", config, result); -} - -static void PerformCustomPoolTest(FILE* file) -{ - PoolTestConfig config; - config.PoolSize = 100 * 1024 * 1024; - config.RandSeed = 2345764; - config.ThreadCount = 1; - config.FrameCount = 200; - config.ItemsToMakeUnusedPercent = 2; - - AllocationSize allocSize = {}; - allocSize.BufferSizeMin = 1024; - allocSize.BufferSizeMax = 1024 * 1024; - allocSize.Probability = 1; - config.AllocationSizes.push_back(allocSize); - - allocSize.BufferSizeMin = 0; - allocSize.BufferSizeMax = 0; - allocSize.ImageSizeMin = 128; - allocSize.ImageSizeMax = 1024; - allocSize.Probability = 1; - config.AllocationSizes.push_back(allocSize); - - config.PoolSize = config.CalcAvgResourceSize() * 200; - config.UsedItemCountMax = 160; - config.TotalItemCount = config.UsedItemCountMax * 10; - config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100; - - PoolTestResult result = {}; - TestPool_Benchmark(result, config); - - WritePoolTestResult(file, "Code desc", "Test desc", config, result); -} - -static void PerformMainTests(FILE* file) -{ - wprintf(L"MAIN TESTS:\n"); - - uint32_t repeatCount = 1; - if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3; - - Config config{}; - config.RandSeed = 65735476; - config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY - config.FreeOrder = FREE_ORDER::FORWARD; - - size_t threadCountCount = 1; - switch(ConfigType) - { - case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break; - case CONFIG_TYPE_SMALL: threadCountCount = 2; break; - case CONFIG_TYPE_AVERAGE: threadCountCount = 3; break; - case CONFIG_TYPE_LARGE: threadCountCount = 5; break; - case CONFIG_TYPE_MAXIMUM: threadCountCount = 7; break; - default: assert(0); - } - - const size_t strategyCount = GetAllocationStrategyCount(); - - for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex) - { - std::string desc1; - - switch(threadCountIndex) - { - case 0: - desc1 += "1_thread"; - config.ThreadCount = 1; - config.ThreadsUsingCommonAllocationsProbabilityPercent = 0; - break; - case 1: - desc1 += "16_threads+0%_common"; - config.ThreadCount = 16; - config.ThreadsUsingCommonAllocationsProbabilityPercent = 0; - break; - case 2: - desc1 += "16_threads+50%_common"; - config.ThreadCount = 16; - config.ThreadsUsingCommonAllocationsProbabilityPercent = 50; - break; - case 3: - desc1 += "16_threads+100%_common"; - config.ThreadCount = 16; - config.ThreadsUsingCommonAllocationsProbabilityPercent = 100; - break; - case 4: - desc1 += "2_threads+0%_common"; - config.ThreadCount = 2; - config.ThreadsUsingCommonAllocationsProbabilityPercent = 0; - break; - case 5: - desc1 += "2_threads+50%_common"; - config.ThreadCount = 2; - config.ThreadsUsingCommonAllocationsProbabilityPercent = 50; - break; - case 6: - desc1 += "2_threads+100%_common"; - config.ThreadCount = 2; - config.ThreadsUsingCommonAllocationsProbabilityPercent = 100; - break; - default: - assert(0); - } - - // 0 = buffers, 1 = images, 2 = buffers and images - size_t buffersVsImagesCount = 2; - if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount; - for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex) - { - std::string desc2 = desc1; - switch(buffersVsImagesIndex) - { - case 0: desc2 += ",Buffers"; break; - case 1: desc2 += ",Images"; break; - case 2: desc2 += ",Buffers+Images"; break; - default: assert(0); - } - - // 0 = small, 1 = large, 2 = small and large - size_t smallVsLargeCount = 2; - if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount; - for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex) - { - std::string desc3 = desc2; - switch(smallVsLargeIndex) - { - case 0: desc3 += ",Small"; break; - case 1: desc3 += ",Large"; break; - case 2: desc3 += ",Small+Large"; break; - default: assert(0); - } - - if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) - config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB - else - config.MaxBytesToAllocate = 4ull * 1024 * 1024; - - // 0 = varying sizes min...max, 1 = set of constant sizes - size_t constantSizesCount = 1; - if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount; - for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex) - { - std::string desc4 = desc3; - switch(constantSizesIndex) - { - case 0: desc4 += " Varying_sizes"; break; - case 1: desc4 += " Constant_sizes"; break; - default: assert(0); - } - - config.AllocationSizes.clear(); - // Buffers present - if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2) - { - // Small - if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) - { - // Varying size - if(constantSizesIndex == 0) - config.AllocationSizes.push_back({4, 16, 1024}); - // Constant sizes - else - { - config.AllocationSizes.push_back({1, 16, 16}); - config.AllocationSizes.push_back({1, 64, 64}); - config.AllocationSizes.push_back({1, 256, 256}); - config.AllocationSizes.push_back({1, 1024, 1024}); - } - } - // Large - if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) - { - // Varying size - if(constantSizesIndex == 0) - config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB - // Constant sizes - else - { - config.AllocationSizes.push_back({1, 0x10000, 0x10000}); - config.AllocationSizes.push_back({1, 0x80000, 0x80000}); - config.AllocationSizes.push_back({1, 0x200000, 0x200000}); - config.AllocationSizes.push_back({1, 0xA00000, 0xA00000}); - } - } - } - // Images present - if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2) - { - // Small - if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) - { - // Varying size - if(constantSizesIndex == 0) - config.AllocationSizes.push_back({4, 0, 0, 4, 32}); - // Constant sizes - else - { - config.AllocationSizes.push_back({1, 0, 0, 4, 4}); - config.AllocationSizes.push_back({1, 0, 0, 8, 8}); - config.AllocationSizes.push_back({1, 0, 0, 16, 16}); - config.AllocationSizes.push_back({1, 0, 0, 32, 32}); - } - } - // Large - if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) - { - // Varying size - if(constantSizesIndex == 0) - config.AllocationSizes.push_back({4, 0, 0, 256, 2048}); - // Constant sizes - else - { - config.AllocationSizes.push_back({1, 0, 0, 256, 256}); - config.AllocationSizes.push_back({1, 0, 0, 512, 512}); - config.AllocationSizes.push_back({1, 0, 0, 1024, 1024}); - config.AllocationSizes.push_back({1, 0, 0, 2048, 2048}); - } - } - } - - // 0 = 100%, additional_operations = 0, 1 = 50%, 2 = 5%, 3 = 95% additional_operations = a lot - size_t beginBytesToAllocateCount = 1; - if(ConfigType >= CONFIG_TYPE_SMALL) ++beginBytesToAllocateCount; - if(ConfigType >= CONFIG_TYPE_AVERAGE) ++beginBytesToAllocateCount; - if(ConfigType >= CONFIG_TYPE_LARGE) ++beginBytesToAllocateCount; - for(size_t beginBytesToAllocateIndex = 0; beginBytesToAllocateIndex < beginBytesToAllocateCount; ++beginBytesToAllocateIndex) - { - std::string desc5 = desc4; - - switch(beginBytesToAllocateIndex) - { - case 0: - desc5 += ",Allocate_100%"; - config.BeginBytesToAllocate = config.MaxBytesToAllocate; - config.AdditionalOperationCount = 0; - break; - case 1: - desc5 += ",Allocate_50%+Operations"; - config.BeginBytesToAllocate = config.MaxBytesToAllocate * 50 / 100; - config.AdditionalOperationCount = 1024; - break; - case 2: - desc5 += ",Allocate_5%+Operations"; - config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100; - config.AdditionalOperationCount = 1024; - break; - case 3: - desc5 += ",Allocate_95%+Operations"; - config.BeginBytesToAllocate = config.MaxBytesToAllocate * 95 / 100; - config.AdditionalOperationCount = 1024; - break; - default: - assert(0); - } - - for(size_t strategyIndex = 0; strategyIndex < strategyCount; ++strategyIndex) - { - std::string desc6 = desc5; - switch(strategyIndex) - { - case 0: - desc6 += ",BestFit"; - config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT; - break; - case 1: - desc6 += ",WorstFit"; - config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT; - break; - case 2: - desc6 += ",FirstFit"; - config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT; - break; - default: - assert(0); - } - - desc6 += ','; - desc6 += FREE_ORDER_NAMES[(uint32_t)config.FreeOrder]; - - const char* testDescription = desc6.c_str(); - - for(size_t repeat = 0; repeat < repeatCount; ++repeat) - { - printf("%s #%u\n", testDescription, (uint32_t)repeat); - - Result result{}; - VkResult res = MainTest(result, config); - TEST(res == VK_SUCCESS); - if(file) - { - WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result); - } - } - } - } - } - } - } - } -} - -static void PerformPoolTests(FILE* file) -{ - wprintf(L"POOL TESTS:\n"); - - const size_t AVG_RESOURCES_PER_POOL = 300; - - uint32_t repeatCount = 1; - if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3; - - PoolTestConfig config{}; - config.RandSeed = 2346343; - config.FrameCount = 200; - config.ItemsToMakeUnusedPercent = 2; - - size_t threadCountCount = 1; - switch(ConfigType) - { - case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break; - case CONFIG_TYPE_SMALL: threadCountCount = 2; break; - case CONFIG_TYPE_AVERAGE: threadCountCount = 2; break; - case CONFIG_TYPE_LARGE: threadCountCount = 3; break; - case CONFIG_TYPE_MAXIMUM: threadCountCount = 3; break; - default: assert(0); - } - for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex) - { - std::string desc1; - - switch(threadCountIndex) - { - case 0: - desc1 += "1_thread"; - config.ThreadCount = 1; - break; - case 1: - desc1 += "16_threads"; - config.ThreadCount = 16; - break; - case 2: - desc1 += "2_threads"; - config.ThreadCount = 2; - break; - default: - assert(0); - } - - // 0 = buffers, 1 = images, 2 = buffers and images - size_t buffersVsImagesCount = 2; - if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount; - for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex) - { - std::string desc2 = desc1; - switch(buffersVsImagesIndex) - { - case 0: desc2 += " Buffers"; break; - case 1: desc2 += " Images"; break; - case 2: desc2 += " Buffers+Images"; break; - default: assert(0); - } - - // 0 = small, 1 = large, 2 = small and large - size_t smallVsLargeCount = 2; - if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount; - for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex) - { - std::string desc3 = desc2; - switch(smallVsLargeIndex) - { - case 0: desc3 += " Small"; break; - case 1: desc3 += " Large"; break; - case 2: desc3 += " Small+Large"; break; - default: assert(0); - } - - if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) - config.PoolSize = 6ull * 1024 * 1024 * 1024; // 6 GB - else - config.PoolSize = 4ull * 1024 * 1024; - - // 0 = varying sizes min...max, 1 = set of constant sizes - size_t constantSizesCount = 1; - if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount; - for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex) - { - std::string desc4 = desc3; - switch(constantSizesIndex) - { - case 0: desc4 += " Varying_sizes"; break; - case 1: desc4 += " Constant_sizes"; break; - default: assert(0); - } - - config.AllocationSizes.clear(); - // Buffers present - if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2) - { - // Small - if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) - { - // Varying size - if(constantSizesIndex == 0) - config.AllocationSizes.push_back({4, 16, 1024}); - // Constant sizes - else - { - config.AllocationSizes.push_back({1, 16, 16}); - config.AllocationSizes.push_back({1, 64, 64}); - config.AllocationSizes.push_back({1, 256, 256}); - config.AllocationSizes.push_back({1, 1024, 1024}); - } - } - // Large - if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) - { - // Varying size - if(constantSizesIndex == 0) - config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB - // Constant sizes - else - { - config.AllocationSizes.push_back({1, 0x10000, 0x10000}); - config.AllocationSizes.push_back({1, 0x80000, 0x80000}); - config.AllocationSizes.push_back({1, 0x200000, 0x200000}); - config.AllocationSizes.push_back({1, 0xA00000, 0xA00000}); - } - } - } - // Images present - if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2) - { - // Small - if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) - { - // Varying size - if(constantSizesIndex == 0) - config.AllocationSizes.push_back({4, 0, 0, 4, 32}); - // Constant sizes - else - { - config.AllocationSizes.push_back({1, 0, 0, 4, 4}); - config.AllocationSizes.push_back({1, 0, 0, 8, 8}); - config.AllocationSizes.push_back({1, 0, 0, 16, 16}); - config.AllocationSizes.push_back({1, 0, 0, 32, 32}); - } - } - // Large - if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) - { - // Varying size - if(constantSizesIndex == 0) - config.AllocationSizes.push_back({4, 0, 0, 256, 2048}); - // Constant sizes - else - { - config.AllocationSizes.push_back({1, 0, 0, 256, 256}); - config.AllocationSizes.push_back({1, 0, 0, 512, 512}); - config.AllocationSizes.push_back({1, 0, 0, 1024, 1024}); - config.AllocationSizes.push_back({1, 0, 0, 2048, 2048}); - } - } - } - - const VkDeviceSize avgResourceSize = config.CalcAvgResourceSize(); - config.PoolSize = avgResourceSize * AVG_RESOURCES_PER_POOL; - - // 0 = 66%, 1 = 133%, 2 = 100%, 3 = 33%, 4 = 166% - size_t subscriptionModeCount; - switch(ConfigType) - { - case CONFIG_TYPE_MINIMUM: subscriptionModeCount = 2; break; - case CONFIG_TYPE_SMALL: subscriptionModeCount = 2; break; - case CONFIG_TYPE_AVERAGE: subscriptionModeCount = 3; break; - case CONFIG_TYPE_LARGE: subscriptionModeCount = 5; break; - case CONFIG_TYPE_MAXIMUM: subscriptionModeCount = 5; break; - default: assert(0); - } - for(size_t subscriptionModeIndex = 0; subscriptionModeIndex < subscriptionModeCount; ++subscriptionModeIndex) - { - std::string desc5 = desc4; - - switch(subscriptionModeIndex) - { - case 0: - desc5 += " Subscription_66%"; - config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 66 / 100; - break; - case 1: - desc5 += " Subscription_133%"; - config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 133 / 100; - break; - case 2: - desc5 += " Subscription_100%"; - config.UsedItemCountMax = AVG_RESOURCES_PER_POOL; - break; - case 3: - desc5 += " Subscription_33%"; - config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 33 / 100; - break; - case 4: - desc5 += " Subscription_166%"; - config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 166 / 100; - break; - default: - assert(0); - } - - config.TotalItemCount = config.UsedItemCountMax * 5; - config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100; - - const char* testDescription = desc5.c_str(); - - for(size_t repeat = 0; repeat < repeatCount; ++repeat) - { - printf("%s #%u\n", testDescription, (uint32_t)repeat); - - PoolTestResult result{}; - TestPool_Benchmark(result, config); - WritePoolTestResult(file, CODE_DESCRIPTION, testDescription, config, result); - } - } - } - } - } - } -} - -static void BasicTestBuddyAllocator() -{ - wprintf(L"Basic test buddy allocator\n"); - - RandomNumberGenerator rand{76543}; - - VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - sampleBufCreateInfo.size = 1024; // Whatever. - sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - - VmaAllocationCreateInfo sampleAllocCreateInfo = {}; - sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - VmaPoolCreateInfo poolCreateInfo = {}; - VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); - TEST(res == VK_SUCCESS); - - // Deliberately adding 1023 to test usable size smaller than memory block size. - poolCreateInfo.blockSize = 1024 * 1024 + 1023; - poolCreateInfo.flags = VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT; - //poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; - - VmaPool pool = nullptr; - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - TEST(res == VK_SUCCESS); - - VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.pool = pool; - - std::vector<BufferInfo> bufInfo; - BufferInfo newBufInfo; - VmaAllocationInfo allocInfo; - - bufCreateInfo.size = 1024 * 256; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - - bufCreateInfo.size = 1024 * 512; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - - bufCreateInfo.size = 1024 * 128; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - - // Test very small allocation, smaller than minimum node size. - bufCreateInfo.size = 1; - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - - // Test some small allocation with alignment requirement. - { - VkMemoryRequirements memReq; - memReq.alignment = 256; - memReq.memoryTypeBits = UINT32_MAX; - memReq.size = 32; - - newBufInfo.Buffer = VK_NULL_HANDLE; - res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, - &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - TEST(allocInfo.offset % memReq.alignment == 0); - bufInfo.push_back(newBufInfo); - } - - //SaveAllocatorStatsToFile(L"TEST.json"); - - VmaPoolStats stats = {}; - vmaGetPoolStats(g_hAllocator, pool, &stats); - int DBG = 0; // Set breakpoint here to inspect `stats`. - - // Allocate enough new buffers to surely fall into second block. - for(uint32_t i = 0; i < 32; ++i) - { - bufCreateInfo.size = 1024 * (rand.Generate() % 32 + 1); - res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, - &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); - TEST(res == VK_SUCCESS); - bufInfo.push_back(newBufInfo); - } - - SaveAllocatorStatsToFile(L"BuddyTest01.json"); - - // Destroy the buffers in random order. - while(!bufInfo.empty()) - { - const size_t indexToDestroy = rand.Generate() % bufInfo.size(); - const BufferInfo& currBufInfo = bufInfo[indexToDestroy]; - vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); - bufInfo.erase(bufInfo.begin() + indexToDestroy); - } - - vmaDestroyPool(g_hAllocator, pool); -} - -static void BasicTestAllocatePages() -{ - wprintf(L"Basic test allocate pages\n"); - - RandomNumberGenerator rand{765461}; - - VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - sampleBufCreateInfo.size = 1024; // Whatever. - sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - VmaAllocationCreateInfo sampleAllocCreateInfo = {}; - sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - - VmaPoolCreateInfo poolCreateInfo = {}; - VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); - TEST(res == VK_SUCCESS); - - // 1 block of 1 MB. - poolCreateInfo.blockSize = 1024 * 1024; - poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; - - // Create pool. - VmaPool pool = nullptr; - res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); - TEST(res == VK_SUCCESS); - - // Make 100 allocations of 4 KB - they should fit into the pool. - VkMemoryRequirements memReq; - memReq.memoryTypeBits = UINT32_MAX; - memReq.alignment = 4 * 1024; - memReq.size = 4 * 1024; - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - allocCreateInfo.pool = pool; - - constexpr uint32_t allocCount = 100; - - std::vector<VmaAllocation> alloc{allocCount}; - std::vector<VmaAllocationInfo> allocInfo{allocCount}; - res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), allocInfo.data()); - TEST(res == VK_SUCCESS); - for(uint32_t i = 0; i < allocCount; ++i) - { - TEST(alloc[i] != VK_NULL_HANDLE && - allocInfo[i].pMappedData != nullptr && - allocInfo[i].deviceMemory == allocInfo[0].deviceMemory && - allocInfo[i].memoryType == allocInfo[0].memoryType); - } - - // Free the allocations. - vmaFreeMemoryPages(g_hAllocator, allocCount, alloc.data()); - std::fill(alloc.begin(), alloc.end(), nullptr); - std::fill(allocInfo.begin(), allocInfo.end(), VmaAllocationInfo{}); - - // Try to make 100 allocations of 100 KB. This call should fail due to not enough memory. - // Also test optional allocationInfo = null. - memReq.size = 100 * 1024; - res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), nullptr); - TEST(res != VK_SUCCESS); - TEST(std::find_if(alloc.begin(), alloc.end(), [](VmaAllocation alloc){ return alloc != VK_NULL_HANDLE; }) == alloc.end()); - - // Make 100 allocations of 4 KB, but with required alignment of 128 KB. This should also fail. - memReq.size = 4 * 1024; - memReq.alignment = 128 * 1024; - res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), allocInfo.data()); - TEST(res != VK_SUCCESS); - - // Make 100 dedicated allocations of 4 KB. - memReq.alignment = 4 * 1024; - memReq.size = 4 * 1024; - - VmaAllocationCreateInfo dedicatedAllocCreateInfo = {}; - dedicatedAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - dedicatedAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &dedicatedAllocCreateInfo, allocCount, alloc.data(), allocInfo.data()); - TEST(res == VK_SUCCESS); - for(uint32_t i = 0; i < allocCount; ++i) - { - TEST(alloc[i] != VK_NULL_HANDLE && - allocInfo[i].pMappedData != nullptr && - allocInfo[i].memoryType == allocInfo[0].memoryType && - allocInfo[i].offset == 0); - if(i > 0) - { - TEST(allocInfo[i].deviceMemory != allocInfo[0].deviceMemory); - } - } - - // Free the allocations. - vmaFreeMemoryPages(g_hAllocator, allocCount, alloc.data()); - std::fill(alloc.begin(), alloc.end(), nullptr); - std::fill(allocInfo.begin(), allocInfo.end(), VmaAllocationInfo{}); - - vmaDestroyPool(g_hAllocator, pool); -} - -// Test the testing environment. -static void TestGpuData() -{ - RandomNumberGenerator rand = { 53434 }; - - std::vector<AllocInfo> allocInfo; - - for(size_t i = 0; i < 100; ++i) - { - AllocInfo info = {}; - - info.m_BufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - info.m_BufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | - VK_BUFFER_USAGE_TRANSFER_SRC_BIT | - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - info.m_BufferInfo.size = 1024 * 1024 * (rand.Generate() % 9 + 1); - - VmaAllocationCreateInfo allocCreateInfo = {}; - allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - VkResult res = vmaCreateBuffer(g_hAllocator, &info.m_BufferInfo, &allocCreateInfo, &info.m_Buffer, &info.m_Allocation, nullptr); - TEST(res == VK_SUCCESS); - - info.m_StartValue = rand.Generate(); - - allocInfo.push_back(std::move(info)); - } - - UploadGpuData(allocInfo.data(), allocInfo.size()); - - ValidateGpuData(allocInfo.data(), allocInfo.size()); - - DestroyAllAllocations(allocInfo); -} - -void Test() -{ - wprintf(L"TESTING:\n"); - - if(false) - { - //////////////////////////////////////////////////////////////////////////////// - // Temporarily insert custom tests here: - return; - } - - // # Simple tests - - TestBasics(); - TestAllocationVersusResourceSize(); - //TestGpuData(); // Not calling this because it's just testing the testing environment. -#if VMA_DEBUG_MARGIN - TestDebugMargin(); -#else - TestPool_SameSize(); - TestPool_MinBlockCount(); - TestPool_MinAllocationAlignment(); - TestHeapSizeLimit(); -#endif -#if VMA_DEBUG_INITIALIZE_ALLOCATIONS - TestAllocationsInitialization(); -#endif - TestMemoryUsage(); - TestDeviceCoherentMemory(); - TestBudget(); - TestAliasing(); - TestMapping(); - TestDeviceLocalMapped(); - TestMappingMultithreaded(); - TestLinearAllocator(); - ManuallyTestLinearAllocator(); - TestLinearAllocatorMultiBlock(); - - BasicTestBuddyAllocator(); - BasicTestAllocatePages(); - - if(VK_KHR_buffer_device_address_enabled) - TestBufferDeviceAddress(); - if(VK_EXT_memory_priority_enabled) - TestMemoryPriority(); - - { - FILE* file; - fopen_s(&file, "Algorithms.csv", "w"); - assert(file != NULL); - BenchmarkAlgorithms(file); - fclose(file); - } - - TestDefragmentationSimple(); - TestDefragmentationFull(); - TestDefragmentationWholePool(); - TestDefragmentationGpu(); - TestDefragmentationIncrementalBasic(); - TestDefragmentationIncrementalComplex(); - - // # Detailed tests - FILE* file; - fopen_s(&file, "Results.csv", "w"); - assert(file != NULL); - - WriteMainTestResultHeader(file); - PerformMainTests(file); - //PerformCustomMainTest(file); - - WritePoolTestResultHeader(file); - PerformPoolTests(file); - //PerformCustomPoolTest(file); - - fclose(file); - - wprintf(L"Done, all PASSED.\n"); -} - -#endif // #ifdef _WIN32 +// +// Copyright (c) 2017-2021 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 "Tests.h" +#include "VmaUsage.h" +#include "Common.h" +#include <atomic> +#include <thread> +#include <mutex> +#include <functional> + +#ifdef _WIN32 + +static const char* CODE_DESCRIPTION = "Foo"; + +extern VkCommandBuffer g_hTemporaryCommandBuffer; +extern const VkAllocationCallbacks* g_Allocs; +extern bool VK_KHR_buffer_device_address_enabled; +extern bool VK_EXT_memory_priority_enabled; +extern PFN_vkGetBufferDeviceAddressKHR g_vkGetBufferDeviceAddressKHR; +void BeginSingleTimeCommands(); +void EndSingleTimeCommands(); +void SetDebugUtilsObjectName(VkObjectType type, uint64_t handle, const char* name); + +#ifndef VMA_DEBUG_MARGIN + #define VMA_DEBUG_MARGIN 0 +#endif + +enum CONFIG_TYPE { + CONFIG_TYPE_MINIMUM, + CONFIG_TYPE_SMALL, + CONFIG_TYPE_AVERAGE, + CONFIG_TYPE_LARGE, + CONFIG_TYPE_MAXIMUM, + CONFIG_TYPE_COUNT +}; + +static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_SMALL; +//static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_LARGE; + +enum class FREE_ORDER { FORWARD, BACKWARD, RANDOM, COUNT }; + +static const char* FREE_ORDER_NAMES[] = { + "FORWARD", + "BACKWARD", + "RANDOM", +}; + +// Copy of internal VmaAlgorithmToStr. +static const char* AlgorithmToStr(uint32_t algorithm) +{ + switch(algorithm) + { + case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT: + return "Linear"; + case VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT: + return "Buddy"; + case 0: + return "Default"; + default: + assert(0); + return ""; + } +} + +struct AllocationSize +{ + uint32_t Probability; + VkDeviceSize BufferSizeMin, BufferSizeMax; + uint32_t ImageSizeMin, ImageSizeMax; +}; + +struct Config +{ + uint32_t RandSeed; + VkDeviceSize BeginBytesToAllocate; + uint32_t AdditionalOperationCount; + VkDeviceSize MaxBytesToAllocate; + uint32_t MemUsageProbability[4]; // For VMA_MEMORY_USAGE_* + std::vector<AllocationSize> AllocationSizes; + uint32_t ThreadCount; + uint32_t ThreadsUsingCommonAllocationsProbabilityPercent; + FREE_ORDER FreeOrder; + VmaAllocationCreateFlags AllocationStrategy; // For VMA_ALLOCATION_CREATE_STRATEGY_* +}; + +struct Result +{ + duration TotalTime; + duration AllocationTimeMin, AllocationTimeAvg, AllocationTimeMax; + duration DeallocationTimeMin, DeallocationTimeAvg, DeallocationTimeMax; + VkDeviceSize TotalMemoryAllocated; + VkDeviceSize FreeRangeSizeAvg, FreeRangeSizeMax; +}; + +void TestDefragmentationSimple(); +void TestDefragmentationFull(); + +struct PoolTestConfig +{ + uint32_t RandSeed; + uint32_t ThreadCount; + VkDeviceSize PoolSize; + uint32_t FrameCount; + uint32_t TotalItemCount; + // Range for number of items used in each frame. + uint32_t UsedItemCountMin, UsedItemCountMax; + // Percent of items to make unused, and possibly make some others used in each frame. + uint32_t ItemsToMakeUnusedPercent; + std::vector<AllocationSize> AllocationSizes; + + VkDeviceSize CalcAvgResourceSize() const + { + uint32_t probabilitySum = 0; + VkDeviceSize sizeSum = 0; + for(size_t i = 0; i < AllocationSizes.size(); ++i) + { + const AllocationSize& allocSize = AllocationSizes[i]; + if(allocSize.BufferSizeMax > 0) + sizeSum += (allocSize.BufferSizeMin + allocSize.BufferSizeMax) / 2 * allocSize.Probability; + else + { + const VkDeviceSize avgDimension = (allocSize.ImageSizeMin + allocSize.ImageSizeMax) / 2; + sizeSum += avgDimension * avgDimension * 4 * allocSize.Probability; + } + probabilitySum += allocSize.Probability; + } + return sizeSum / probabilitySum; + } + + bool UsesBuffers() const + { + for(size_t i = 0; i < AllocationSizes.size(); ++i) + if(AllocationSizes[i].BufferSizeMax > 0) + return true; + return false; + } + + bool UsesImages() const + { + for(size_t i = 0; i < AllocationSizes.size(); ++i) + if(AllocationSizes[i].ImageSizeMax > 0) + return true; + return false; + } +}; + +struct PoolTestResult +{ + duration TotalTime; + duration AllocationTimeMin, AllocationTimeAvg, AllocationTimeMax; + duration DeallocationTimeMin, DeallocationTimeAvg, DeallocationTimeMax; + size_t LostAllocationCount, LostAllocationTotalSize; + size_t FailedAllocationCount, FailedAllocationTotalSize; +}; + +static const uint32_t IMAGE_BYTES_PER_PIXEL = 1; + +uint32_t g_FrameIndex = 0; + +struct BufferInfo +{ + VkBuffer Buffer = VK_NULL_HANDLE; + VmaAllocation Allocation = VK_NULL_HANDLE; +}; + +static uint32_t MemoryTypeToHeap(uint32_t memoryTypeIndex) +{ + const VkPhysicalDeviceMemoryProperties* props; + vmaGetMemoryProperties(g_hAllocator, &props); + return props->memoryTypes[memoryTypeIndex].heapIndex; +} + +static uint32_t GetAllocationStrategyCount() +{ + uint32_t strategyCount = 0; + switch(ConfigType) + { + case CONFIG_TYPE_MINIMUM: strategyCount = 1; break; + case CONFIG_TYPE_SMALL: strategyCount = 1; break; + case CONFIG_TYPE_AVERAGE: strategyCount = 2; break; + case CONFIG_TYPE_LARGE: strategyCount = 2; break; + case CONFIG_TYPE_MAXIMUM: strategyCount = 3; break; + default: assert(0); + } + return strategyCount; +} + +static const char* GetAllocationStrategyName(VmaAllocationCreateFlags allocStrategy) +{ + switch(allocStrategy) + { + case VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT: return "BEST_FIT"; break; + case VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT: return "WORST_FIT"; break; + case VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT: return "FIRST_FIT"; break; + case 0: return "Default"; break; + default: assert(0); return ""; + } +} + +static void InitResult(Result& outResult) +{ + outResult.TotalTime = duration::zero(); + outResult.AllocationTimeMin = duration::max(); + outResult.AllocationTimeAvg = duration::zero(); + outResult.AllocationTimeMax = duration::min(); + outResult.DeallocationTimeMin = duration::max(); + outResult.DeallocationTimeAvg = duration::zero(); + outResult.DeallocationTimeMax = duration::min(); + outResult.TotalMemoryAllocated = 0; + outResult.FreeRangeSizeAvg = 0; + outResult.FreeRangeSizeMax = 0; +} + +class TimeRegisterObj +{ +public: + TimeRegisterObj(duration& min, duration& sum, duration& max) : + m_Min(min), + m_Sum(sum), + m_Max(max), + m_TimeBeg(std::chrono::high_resolution_clock::now()) + { + } + + ~TimeRegisterObj() + { + duration d = std::chrono::high_resolution_clock::now() - m_TimeBeg; + m_Sum += d; + if(d < m_Min) m_Min = d; + if(d > m_Max) m_Max = d; + } + +private: + duration& m_Min; + duration& m_Sum; + duration& m_Max; + time_point m_TimeBeg; +}; + +struct PoolTestThreadResult +{ + duration AllocationTimeMin, AllocationTimeSum, AllocationTimeMax; + duration DeallocationTimeMin, DeallocationTimeSum, DeallocationTimeMax; + size_t AllocationCount, DeallocationCount; + size_t LostAllocationCount, LostAllocationTotalSize; + size_t FailedAllocationCount, FailedAllocationTotalSize; +}; + +class AllocationTimeRegisterObj : public TimeRegisterObj +{ +public: + AllocationTimeRegisterObj(Result& result) : + TimeRegisterObj(result.AllocationTimeMin, result.AllocationTimeAvg, result.AllocationTimeMax) + { + } +}; + +class DeallocationTimeRegisterObj : public TimeRegisterObj +{ +public: + DeallocationTimeRegisterObj(Result& result) : + TimeRegisterObj(result.DeallocationTimeMin, result.DeallocationTimeAvg, result.DeallocationTimeMax) + { + } +}; + +class PoolAllocationTimeRegisterObj : public TimeRegisterObj +{ +public: + PoolAllocationTimeRegisterObj(PoolTestThreadResult& result) : + TimeRegisterObj(result.AllocationTimeMin, result.AllocationTimeSum, result.AllocationTimeMax) + { + } +}; + +class PoolDeallocationTimeRegisterObj : public TimeRegisterObj +{ +public: + PoolDeallocationTimeRegisterObj(PoolTestThreadResult& result) : + TimeRegisterObj(result.DeallocationTimeMin, result.DeallocationTimeSum, result.DeallocationTimeMax) + { + } +}; + +static void CurrentTimeToStr(std::string& out) +{ + time_t rawTime; time(&rawTime); + struct tm timeInfo; localtime_s(&timeInfo, &rawTime); + char timeStr[128]; + strftime(timeStr, _countof(timeStr), "%c", &timeInfo); + out = timeStr; +} + +VkResult MainTest(Result& outResult, const Config& config) +{ + assert(config.ThreadCount > 0); + + InitResult(outResult); + + RandomNumberGenerator mainRand{config.RandSeed}; + + time_point timeBeg = std::chrono::high_resolution_clock::now(); + + std::atomic<size_t> allocationCount = 0; + VkResult res = VK_SUCCESS; + + uint32_t memUsageProbabilitySum = + config.MemUsageProbability[0] + config.MemUsageProbability[1] + + config.MemUsageProbability[2] + config.MemUsageProbability[3]; + assert(memUsageProbabilitySum > 0); + + uint32_t allocationSizeProbabilitySum = std::accumulate( + config.AllocationSizes.begin(), + config.AllocationSizes.end(), + 0u, + [](uint32_t sum, const AllocationSize& allocSize) { + return sum + allocSize.Probability; + }); + + struct Allocation + { + VkBuffer Buffer; + VkImage Image; + VmaAllocation Alloc; + }; + + std::vector<Allocation> commonAllocations; + std::mutex commonAllocationsMutex; + + auto Allocate = [&]( + VkDeviceSize bufferSize, + const VkExtent2D imageExtent, + RandomNumberGenerator& localRand, + VkDeviceSize& totalAllocatedBytes, + std::vector<Allocation>& allocations) -> VkResult + { + assert((bufferSize == 0) != (imageExtent.width == 0 && imageExtent.height == 0)); + + uint32_t memUsageIndex = 0; + uint32_t memUsageRand = localRand.Generate() % memUsageProbabilitySum; + while(memUsageRand >= config.MemUsageProbability[memUsageIndex]) + memUsageRand -= config.MemUsageProbability[memUsageIndex++]; + + VmaAllocationCreateInfo memReq = {}; + memReq.usage = (VmaMemoryUsage)(VMA_MEMORY_USAGE_GPU_ONLY + memUsageIndex); + memReq.flags |= config.AllocationStrategy; + + Allocation allocation = {}; + VmaAllocationInfo allocationInfo; + + // Buffer + if(bufferSize > 0) + { + assert(imageExtent.width == 0); + VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferInfo.size = bufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + { + AllocationTimeRegisterObj timeRegisterObj{outResult}; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &memReq, &allocation.Buffer, &allocation.Alloc, &allocationInfo); + } + } + // Image + else + { + VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = imageExtent.width; + imageInfo.extent.height = imageExtent.height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageInfo.tiling = memReq.usage == VMA_MEMORY_USAGE_GPU_ONLY ? + VK_IMAGE_TILING_OPTIMAL : + VK_IMAGE_TILING_LINEAR; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + switch(memReq.usage) + { + case VMA_MEMORY_USAGE_GPU_ONLY: + switch(localRand.Generate() % 3) + { + case 0: + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + break; + case 1: + imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + break; + case 2: + imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + break; + } + break; + case VMA_MEMORY_USAGE_CPU_ONLY: + case VMA_MEMORY_USAGE_CPU_TO_GPU: + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + break; + case VMA_MEMORY_USAGE_GPU_TO_CPU: + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; + break; + } + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.flags = 0; + + { + AllocationTimeRegisterObj timeRegisterObj{outResult}; + res = vmaCreateImage(g_hAllocator, &imageInfo, &memReq, &allocation.Image, &allocation.Alloc, &allocationInfo); + } + } + + if(res == VK_SUCCESS) + { + ++allocationCount; + totalAllocatedBytes += allocationInfo.size; + bool useCommonAllocations = localRand.Generate() % 100 < config.ThreadsUsingCommonAllocationsProbabilityPercent; + if(useCommonAllocations) + { + std::unique_lock<std::mutex> lock(commonAllocationsMutex); + commonAllocations.push_back(allocation); + } + else + allocations.push_back(allocation); + } + else + { + TEST(0); + } + return res; + }; + + auto GetNextAllocationSize = [&]( + VkDeviceSize& outBufSize, + VkExtent2D& outImageSize, + RandomNumberGenerator& localRand) + { + outBufSize = 0; + outImageSize = {0, 0}; + + uint32_t allocSizeIndex = 0; + uint32_t r = localRand.Generate() % allocationSizeProbabilitySum; + while(r >= config.AllocationSizes[allocSizeIndex].Probability) + r -= config.AllocationSizes[allocSizeIndex++].Probability; + + const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex]; + if(allocSize.BufferSizeMax > 0) + { + assert(allocSize.ImageSizeMax == 0); + if(allocSize.BufferSizeMax == allocSize.BufferSizeMin) + outBufSize = allocSize.BufferSizeMin; + else + { + outBufSize = allocSize.BufferSizeMin + localRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin); + outBufSize = outBufSize / 16 * 16; + } + } + else + { + if(allocSize.ImageSizeMax == allocSize.ImageSizeMin) + outImageSize.width = outImageSize.height = allocSize.ImageSizeMax; + else + { + outImageSize.width = allocSize.ImageSizeMin + localRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); + outImageSize.height = allocSize.ImageSizeMin + localRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); + } + } + }; + + std::atomic<uint32_t> numThreadsReachedMaxAllocations = 0; + HANDLE threadsFinishEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + auto ThreadProc = [&](uint32_t randSeed) -> void + { + RandomNumberGenerator threadRand(randSeed); + VkDeviceSize threadTotalAllocatedBytes = 0; + std::vector<Allocation> threadAllocations; + VkDeviceSize threadBeginBytesToAllocate = config.BeginBytesToAllocate / config.ThreadCount; + VkDeviceSize threadMaxBytesToAllocate = config.MaxBytesToAllocate / config.ThreadCount; + uint32_t threadAdditionalOperationCount = config.AdditionalOperationCount / config.ThreadCount; + + // BEGIN ALLOCATIONS + for(;;) + { + VkDeviceSize bufferSize = 0; + VkExtent2D imageExtent = {}; + GetNextAllocationSize(bufferSize, imageExtent, threadRand); + if(threadTotalAllocatedBytes + bufferSize + imageExtent.width * imageExtent.height * IMAGE_BYTES_PER_PIXEL < + threadBeginBytesToAllocate) + { + if(Allocate(bufferSize, imageExtent, threadRand, threadTotalAllocatedBytes, threadAllocations) != VK_SUCCESS) + break; + } + else + break; + } + + // ADDITIONAL ALLOCATIONS AND FREES + for(size_t i = 0; i < threadAdditionalOperationCount; ++i) + { + VkDeviceSize bufferSize = 0; + VkExtent2D imageExtent = {}; + GetNextAllocationSize(bufferSize, imageExtent, threadRand); + + // true = allocate, false = free + bool allocate = threadRand.Generate() % 2 != 0; + + if(allocate) + { + if(threadTotalAllocatedBytes + + bufferSize + + imageExtent.width * imageExtent.height * IMAGE_BYTES_PER_PIXEL < + threadMaxBytesToAllocate) + { + if(Allocate(bufferSize, imageExtent, threadRand, threadTotalAllocatedBytes, threadAllocations) != VK_SUCCESS) + break; + } + } + else + { + bool useCommonAllocations = threadRand.Generate() % 100 < config.ThreadsUsingCommonAllocationsProbabilityPercent; + if(useCommonAllocations) + { + std::unique_lock<std::mutex> lock(commonAllocationsMutex); + if(!commonAllocations.empty()) + { + size_t indexToFree = threadRand.Generate() % commonAllocations.size(); + VmaAllocationInfo allocationInfo; + vmaGetAllocationInfo(g_hAllocator, commonAllocations[indexToFree].Alloc, &allocationInfo); + if(threadTotalAllocatedBytes >= allocationInfo.size) + { + DeallocationTimeRegisterObj timeRegisterObj{outResult}; + if(commonAllocations[indexToFree].Buffer != VK_NULL_HANDLE) + vmaDestroyBuffer(g_hAllocator, commonAllocations[indexToFree].Buffer, commonAllocations[indexToFree].Alloc); + else + vmaDestroyImage(g_hAllocator, commonAllocations[indexToFree].Image, commonAllocations[indexToFree].Alloc); + threadTotalAllocatedBytes -= allocationInfo.size; + commonAllocations.erase(commonAllocations.begin() + indexToFree); + } + } + } + else + { + if(!threadAllocations.empty()) + { + size_t indexToFree = threadRand.Generate() % threadAllocations.size(); + VmaAllocationInfo allocationInfo; + vmaGetAllocationInfo(g_hAllocator, threadAllocations[indexToFree].Alloc, &allocationInfo); + if(threadTotalAllocatedBytes >= allocationInfo.size) + { + DeallocationTimeRegisterObj timeRegisterObj{outResult}; + if(threadAllocations[indexToFree].Buffer != VK_NULL_HANDLE) + vmaDestroyBuffer(g_hAllocator, threadAllocations[indexToFree].Buffer, threadAllocations[indexToFree].Alloc); + else + vmaDestroyImage(g_hAllocator, threadAllocations[indexToFree].Image, threadAllocations[indexToFree].Alloc); + threadTotalAllocatedBytes -= allocationInfo.size; + threadAllocations.erase(threadAllocations.begin() + indexToFree); + } + } + } + } + } + + ++numThreadsReachedMaxAllocations; + + WaitForSingleObject(threadsFinishEvent, INFINITE); + + // DEALLOCATION + while(!threadAllocations.empty()) + { + size_t indexToFree = 0; + switch(config.FreeOrder) + { + case FREE_ORDER::FORWARD: + indexToFree = 0; + break; + case FREE_ORDER::BACKWARD: + indexToFree = threadAllocations.size() - 1; + break; + case FREE_ORDER::RANDOM: + indexToFree = mainRand.Generate() % threadAllocations.size(); + break; + } + + { + DeallocationTimeRegisterObj timeRegisterObj{outResult}; + if(threadAllocations[indexToFree].Buffer != VK_NULL_HANDLE) + vmaDestroyBuffer(g_hAllocator, threadAllocations[indexToFree].Buffer, threadAllocations[indexToFree].Alloc); + else + vmaDestroyImage(g_hAllocator, threadAllocations[indexToFree].Image, threadAllocations[indexToFree].Alloc); + } + threadAllocations.erase(threadAllocations.begin() + indexToFree); + } + }; + + uint32_t threadRandSeed = mainRand.Generate(); + std::vector<std::thread> bkgThreads; + for(size_t i = 0; i < config.ThreadCount; ++i) + { + bkgThreads.emplace_back(std::bind(ThreadProc, threadRandSeed + (uint32_t)i)); + } + + // Wait for threads reached max allocations + while(numThreadsReachedMaxAllocations < config.ThreadCount) + Sleep(0); + + // CALCULATE MEMORY STATISTICS ON FINAL USAGE + VmaStats vmaStats = {}; + vmaCalculateStats(g_hAllocator, &vmaStats); + outResult.TotalMemoryAllocated = vmaStats.total.usedBytes + vmaStats.total.unusedBytes; + outResult.FreeRangeSizeMax = vmaStats.total.unusedRangeSizeMax; + outResult.FreeRangeSizeAvg = vmaStats.total.unusedRangeSizeAvg; + + // Signal threads to deallocate + SetEvent(threadsFinishEvent); + + // Wait for threads finished + for(size_t i = 0; i < bkgThreads.size(); ++i) + bkgThreads[i].join(); + bkgThreads.clear(); + + CloseHandle(threadsFinishEvent); + + // Deallocate remaining common resources + while(!commonAllocations.empty()) + { + size_t indexToFree = 0; + switch(config.FreeOrder) + { + case FREE_ORDER::FORWARD: + indexToFree = 0; + break; + case FREE_ORDER::BACKWARD: + indexToFree = commonAllocations.size() - 1; + break; + case FREE_ORDER::RANDOM: + indexToFree = mainRand.Generate() % commonAllocations.size(); + break; + } + + { + DeallocationTimeRegisterObj timeRegisterObj{outResult}; + if(commonAllocations[indexToFree].Buffer != VK_NULL_HANDLE) + vmaDestroyBuffer(g_hAllocator, commonAllocations[indexToFree].Buffer, commonAllocations[indexToFree].Alloc); + else + vmaDestroyImage(g_hAllocator, commonAllocations[indexToFree].Image, commonAllocations[indexToFree].Alloc); + } + commonAllocations.erase(commonAllocations.begin() + indexToFree); + } + + if(allocationCount) + { + outResult.AllocationTimeAvg /= allocationCount; + outResult.DeallocationTimeAvg /= allocationCount; + } + + outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg; + + return res; +} + +void SaveAllocatorStatsToFile(const wchar_t* filePath) +{ + wprintf(L"Saving JSON dump to file \"%s\"\n", filePath); + char* stats; + vmaBuildStatsString(g_hAllocator, &stats, VK_TRUE); + SaveFile(filePath, stats, strlen(stats)); + vmaFreeStatsString(g_hAllocator, stats); +} + +struct AllocInfo +{ + VmaAllocation m_Allocation = VK_NULL_HANDLE; + VkBuffer m_Buffer = VK_NULL_HANDLE; + VkImage m_Image = VK_NULL_HANDLE; + VkImageLayout m_ImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + uint32_t m_StartValue = 0; + union + { + VkBufferCreateInfo m_BufferInfo; + VkImageCreateInfo m_ImageInfo; + }; + + // After defragmentation. + VkBuffer m_NewBuffer = VK_NULL_HANDLE; + VkImage m_NewImage = VK_NULL_HANDLE; + + void CreateBuffer( + const VkBufferCreateInfo& bufCreateInfo, + const VmaAllocationCreateInfo& allocCreateInfo); + void CreateImage( + const VkImageCreateInfo& imageCreateInfo, + const VmaAllocationCreateInfo& allocCreateInfo, + VkImageLayout layout); + void Destroy(); +}; + +void AllocInfo::CreateBuffer( + const VkBufferCreateInfo& bufCreateInfo, + const VmaAllocationCreateInfo& allocCreateInfo) +{ + m_BufferInfo = bufCreateInfo; + VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &m_Buffer, &m_Allocation, nullptr); + TEST(res == VK_SUCCESS); +} +void AllocInfo::CreateImage( + const VkImageCreateInfo& imageCreateInfo, + const VmaAllocationCreateInfo& allocCreateInfo, + VkImageLayout layout) +{ + m_ImageInfo = imageCreateInfo; + m_ImageLayout = layout; + VkResult res = vmaCreateImage(g_hAllocator, &imageCreateInfo, &allocCreateInfo, &m_Image, &m_Allocation, nullptr); + TEST(res == VK_SUCCESS); +} + +void AllocInfo::Destroy() +{ + if(m_Image) + { + assert(!m_Buffer); + vkDestroyImage(g_hDevice, m_Image, g_Allocs); + m_Image = VK_NULL_HANDLE; + } + if(m_Buffer) + { + assert(!m_Image); + vkDestroyBuffer(g_hDevice, m_Buffer, g_Allocs); + m_Buffer = VK_NULL_HANDLE; + } + if(m_Allocation) + { + vmaFreeMemory(g_hAllocator, m_Allocation); + m_Allocation = VK_NULL_HANDLE; + } +} + +class StagingBufferCollection +{ +public: + StagingBufferCollection() { } + ~StagingBufferCollection(); + // Returns false if maximum total size of buffers would be exceeded. + bool AcquireBuffer(VkDeviceSize size, VkBuffer& outBuffer, void*& outMappedPtr); + void ReleaseAllBuffers(); + +private: + static const VkDeviceSize MAX_TOTAL_SIZE = 256ull * 1024 * 1024; + struct BufInfo + { + VmaAllocation Allocation = VK_NULL_HANDLE; + VkBuffer Buffer = VK_NULL_HANDLE; + VkDeviceSize Size = VK_WHOLE_SIZE; + void* MappedPtr = nullptr; + bool Used = false; + }; + std::vector<BufInfo> m_Bufs; + // Including both used and unused. + VkDeviceSize m_TotalSize = 0; +}; + +StagingBufferCollection::~StagingBufferCollection() +{ + for(size_t i = m_Bufs.size(); i--; ) + { + vmaDestroyBuffer(g_hAllocator, m_Bufs[i].Buffer, m_Bufs[i].Allocation); + } +} + +bool StagingBufferCollection::AcquireBuffer(VkDeviceSize size, VkBuffer& outBuffer, void*& outMappedPtr) +{ + assert(size <= MAX_TOTAL_SIZE); + + // Try to find existing unused buffer with best size. + size_t bestIndex = SIZE_MAX; + for(size_t i = 0, count = m_Bufs.size(); i < count; ++i) + { + BufInfo& currBufInfo = m_Bufs[i]; + if(!currBufInfo.Used && currBufInfo.Size >= size && + (bestIndex == SIZE_MAX || currBufInfo.Size < m_Bufs[bestIndex].Size)) + { + bestIndex = i; + } + } + + if(bestIndex != SIZE_MAX) + { + m_Bufs[bestIndex].Used = true; + outBuffer = m_Bufs[bestIndex].Buffer; + outMappedPtr = m_Bufs[bestIndex].MappedPtr; + return true; + } + + // Allocate new buffer with requested size. + if(m_TotalSize + size <= MAX_TOTAL_SIZE) + { + BufInfo bufInfo; + bufInfo.Size = size; + bufInfo.Used = true; + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = size; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VmaAllocationInfo allocInfo; + VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo); + bufInfo.MappedPtr = allocInfo.pMappedData; + TEST(res == VK_SUCCESS && bufInfo.MappedPtr); + + outBuffer = bufInfo.Buffer; + outMappedPtr = bufInfo.MappedPtr; + + m_Bufs.push_back(std::move(bufInfo)); + + m_TotalSize += size; + + return true; + } + + // There are some unused but smaller buffers: Free them and try again. + bool hasUnused = false; + for(size_t i = 0, count = m_Bufs.size(); i < count; ++i) + { + if(!m_Bufs[i].Used) + { + hasUnused = true; + break; + } + } + if(hasUnused) + { + for(size_t i = m_Bufs.size(); i--; ) + { + if(!m_Bufs[i].Used) + { + m_TotalSize -= m_Bufs[i].Size; + vmaDestroyBuffer(g_hAllocator, m_Bufs[i].Buffer, m_Bufs[i].Allocation); + m_Bufs.erase(m_Bufs.begin() + i); + } + } + + return AcquireBuffer(size, outBuffer, outMappedPtr); + } + + return false; +} + +void StagingBufferCollection::ReleaseAllBuffers() +{ + for(size_t i = 0, count = m_Bufs.size(); i < count; ++i) + { + m_Bufs[i].Used = false; + } +} + +static void UploadGpuData(const AllocInfo* allocInfo, size_t allocInfoCount) +{ + StagingBufferCollection stagingBufs; + + bool cmdBufferStarted = false; + for(size_t allocInfoIndex = 0; allocInfoIndex < allocInfoCount; ++allocInfoIndex) + { + const AllocInfo& currAllocInfo = allocInfo[allocInfoIndex]; + if(currAllocInfo.m_Buffer) + { + const VkDeviceSize size = currAllocInfo.m_BufferInfo.size; + + VkBuffer stagingBuf = VK_NULL_HANDLE; + void* stagingBufMappedPtr = nullptr; + if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr)) + { + TEST(cmdBufferStarted); + EndSingleTimeCommands(); + stagingBufs.ReleaseAllBuffers(); + cmdBufferStarted = false; + + bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr); + TEST(ok); + } + + // Fill staging buffer. + { + assert(size % sizeof(uint32_t) == 0); + uint32_t* stagingValPtr = (uint32_t*)stagingBufMappedPtr; + uint32_t val = currAllocInfo.m_StartValue; + for(size_t i = 0; i < size / sizeof(uint32_t); ++i) + { + *stagingValPtr = val; + ++stagingValPtr; + ++val; + } + } + + // Issue copy command from staging buffer to destination buffer. + if(!cmdBufferStarted) + { + cmdBufferStarted = true; + BeginSingleTimeCommands(); + } + + VkBufferCopy copy = {}; + copy.srcOffset = 0; + copy.dstOffset = 0; + copy.size = size; + vkCmdCopyBuffer(g_hTemporaryCommandBuffer, stagingBuf, currAllocInfo.m_Buffer, 1, ©); + } + else + { + TEST(currAllocInfo.m_ImageInfo.format == VK_FORMAT_R8G8B8A8_UNORM && "Only RGBA8 images are currently supported."); + TEST(currAllocInfo.m_ImageInfo.mipLevels == 1 && "Only single mip images are currently supported."); + + const VkDeviceSize size = (VkDeviceSize)currAllocInfo.m_ImageInfo.extent.width * currAllocInfo.m_ImageInfo.extent.height * sizeof(uint32_t); + + VkBuffer stagingBuf = VK_NULL_HANDLE; + void* stagingBufMappedPtr = nullptr; + if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr)) + { + TEST(cmdBufferStarted); + EndSingleTimeCommands(); + stagingBufs.ReleaseAllBuffers(); + cmdBufferStarted = false; + + bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr); + TEST(ok); + } + + // Fill staging buffer. + { + assert(size % sizeof(uint32_t) == 0); + uint32_t *stagingValPtr = (uint32_t *)stagingBufMappedPtr; + uint32_t val = currAllocInfo.m_StartValue; + for(size_t i = 0; i < size / sizeof(uint32_t); ++i) + { + *stagingValPtr = val; + ++stagingValPtr; + ++val; + } + } + + // Issue copy command from staging buffer to destination buffer. + if(!cmdBufferStarted) + { + cmdBufferStarted = true; + BeginSingleTimeCommands(); + } + + + // Transfer to transfer dst layout + VkImageSubresourceRange subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, VK_REMAINING_MIP_LEVELS, + 0, VK_REMAINING_ARRAY_LAYERS + }; + + VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = 0; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = currAllocInfo.m_Image; + barrier.subresourceRange = subresourceRange; + + vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + // Copy image date + VkBufferImageCopy copy = {}; + copy.bufferOffset = 0; + copy.bufferRowLength = 0; + copy.bufferImageHeight = 0; + copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy.imageSubresource.layerCount = 1; + copy.imageExtent = currAllocInfo.m_ImageInfo.extent; + + vkCmdCopyBufferToImage(g_hTemporaryCommandBuffer, stagingBuf, currAllocInfo.m_Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); + + // Transfer to desired layout + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = currAllocInfo.m_ImageLayout; + + vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + } + } + + if(cmdBufferStarted) + { + EndSingleTimeCommands(); + stagingBufs.ReleaseAllBuffers(); + } +} + +static void ValidateGpuData(const AllocInfo* allocInfo, size_t allocInfoCount) +{ + StagingBufferCollection stagingBufs; + + bool cmdBufferStarted = false; + size_t validateAllocIndexOffset = 0; + std::vector<void*> validateStagingBuffers; + for(size_t allocInfoIndex = 0; allocInfoIndex < allocInfoCount; ++allocInfoIndex) + { + const AllocInfo& currAllocInfo = allocInfo[allocInfoIndex]; + if(currAllocInfo.m_Buffer) + { + const VkDeviceSize size = currAllocInfo.m_BufferInfo.size; + + VkBuffer stagingBuf = VK_NULL_HANDLE; + void* stagingBufMappedPtr = nullptr; + if(!stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr)) + { + TEST(cmdBufferStarted); + EndSingleTimeCommands(); + cmdBufferStarted = false; + + for(size_t validateIndex = 0; + validateIndex < validateStagingBuffers.size(); + ++validateIndex) + { + const size_t validateAllocIndex = validateIndex + validateAllocIndexOffset; + const VkDeviceSize validateSize = allocInfo[validateAllocIndex].m_BufferInfo.size; + TEST(validateSize % sizeof(uint32_t) == 0); + const uint32_t* stagingValPtr = (const uint32_t*)validateStagingBuffers[validateIndex]; + uint32_t val = allocInfo[validateAllocIndex].m_StartValue; + bool valid = true; + for(size_t i = 0; i < validateSize / sizeof(uint32_t); ++i) + { + if(*stagingValPtr != val) + { + valid = false; + break; + } + ++stagingValPtr; + ++val; + } + TEST(valid); + } + + stagingBufs.ReleaseAllBuffers(); + + validateAllocIndexOffset = allocInfoIndex; + validateStagingBuffers.clear(); + + bool ok = stagingBufs.AcquireBuffer(size, stagingBuf, stagingBufMappedPtr); + TEST(ok); + } + + // Issue copy command from staging buffer to destination buffer. + if(!cmdBufferStarted) + { + cmdBufferStarted = true; + BeginSingleTimeCommands(); + } + + VkBufferCopy copy = {}; + copy.srcOffset = 0; + copy.dstOffset = 0; + copy.size = size; + vkCmdCopyBuffer(g_hTemporaryCommandBuffer, currAllocInfo.m_Buffer, stagingBuf, 1, ©); + + // Sava mapped pointer for later validation. + validateStagingBuffers.push_back(stagingBufMappedPtr); + } + else + { + TEST(0 && "Images not currently supported."); + } + } + + if(cmdBufferStarted) + { + EndSingleTimeCommands(); + + for(size_t validateIndex = 0; + validateIndex < validateStagingBuffers.size(); + ++validateIndex) + { + const size_t validateAllocIndex = validateIndex + validateAllocIndexOffset; + const VkDeviceSize validateSize = allocInfo[validateAllocIndex].m_BufferInfo.size; + TEST(validateSize % sizeof(uint32_t) == 0); + const uint32_t* stagingValPtr = (const uint32_t*)validateStagingBuffers[validateIndex]; + uint32_t val = allocInfo[validateAllocIndex].m_StartValue; + bool valid = true; + for(size_t i = 0; i < validateSize / sizeof(uint32_t); ++i) + { + if(*stagingValPtr != val) + { + valid = false; + break; + } + ++stagingValPtr; + ++val; + } + TEST(valid); + } + + stagingBufs.ReleaseAllBuffers(); + } +} + +static void GetMemReq(VmaAllocationCreateInfo& outMemReq) +{ + outMemReq = {}; + outMemReq.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; + //outMemReq.flags = VMA_ALLOCATION_CREATE_PERSISTENT_MAP_BIT; +} + +static void CreateBuffer( + VmaPool pool, + const VkBufferCreateInfo& bufCreateInfo, + bool persistentlyMapped, + AllocInfo& outAllocInfo) +{ + outAllocInfo = {}; + outAllocInfo.m_BufferInfo = bufCreateInfo; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = pool; + if(persistentlyMapped) + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VmaAllocationInfo vmaAllocInfo = {}; + ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &outAllocInfo.m_Buffer, &outAllocInfo.m_Allocation, &vmaAllocInfo) ); + + // Setup StartValue and fill. + { + outAllocInfo.m_StartValue = (uint32_t)rand(); + uint32_t* data = (uint32_t*)vmaAllocInfo.pMappedData; + TEST((data != nullptr) == persistentlyMapped); + if(!persistentlyMapped) + { + ERR_GUARD_VULKAN( vmaMapMemory(g_hAllocator, outAllocInfo.m_Allocation, (void**)&data) ); + } + + uint32_t value = outAllocInfo.m_StartValue; + TEST(bufCreateInfo.size % 4 == 0); + for(size_t i = 0; i < bufCreateInfo.size / sizeof(uint32_t); ++i) + data[i] = value++; + + if(!persistentlyMapped) + vmaUnmapMemory(g_hAllocator, outAllocInfo.m_Allocation); + } +} + +static void CreateAllocation(AllocInfo& outAllocation) +{ + outAllocation.m_Allocation = nullptr; + outAllocation.m_Buffer = nullptr; + outAllocation.m_Image = nullptr; + outAllocation.m_StartValue = (uint32_t)rand(); + + VmaAllocationCreateInfo vmaMemReq; + GetMemReq(vmaMemReq); + + VmaAllocationInfo allocInfo; + + const bool isBuffer = true;//(rand() & 0x1) != 0; + const bool isLarge = (rand() % 16) == 0; + if(isBuffer) + { + const uint32_t bufferSize = isLarge ? + (rand() % 10 + 1) * (1024 * 1024) : // 1 MB ... 10 MB + (rand() % 1024 + 1) * 1024; // 1 KB ... 1 MB + + VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferInfo.size = bufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VkResult res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &vmaMemReq, &outAllocation.m_Buffer, &outAllocation.m_Allocation, &allocInfo); + outAllocation.m_BufferInfo = bufferInfo; + TEST(res == VK_SUCCESS); + } + else + { + const uint32_t imageSizeX = isLarge ? + 1024 + rand() % (4096 - 1024) : // 1024 ... 4096 + rand() % 1024 + 1; // 1 ... 1024 + const uint32_t imageSizeY = isLarge ? + 1024 + rand() % (4096 - 1024) : // 1024 ... 4096 + rand() % 1024 + 1; // 1 ... 1024 + + VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageInfo.extent.width = imageSizeX; + imageInfo.extent.height = imageSizeY; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + + VkResult res = vmaCreateImage(g_hAllocator, &imageInfo, &vmaMemReq, &outAllocation.m_Image, &outAllocation.m_Allocation, &allocInfo); + outAllocation.m_ImageInfo = imageInfo; + TEST(res == VK_SUCCESS); + } + + uint32_t* data = (uint32_t*)allocInfo.pMappedData; + if(allocInfo.pMappedData == nullptr) + { + VkResult res = vmaMapMemory(g_hAllocator, outAllocation.m_Allocation, (void**)&data); + TEST(res == VK_SUCCESS); + } + + uint32_t value = outAllocation.m_StartValue; + TEST(allocInfo.size % 4 == 0); + for(size_t i = 0; i < allocInfo.size / sizeof(uint32_t); ++i) + data[i] = value++; + + if(allocInfo.pMappedData == nullptr) + vmaUnmapMemory(g_hAllocator, outAllocation.m_Allocation); +} + +static void DestroyAllocation(const AllocInfo& allocation) +{ + if(allocation.m_Buffer) + vmaDestroyBuffer(g_hAllocator, allocation.m_Buffer, allocation.m_Allocation); + else + vmaDestroyImage(g_hAllocator, allocation.m_Image, allocation.m_Allocation); +} + +static void DestroyAllAllocations(std::vector<AllocInfo>& allocations) +{ + for(size_t i = allocations.size(); i--; ) + DestroyAllocation(allocations[i]); + allocations.clear(); +} + +static void ValidateAllocationData(const AllocInfo& allocation) +{ + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, allocation.m_Allocation, &allocInfo); + + uint32_t* data = (uint32_t*)allocInfo.pMappedData; + if(allocInfo.pMappedData == nullptr) + { + VkResult res = vmaMapMemory(g_hAllocator, allocation.m_Allocation, (void**)&data); + TEST(res == VK_SUCCESS); + } + + uint32_t value = allocation.m_StartValue; + bool ok = true; + size_t i; + TEST(allocInfo.size % 4 == 0); + for(i = 0; i < allocInfo.size / sizeof(uint32_t); ++i) + { + if(data[i] != value++) + { + ok = false; + break; + } + } + TEST(ok); + + if(allocInfo.pMappedData == nullptr) + vmaUnmapMemory(g_hAllocator, allocation.m_Allocation); +} + +static void RecreateAllocationResource(AllocInfo& allocation) +{ + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, allocation.m_Allocation, &allocInfo); + + if(allocation.m_Buffer) + { + vkDestroyBuffer(g_hDevice, allocation.m_Buffer, g_Allocs); + + VkResult res = vkCreateBuffer(g_hDevice, &allocation.m_BufferInfo, g_Allocs, &allocation.m_Buffer); + TEST(res == VK_SUCCESS); + + // Just to silence validation layer warnings. + VkMemoryRequirements vkMemReq; + vkGetBufferMemoryRequirements(g_hDevice, allocation.m_Buffer, &vkMemReq); + TEST(vkMemReq.size >= allocation.m_BufferInfo.size); + + res = vmaBindBufferMemory(g_hAllocator, allocation.m_Allocation, allocation.m_Buffer); + TEST(res == VK_SUCCESS); + } + else + { + vkDestroyImage(g_hDevice, allocation.m_Image, g_Allocs); + + VkResult res = vkCreateImage(g_hDevice, &allocation.m_ImageInfo, g_Allocs, &allocation.m_Image); + TEST(res == VK_SUCCESS); + + // Just to silence validation layer warnings. + VkMemoryRequirements vkMemReq; + vkGetImageMemoryRequirements(g_hDevice, allocation.m_Image, &vkMemReq); + + res = vmaBindImageMemory(g_hAllocator, allocation.m_Allocation, allocation.m_Image); + TEST(res == VK_SUCCESS); + } +} + +static void Defragment(AllocInfo* allocs, size_t allocCount, + const VmaDefragmentationInfo* defragmentationInfo = nullptr, + VmaDefragmentationStats* defragmentationStats = nullptr) +{ + std::vector<VmaAllocation> vmaAllocs(allocCount); + for(size_t i = 0; i < allocCount; ++i) + vmaAllocs[i] = allocs[i].m_Allocation; + + std::vector<VkBool32> allocChanged(allocCount); + + ERR_GUARD_VULKAN( vmaDefragment(g_hAllocator, vmaAllocs.data(), allocCount, allocChanged.data(), + defragmentationInfo, defragmentationStats) ); + + for(size_t i = 0; i < allocCount; ++i) + { + if(allocChanged[i]) + { + RecreateAllocationResource(allocs[i]); + } + } +} + +static void ValidateAllocationsData(const AllocInfo* allocs, size_t allocCount) +{ + std::for_each(allocs, allocs + allocCount, [](const AllocInfo& allocInfo) { + ValidateAllocationData(allocInfo); + }); +} + +void TestDefragmentationSimple() +{ + wprintf(L"Test defragmentation simple\n"); + + RandomNumberGenerator rand(667); + + const VkDeviceSize BUF_SIZE = 0x10000; + const VkDeviceSize BLOCK_SIZE = BUF_SIZE * 8; + + const VkDeviceSize MIN_BUF_SIZE = 32; + const VkDeviceSize MAX_BUF_SIZE = BUF_SIZE * 4; + auto RandomBufSize = [&]() -> VkDeviceSize { + return align_up<VkDeviceSize>(rand.Generate() % (MAX_BUF_SIZE - MIN_BUF_SIZE + 1) + MIN_BUF_SIZE, 32); + }; + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = BUF_SIZE; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo exampleAllocCreateInfo = {}; + exampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + + uint32_t memTypeIndex = UINT32_MAX; + vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &exampleAllocCreateInfo, &memTypeIndex); + + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.blockSize = BLOCK_SIZE; + poolCreateInfo.memoryTypeIndex = memTypeIndex; + + VmaPool pool; + ERR_GUARD_VULKAN( vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) ); + + // Defragmentation of empty pool. + { + VmaDefragmentationInfo2 defragInfo = {}; + defragInfo.maxCpuBytesToMove = VK_WHOLE_SIZE; + defragInfo.maxCpuAllocationsToMove = UINT32_MAX; + defragInfo.poolCount = 1; + defragInfo.pPools = &pool; + + VmaDefragmentationStats defragStats = {}; + VmaDefragmentationContext defragCtx = nullptr; + VkResult res = vmaDefragmentationBegin(g_hAllocator, &defragInfo, &defragStats, &defragCtx); + TEST(res >= VK_SUCCESS); + vmaDefragmentationEnd(g_hAllocator, defragCtx); + TEST(defragStats.allocationsMoved == 0 && defragStats.bytesFreed == 0 && + defragStats.bytesMoved == 0 && defragStats.deviceMemoryBlocksFreed == 0); + } + + std::vector<AllocInfo> allocations; + + // persistentlyMappedOption = 0 - not persistently mapped. + // persistentlyMappedOption = 1 - persistently mapped. + for(uint32_t persistentlyMappedOption = 0; persistentlyMappedOption < 2; ++persistentlyMappedOption) + { + wprintf(L" Persistently mapped option = %u\n", persistentlyMappedOption); + const bool persistentlyMapped = persistentlyMappedOption != 0; + + // # Test 1 + // Buffers of fixed size. + // Fill 2 blocks. Remove odd buffers. Defragment everything. + // Expected result: at least 1 block freed. + { + for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i) + { + AllocInfo allocInfo; + CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo); + allocations.push_back(allocInfo); + } + + for(size_t i = 1; i < allocations.size(); ++i) + { + DestroyAllocation(allocations[i]); + allocations.erase(allocations.begin() + i); + } + + VmaDefragmentationStats defragStats; + Defragment(allocations.data(), allocations.size(), nullptr, &defragStats); + TEST(defragStats.allocationsMoved > 0 && defragStats.bytesMoved > 0); + TEST(defragStats.deviceMemoryBlocksFreed >= 1); + + ValidateAllocationsData(allocations.data(), allocations.size()); + + DestroyAllAllocations(allocations); + } + + // # Test 2 + // Buffers of fixed size. + // Fill 2 blocks. Remove odd buffers. Defragment one buffer at time. + // Expected result: Each of 4 interations makes some progress. + { + for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i) + { + AllocInfo allocInfo; + CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo); + allocations.push_back(allocInfo); + } + + for(size_t i = 1; i < allocations.size(); ++i) + { + DestroyAllocation(allocations[i]); + allocations.erase(allocations.begin() + i); + } + + VmaDefragmentationInfo defragInfo = {}; + defragInfo.maxAllocationsToMove = 1; + defragInfo.maxBytesToMove = BUF_SIZE; + + for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE / 2; ++i) + { + VmaDefragmentationStats defragStats; + Defragment(allocations.data(), allocations.size(), &defragInfo, &defragStats); + TEST(defragStats.allocationsMoved > 0 && defragStats.bytesMoved > 0); + } + + ValidateAllocationsData(allocations.data(), allocations.size()); + + DestroyAllAllocations(allocations); + } + + // # Test 3 + // Buffers of variable size. + // Create a number of buffers. Remove some percent of them. + // Defragment while having some percent of them unmovable. + // Expected result: Just simple validation. + { + for(size_t i = 0; i < 100; ++i) + { + VkBufferCreateInfo localBufCreateInfo = bufCreateInfo; + localBufCreateInfo.size = RandomBufSize(); + + AllocInfo allocInfo; + CreateBuffer(pool, bufCreateInfo, persistentlyMapped, allocInfo); + allocations.push_back(allocInfo); + } + + const uint32_t percentToDelete = 60; + const size_t numberToDelete = allocations.size() * percentToDelete / 100; + for(size_t i = 0; i < numberToDelete; ++i) + { + size_t indexToDelete = rand.Generate() % (uint32_t)allocations.size(); + DestroyAllocation(allocations[indexToDelete]); + allocations.erase(allocations.begin() + indexToDelete); + } + + // Non-movable allocations will be at the beginning of allocations array. + const uint32_t percentNonMovable = 20; + const size_t numberNonMovable = allocations.size() * percentNonMovable / 100; + for(size_t i = 0; i < numberNonMovable; ++i) + { + size_t indexNonMovable = i + rand.Generate() % (uint32_t)(allocations.size() - i); + if(indexNonMovable != i) + std::swap(allocations[i], allocations[indexNonMovable]); + } + + VmaDefragmentationStats defragStats; + Defragment( + allocations.data() + numberNonMovable, + allocations.size() - numberNonMovable, + nullptr, &defragStats); + + ValidateAllocationsData(allocations.data(), allocations.size()); + + DestroyAllAllocations(allocations); + } + } + + /* + Allocation that must be move to an overlapping place using memmove(). + Create 2 buffers, second slightly bigger than the first. Delete first. Then defragment. + */ + if(VMA_DEBUG_MARGIN == 0) // FAST algorithm works only when DEBUG_MARGIN disabled. + { + AllocInfo allocInfo[2]; + + bufCreateInfo.size = BUF_SIZE; + CreateBuffer(pool, bufCreateInfo, false, allocInfo[0]); + const VkDeviceSize biggerBufSize = BUF_SIZE + BUF_SIZE / 256; + bufCreateInfo.size = biggerBufSize; + CreateBuffer(pool, bufCreateInfo, false, allocInfo[1]); + + DestroyAllocation(allocInfo[0]); + + VmaDefragmentationStats defragStats; + Defragment(&allocInfo[1], 1, nullptr, &defragStats); + // If this fails, it means we couldn't do memmove with overlapping regions. + TEST(defragStats.allocationsMoved == 1 && defragStats.bytesMoved > 0); + + ValidateAllocationsData(&allocInfo[1], 1); + DestroyAllocation(allocInfo[1]); + } + + vmaDestroyPool(g_hAllocator, pool); +} + +void TestDefragmentationWholePool() +{ + wprintf(L"Test defragmentation whole pool\n"); + + RandomNumberGenerator rand(668); + + const VkDeviceSize BUF_SIZE = 0x10000; + const VkDeviceSize BLOCK_SIZE = BUF_SIZE * 8; + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = BUF_SIZE; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo exampleAllocCreateInfo = {}; + exampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + + uint32_t memTypeIndex = UINT32_MAX; + vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &exampleAllocCreateInfo, &memTypeIndex); + + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.blockSize = BLOCK_SIZE; + poolCreateInfo.memoryTypeIndex = memTypeIndex; + + VmaDefragmentationStats defragStats[2]; + for(size_t caseIndex = 0; caseIndex < 2; ++caseIndex) + { + VmaPool pool; + ERR_GUARD_VULKAN( vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool) ); + + std::vector<AllocInfo> allocations; + + // Buffers of fixed size. + // Fill 2 blocks. Remove odd buffers. Defragment all of them. + for(size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i) + { + AllocInfo allocInfo; + CreateBuffer(pool, bufCreateInfo, false, allocInfo); + allocations.push_back(allocInfo); + } + + for(size_t i = 1; i < allocations.size(); ++i) + { + DestroyAllocation(allocations[i]); + allocations.erase(allocations.begin() + i); + } + + VmaDefragmentationInfo2 defragInfo = {}; + defragInfo.maxCpuAllocationsToMove = UINT32_MAX; + defragInfo.maxCpuBytesToMove = VK_WHOLE_SIZE; + std::vector<VmaAllocation> allocationsToDefrag; + if(caseIndex == 0) + { + defragInfo.poolCount = 1; + defragInfo.pPools = &pool; + } + else + { + const size_t allocCount = allocations.size(); + allocationsToDefrag.resize(allocCount); + std::transform( + allocations.begin(), allocations.end(), + allocationsToDefrag.begin(), + [](const AllocInfo& allocInfo) { return allocInfo.m_Allocation; }); + defragInfo.allocationCount = (uint32_t)allocCount; + defragInfo.pAllocations = allocationsToDefrag.data(); + } + + VmaDefragmentationContext defragCtx = VK_NULL_HANDLE; + VkResult res = vmaDefragmentationBegin(g_hAllocator, &defragInfo, &defragStats[caseIndex], &defragCtx); + TEST(res >= VK_SUCCESS); + vmaDefragmentationEnd(g_hAllocator, defragCtx); + + TEST(defragStats[caseIndex].allocationsMoved > 0 && defragStats[caseIndex].bytesMoved > 0); + + ValidateAllocationsData(allocations.data(), allocations.size()); + + DestroyAllAllocations(allocations); + + vmaDestroyPool(g_hAllocator, pool); + } + + TEST(defragStats[0].bytesMoved == defragStats[1].bytesMoved); + TEST(defragStats[0].allocationsMoved == defragStats[1].allocationsMoved); + TEST(defragStats[0].bytesFreed == defragStats[1].bytesFreed); + TEST(defragStats[0].deviceMemoryBlocksFreed == defragStats[1].deviceMemoryBlocksFreed); +} + +void TestDefragmentationFull() +{ + std::vector<AllocInfo> allocations; + + // Create initial allocations. + for(size_t i = 0; i < 400; ++i) + { + AllocInfo allocation; + CreateAllocation(allocation); + allocations.push_back(allocation); + } + + // Delete random allocations + const size_t allocationsToDeletePercent = 80; + size_t allocationsToDelete = allocations.size() * allocationsToDeletePercent / 100; + for(size_t i = 0; i < allocationsToDelete; ++i) + { + size_t index = (size_t)rand() % allocations.size(); + DestroyAllocation(allocations[index]); + allocations.erase(allocations.begin() + index); + } + + for(size_t i = 0; i < allocations.size(); ++i) + ValidateAllocationData(allocations[i]); + + //SaveAllocatorStatsToFile(L"Before.csv"); + + { + std::vector<VmaAllocation> vmaAllocations(allocations.size()); + for(size_t i = 0; i < allocations.size(); ++i) + vmaAllocations[i] = allocations[i].m_Allocation; + + const size_t nonMovablePercent = 0; + size_t nonMovableCount = vmaAllocations.size() * nonMovablePercent / 100; + for(size_t i = 0; i < nonMovableCount; ++i) + { + size_t index = (size_t)rand() % vmaAllocations.size(); + vmaAllocations.erase(vmaAllocations.begin() + index); + } + + const uint32_t defragCount = 1; + for(uint32_t defragIndex = 0; defragIndex < defragCount; ++defragIndex) + { + std::vector<VkBool32> allocationsChanged(vmaAllocations.size()); + + VmaDefragmentationInfo defragmentationInfo; + defragmentationInfo.maxAllocationsToMove = UINT_MAX; + defragmentationInfo.maxBytesToMove = SIZE_MAX; + + wprintf(L"Defragmentation #%u\n", defragIndex); + + time_point begTime = std::chrono::high_resolution_clock::now(); + + VmaDefragmentationStats stats; + VkResult res = vmaDefragment(g_hAllocator, vmaAllocations.data(), vmaAllocations.size(), allocationsChanged.data(), &defragmentationInfo, &stats); + TEST(res >= 0); + + float defragmentDuration = ToFloatSeconds(std::chrono::high_resolution_clock::now() - begTime); + + wprintf(L"Moved allocations %u, bytes %llu\n", stats.allocationsMoved, stats.bytesMoved); + wprintf(L"Freed blocks %u, bytes %llu\n", stats.deviceMemoryBlocksFreed, stats.bytesFreed); + wprintf(L"Time: %.2f s\n", defragmentDuration); + + for(size_t i = 0; i < vmaAllocations.size(); ++i) + { + if(allocationsChanged[i]) + { + RecreateAllocationResource(allocations[i]); + } + } + + for(size_t i = 0; i < allocations.size(); ++i) + ValidateAllocationData(allocations[i]); + + //wchar_t fileName[MAX_PATH]; + //swprintf(fileName, MAX_PATH, L"After_%02u.csv", defragIndex); + //SaveAllocatorStatsToFile(fileName); + } + } + + // Destroy all remaining allocations. + DestroyAllAllocations(allocations); +} + +static void TestDefragmentationGpu() +{ + wprintf(L"Test defragmentation GPU\n"); + + std::vector<AllocInfo> allocations; + + // Create that many allocations to surely fill 3 new blocks of 256 MB. + const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024; + const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024; + const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024; + const size_t bufCount = (size_t)(totalSize / bufSizeMin); + const size_t percentToLeave = 30; + const size_t percentNonMovable = 3; + RandomNumberGenerator rand = { 234522 }; + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + allocCreateInfo.flags = 0; + + // Create all intended buffers. + for(size_t i = 0; i < bufCount; ++i) + { + bufCreateInfo.size = align_up(rand.Generate() % (bufSizeMax - bufSizeMin) + bufSizeMin, 32ull); + + if(rand.Generate() % 100 < percentNonMovable) + { + bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + allocCreateInfo.pUserData = (void*)(uintptr_t)2; + } + else + { + // Different usage just to see different color in output from VmaDumpVis. + bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + // And in JSON dump. + allocCreateInfo.pUserData = (void*)(uintptr_t)1; + } + + AllocInfo alloc; + alloc.CreateBuffer(bufCreateInfo, allocCreateInfo); + alloc.m_StartValue = rand.Generate(); + allocations.push_back(alloc); + } + + // Destroy some percentage of them. + { + const size_t buffersToDestroy = round_div<size_t>(bufCount * (100 - percentToLeave), 100); + for(size_t i = 0; i < buffersToDestroy; ++i) + { + const size_t index = rand.Generate() % allocations.size(); + allocations[index].Destroy(); + allocations.erase(allocations.begin() + index); + } + } + + // Fill them with meaningful data. + UploadGpuData(allocations.data(), allocations.size()); + + wchar_t fileName[MAX_PATH]; + swprintf_s(fileName, L"GPU_defragmentation_A_before.json"); + SaveAllocatorStatsToFile(fileName); + + // Defragment using GPU only. + { + const size_t allocCount = allocations.size(); + + std::vector<VmaAllocation> allocationPtrs; + std::vector<VkBool32> allocationChanged; + std::vector<size_t> allocationOriginalIndex; + + for(size_t i = 0; i < allocCount; ++i) + { + VmaAllocationInfo allocInfo = {}; + vmaGetAllocationInfo(g_hAllocator, allocations[i].m_Allocation, &allocInfo); + if((uintptr_t)allocInfo.pUserData == 1) // Movable + { + allocationPtrs.push_back(allocations[i].m_Allocation); + allocationChanged.push_back(VK_FALSE); + allocationOriginalIndex.push_back(i); + } + } + + const size_t movableAllocCount = allocationPtrs.size(); + + BeginSingleTimeCommands(); + + VmaDefragmentationInfo2 defragInfo = {}; + defragInfo.flags = 0; + defragInfo.allocationCount = (uint32_t)movableAllocCount; + defragInfo.pAllocations = allocationPtrs.data(); + defragInfo.pAllocationsChanged = allocationChanged.data(); + defragInfo.maxGpuBytesToMove = VK_WHOLE_SIZE; + defragInfo.maxGpuAllocationsToMove = UINT32_MAX; + defragInfo.commandBuffer = g_hTemporaryCommandBuffer; + + VmaDefragmentationStats stats = {}; + VmaDefragmentationContext ctx = VK_NULL_HANDLE; + VkResult res = vmaDefragmentationBegin(g_hAllocator, &defragInfo, &stats, &ctx); + TEST(res >= VK_SUCCESS); + + EndSingleTimeCommands(); + + vmaDefragmentationEnd(g_hAllocator, ctx); + + for(size_t i = 0; i < movableAllocCount; ++i) + { + if(allocationChanged[i]) + { + const size_t origAllocIndex = allocationOriginalIndex[i]; + RecreateAllocationResource(allocations[origAllocIndex]); + } + } + + // If corruption detection is enabled, GPU defragmentation may not work on + // memory types that have this detection active, e.g. on Intel. + #if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0 + TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0); + TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0); + #endif + } + + ValidateGpuData(allocations.data(), allocations.size()); + + swprintf_s(fileName, L"GPU_defragmentation_B_after.json"); + SaveAllocatorStatsToFile(fileName); + + // Destroy all remaining buffers. + for(size_t i = allocations.size(); i--; ) + { + allocations[i].Destroy(); + } +} + +static void ProcessDefragmentationStepInfo(VmaDefragmentationPassInfo &stepInfo) +{ + std::vector<VkImageMemoryBarrier> beginImageBarriers; + std::vector<VkImageMemoryBarrier> finalizeImageBarriers; + + VkPipelineStageFlags beginSrcStageMask = 0; + VkPipelineStageFlags beginDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + + VkPipelineStageFlags finalizeSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkPipelineStageFlags finalizeDstStageMask = 0; + + bool wantsMemoryBarrier = false; + + VkMemoryBarrier beginMemoryBarrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; + VkMemoryBarrier finalizeMemoryBarrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; + + for(uint32_t i = 0; i < stepInfo.moveCount; ++i) + { + VmaAllocationInfo info; + vmaGetAllocationInfo(g_hAllocator, stepInfo.pMoves[i].allocation, &info); + + AllocInfo *allocInfo = (AllocInfo *)info.pUserData; + + if(allocInfo->m_Image) + { + VkImage newImage; + + const VkResult result = vkCreateImage(g_hDevice, &allocInfo->m_ImageInfo, g_Allocs, &newImage); + TEST(result >= VK_SUCCESS); + + vkBindImageMemory(g_hDevice, newImage, stepInfo.pMoves[i].memory, stepInfo.pMoves[i].offset); + allocInfo->m_NewImage = newImage; + + // Keep track of our pipeline stages that we need to wait/signal on + beginSrcStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + finalizeDstStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + + // We need one pipeline barrier and two image layout transitions here + // First we'll have to turn our newly created image into VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL + // And the second one is turning the old image into VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL + + VkImageSubresourceRange subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, VK_REMAINING_MIP_LEVELS, + 0, VK_REMAINING_ARRAY_LAYERS + }; + + VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = newImage; + barrier.subresourceRange = subresourceRange; + + beginImageBarriers.push_back(barrier); + + // Second barrier to convert the existing image. This one actually needs a real barrier + barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.oldLayout = allocInfo->m_ImageLayout; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.image = allocInfo->m_Image; + + beginImageBarriers.push_back(barrier); + + // And lastly we need a barrier that turns our new image into the layout of the old one + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = allocInfo->m_ImageLayout; + barrier.image = newImage; + + finalizeImageBarriers.push_back(barrier); + } + else if(allocInfo->m_Buffer) + { + VkBuffer newBuffer; + + const VkResult result = vkCreateBuffer(g_hDevice, &allocInfo->m_BufferInfo, g_Allocs, &newBuffer); + TEST(result >= VK_SUCCESS); + + vkBindBufferMemory(g_hDevice, newBuffer, stepInfo.pMoves[i].memory, stepInfo.pMoves[i].offset); + allocInfo->m_NewBuffer = newBuffer; + + // Keep track of our pipeline stages that we need to wait/signal on + beginSrcStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + finalizeDstStageMask |= VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + + beginMemoryBarrier.srcAccessMask |= VK_ACCESS_MEMORY_WRITE_BIT; + beginMemoryBarrier.dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; + + finalizeMemoryBarrier.srcAccessMask |= VK_ACCESS_TRANSFER_WRITE_BIT; + finalizeMemoryBarrier.dstAccessMask |= VK_ACCESS_MEMORY_READ_BIT; + + wantsMemoryBarrier = true; + } + } + + if(!beginImageBarriers.empty() || wantsMemoryBarrier) + { + const uint32_t memoryBarrierCount = wantsMemoryBarrier ? 1 : 0; + + vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, beginSrcStageMask, beginDstStageMask, 0, + memoryBarrierCount, &beginMemoryBarrier, + 0, nullptr, + (uint32_t)beginImageBarriers.size(), beginImageBarriers.data()); + } + + for(uint32_t i = 0; i < stepInfo.moveCount; ++ i) + { + VmaAllocationInfo info; + vmaGetAllocationInfo(g_hAllocator, stepInfo.pMoves[i].allocation, &info); + + AllocInfo *allocInfo = (AllocInfo *)info.pUserData; + + if(allocInfo->m_Image) + { + std::vector<VkImageCopy> imageCopies; + + // Copy all mips of the source image into the target image + VkOffset3D offset = { 0, 0, 0 }; + VkExtent3D extent = allocInfo->m_ImageInfo.extent; + + VkImageSubresourceLayers subresourceLayers = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, + 0, 1 + }; + + for(uint32_t mip = 0; mip < allocInfo->m_ImageInfo.mipLevels; ++ mip) + { + subresourceLayers.mipLevel = mip; + + VkImageCopy imageCopy{ + subresourceLayers, + offset, + subresourceLayers, + offset, + extent + }; + + imageCopies.push_back(imageCopy); + + extent.width = std::max(uint32_t(1), extent.width >> 1); + extent.height = std::max(uint32_t(1), extent.height >> 1); + extent.depth = std::max(uint32_t(1), extent.depth >> 1); + } + + vkCmdCopyImage( + g_hTemporaryCommandBuffer, + allocInfo->m_Image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + allocInfo->m_NewImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + (uint32_t)imageCopies.size(), imageCopies.data()); + } + else if(allocInfo->m_Buffer) + { + VkBufferCopy region = { + 0, + 0, + allocInfo->m_BufferInfo.size }; + + vkCmdCopyBuffer(g_hTemporaryCommandBuffer, + allocInfo->m_Buffer, allocInfo->m_NewBuffer, + 1, ®ion); + } + } + + if(!finalizeImageBarriers.empty() || wantsMemoryBarrier) + { + const uint32_t memoryBarrierCount = wantsMemoryBarrier ? 1 : 0; + + vkCmdPipelineBarrier(g_hTemporaryCommandBuffer, finalizeSrcStageMask, finalizeDstStageMask, 0, + memoryBarrierCount, &finalizeMemoryBarrier, + 0, nullptr, + (uint32_t)finalizeImageBarriers.size(), finalizeImageBarriers.data()); + } +} + + +static void TestDefragmentationIncrementalBasic() +{ + wprintf(L"Test defragmentation incremental basic\n"); + + std::vector<AllocInfo> allocations; + + // Create that many allocations to surely fill 3 new blocks of 256 MB. + const std::array<uint32_t, 3> imageSizes = { 256, 512, 1024 }; + const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024; + const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024; + const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024; + const size_t imageCount = totalSize / ((size_t)imageSizes[0] * imageSizes[0] * 4) / 2; + const size_t bufCount = (size_t)(totalSize / bufSizeMin) / 2; + const size_t percentToLeave = 30; + RandomNumberGenerator rand = { 234522 }; + + VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + allocCreateInfo.flags = 0; + + // Create all intended images. + for(size_t i = 0; i < imageCount; ++i) + { + const uint32_t size = imageSizes[rand.Generate() % 3]; + + imageInfo.extent.width = size; + imageInfo.extent.height = size; + + AllocInfo alloc; + alloc.CreateImage(imageInfo, allocCreateInfo, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + alloc.m_StartValue = 0; + + allocations.push_back(alloc); + } + + // And all buffers + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + + for(size_t i = 0; i < bufCount; ++i) + { + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); + bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + AllocInfo alloc; + alloc.CreateBuffer(bufCreateInfo, allocCreateInfo); + alloc.m_StartValue = 0; + + allocations.push_back(alloc); + } + + // Destroy some percentage of them. + { + const size_t allocationsToDestroy = round_div<size_t>((imageCount + bufCount) * (100 - percentToLeave), 100); + for(size_t i = 0; i < allocationsToDestroy; ++i) + { + const size_t index = rand.Generate() % allocations.size(); + allocations[index].Destroy(); + allocations.erase(allocations.begin() + index); + } + } + + { + // Set our user data pointers. A real application should probably be more clever here + const size_t allocationCount = allocations.size(); + for(size_t i = 0; i < allocationCount; ++i) + { + AllocInfo &alloc = allocations[i]; + vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc); + } + } + + // Fill them with meaningful data. + UploadGpuData(allocations.data(), allocations.size()); + + wchar_t fileName[MAX_PATH]; + swprintf_s(fileName, L"GPU_defragmentation_incremental_basic_A_before.json"); + SaveAllocatorStatsToFile(fileName); + + // Defragment using GPU only. + { + const size_t allocCount = allocations.size(); + + std::vector<VmaAllocation> allocationPtrs; + + for(size_t i = 0; i < allocCount; ++i) + { + allocationPtrs.push_back(allocations[i].m_Allocation); + } + + const size_t movableAllocCount = allocationPtrs.size(); + + VmaDefragmentationInfo2 defragInfo = {}; + defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_INCREMENTAL; + defragInfo.allocationCount = (uint32_t)movableAllocCount; + defragInfo.pAllocations = allocationPtrs.data(); + defragInfo.maxGpuBytesToMove = VK_WHOLE_SIZE; + defragInfo.maxGpuAllocationsToMove = UINT32_MAX; + + VmaDefragmentationStats stats = {}; + VmaDefragmentationContext ctx = VK_NULL_HANDLE; + VkResult res = vmaDefragmentationBegin(g_hAllocator, &defragInfo, &stats, &ctx); + TEST(res >= VK_SUCCESS); + + res = VK_NOT_READY; + + std::vector<VmaDefragmentationPassMoveInfo> moveInfo; + moveInfo.resize(movableAllocCount); + + while(res == VK_NOT_READY) + { + VmaDefragmentationPassInfo stepInfo = {}; + stepInfo.pMoves = moveInfo.data(); + stepInfo.moveCount = (uint32_t)moveInfo.size(); + + res = vmaBeginDefragmentationPass(g_hAllocator, ctx, &stepInfo); + TEST(res >= VK_SUCCESS); + + BeginSingleTimeCommands(); + std::vector<void*> newHandles; + ProcessDefragmentationStepInfo(stepInfo); + EndSingleTimeCommands(); + + res = vmaEndDefragmentationPass(g_hAllocator, ctx); + + // Destroy old buffers/images and replace them with new handles. + for(size_t i = 0; i < stepInfo.moveCount; ++i) + { + VmaAllocation const alloc = stepInfo.pMoves[i].allocation; + VmaAllocationInfo vmaAllocInfo; + vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo); + AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData; + if(allocInfo->m_Buffer) + { + assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage); + vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs); + allocInfo->m_Buffer = allocInfo->m_NewBuffer; + allocInfo->m_NewBuffer = VK_NULL_HANDLE; + } + else if(allocInfo->m_Image) + { + assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer); + vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs); + allocInfo->m_Image = allocInfo->m_NewImage; + allocInfo->m_NewImage = VK_NULL_HANDLE; + } + else + assert(0); + } + } + + TEST(res >= VK_SUCCESS); + vmaDefragmentationEnd(g_hAllocator, ctx); + + // If corruption detection is enabled, GPU defragmentation may not work on + // memory types that have this detection active, e.g. on Intel. +#if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0 + TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0); + TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0); +#endif + } + + //ValidateGpuData(allocations.data(), allocations.size()); + + swprintf_s(fileName, L"GPU_defragmentation_incremental_basic_B_after.json"); + SaveAllocatorStatsToFile(fileName); + + // Destroy all remaining buffers and images. + for(size_t i = allocations.size(); i--; ) + { + allocations[i].Destroy(); + } +} + +void TestDefragmentationIncrementalComplex() +{ + wprintf(L"Test defragmentation incremental complex\n"); + + std::vector<AllocInfo> allocations; + + // Create that many allocations to surely fill 3 new blocks of 256 MB. + const std::array<uint32_t, 3> imageSizes = { 256, 512, 1024 }; + const VkDeviceSize bufSizeMin = 5ull * 1024 * 1024; + const VkDeviceSize bufSizeMax = 10ull * 1024 * 1024; + const VkDeviceSize totalSize = 3ull * 256 * 1024 * 1024; + const size_t imageCount = (size_t)(totalSize / (imageSizes[0] * imageSizes[0] * 4)) / 2; + const size_t bufCount = (size_t)(totalSize / bufSizeMin) / 2; + const size_t percentToLeave = 30; + RandomNumberGenerator rand = { 234522 }; + + VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + allocCreateInfo.flags = 0; + + // Create all intended images. + for(size_t i = 0; i < imageCount; ++i) + { + const uint32_t size = imageSizes[rand.Generate() % 3]; + + imageInfo.extent.width = size; + imageInfo.extent.height = size; + + AllocInfo alloc; + alloc.CreateImage(imageInfo, allocCreateInfo, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + alloc.m_StartValue = 0; + + allocations.push_back(alloc); + } + + // And all buffers + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + + for(size_t i = 0; i < bufCount; ++i) + { + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); + bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + AllocInfo alloc; + alloc.CreateBuffer(bufCreateInfo, allocCreateInfo); + alloc.m_StartValue = 0; + + allocations.push_back(alloc); + } + + // Destroy some percentage of them. + { + const size_t allocationsToDestroy = round_div<size_t>((imageCount + bufCount) * (100 - percentToLeave), 100); + for(size_t i = 0; i < allocationsToDestroy; ++i) + { + const size_t index = rand.Generate() % allocations.size(); + allocations[index].Destroy(); + allocations.erase(allocations.begin() + index); + } + } + + { + // Set our user data pointers. A real application should probably be more clever here + const size_t allocationCount = allocations.size(); + for(size_t i = 0; i < allocationCount; ++i) + { + AllocInfo &alloc = allocations[i]; + vmaSetAllocationUserData(g_hAllocator, alloc.m_Allocation, &alloc); + } + } + + // Fill them with meaningful data. + UploadGpuData(allocations.data(), allocations.size()); + + wchar_t fileName[MAX_PATH]; + swprintf_s(fileName, L"GPU_defragmentation_incremental_complex_A_before.json"); + SaveAllocatorStatsToFile(fileName); + + std::vector<AllocInfo> additionalAllocations; + +#define MakeAdditionalAllocation() \ + do { \ + { \ + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); \ + bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; \ + \ + AllocInfo alloc; \ + alloc.CreateBuffer(bufCreateInfo, allocCreateInfo); \ + \ + additionalAllocations.push_back(alloc); \ + } \ + } while(0) + + // Defragment using GPU only. + { + const size_t allocCount = allocations.size(); + + std::vector<VmaAllocation> allocationPtrs; + + for(size_t i = 0; i < allocCount; ++i) + { + VmaAllocationInfo allocInfo = {}; + vmaGetAllocationInfo(g_hAllocator, allocations[i].m_Allocation, &allocInfo); + + allocationPtrs.push_back(allocations[i].m_Allocation); + } + + const size_t movableAllocCount = allocationPtrs.size(); + + VmaDefragmentationInfo2 defragInfo = {}; + defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_INCREMENTAL; + defragInfo.allocationCount = (uint32_t)movableAllocCount; + defragInfo.pAllocations = allocationPtrs.data(); + defragInfo.maxGpuBytesToMove = VK_WHOLE_SIZE; + defragInfo.maxGpuAllocationsToMove = UINT32_MAX; + + VmaDefragmentationStats stats = {}; + VmaDefragmentationContext ctx = VK_NULL_HANDLE; + VkResult res = vmaDefragmentationBegin(g_hAllocator, &defragInfo, &stats, &ctx); + TEST(res >= VK_SUCCESS); + + res = VK_NOT_READY; + + std::vector<VmaDefragmentationPassMoveInfo> moveInfo; + moveInfo.resize(movableAllocCount); + + MakeAdditionalAllocation(); + + while(res == VK_NOT_READY) + { + VmaDefragmentationPassInfo stepInfo = {}; + stepInfo.pMoves = moveInfo.data(); + stepInfo.moveCount = (uint32_t)moveInfo.size(); + + res = vmaBeginDefragmentationPass(g_hAllocator, ctx, &stepInfo); + TEST(res >= VK_SUCCESS); + + MakeAdditionalAllocation(); + + BeginSingleTimeCommands(); + ProcessDefragmentationStepInfo(stepInfo); + EndSingleTimeCommands(); + + res = vmaEndDefragmentationPass(g_hAllocator, ctx); + + // Destroy old buffers/images and replace them with new handles. + for(size_t i = 0; i < stepInfo.moveCount; ++i) + { + VmaAllocation const alloc = stepInfo.pMoves[i].allocation; + VmaAllocationInfo vmaAllocInfo; + vmaGetAllocationInfo(g_hAllocator, alloc, &vmaAllocInfo); + AllocInfo* allocInfo = (AllocInfo*)vmaAllocInfo.pUserData; + if(allocInfo->m_Buffer) + { + assert(allocInfo->m_NewBuffer && !allocInfo->m_Image && !allocInfo->m_NewImage); + vkDestroyBuffer(g_hDevice, allocInfo->m_Buffer, g_Allocs); + allocInfo->m_Buffer = allocInfo->m_NewBuffer; + allocInfo->m_NewBuffer = VK_NULL_HANDLE; + } + else if(allocInfo->m_Image) + { + assert(allocInfo->m_NewImage && !allocInfo->m_Buffer && !allocInfo->m_NewBuffer); + vkDestroyImage(g_hDevice, allocInfo->m_Image, g_Allocs); + allocInfo->m_Image = allocInfo->m_NewImage; + allocInfo->m_NewImage = VK_NULL_HANDLE; + } + else + assert(0); + } + + MakeAdditionalAllocation(); + } + + TEST(res >= VK_SUCCESS); + vmaDefragmentationEnd(g_hAllocator, ctx); + + // If corruption detection is enabled, GPU defragmentation may not work on + // memory types that have this detection active, e.g. on Intel. +#if !defined(VMA_DEBUG_DETECT_CORRUPTION) || VMA_DEBUG_DETECT_CORRUPTION == 0 + TEST(stats.allocationsMoved > 0 && stats.bytesMoved > 0); + TEST(stats.deviceMemoryBlocksFreed > 0 && stats.bytesFreed > 0); +#endif + } + + //ValidateGpuData(allocations.data(), allocations.size()); + + swprintf_s(fileName, L"GPU_defragmentation_incremental_complex_B_after.json"); + SaveAllocatorStatsToFile(fileName); + + // Destroy all remaining buffers. + for(size_t i = allocations.size(); i--; ) + { + allocations[i].Destroy(); + } + + for(size_t i = additionalAllocations.size(); i--; ) + { + additionalAllocations[i].Destroy(); + } +} + + +static void TestUserData() +{ + VkResult res; + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + bufCreateInfo.size = 0x10000; + + for(uint32_t testIndex = 0; testIndex < 2; ++testIndex) + { + // Opaque pointer + { + + void* numberAsPointer = (void*)(size_t)0xC2501FF3u; + void* pointerToSomething = &res; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.pUserData = numberAsPointer; + if(testIndex == 1) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + TEST(res == VK_SUCCESS); + TEST(allocInfo.pUserData = numberAsPointer); + + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + TEST(allocInfo.pUserData == numberAsPointer); + + vmaSetAllocationUserData(g_hAllocator, alloc, pointerToSomething); + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + TEST(allocInfo.pUserData == pointerToSomething); + + vmaDestroyBuffer(g_hAllocator, buf, alloc); + } + + // String + { + const char* name1 = "Buffer name \\\"\'<>&% \nSecond line .,;="; + const char* name2 = "2"; + const size_t name1Len = strlen(name1); + + char* name1Buf = new char[name1Len + 1]; + strcpy_s(name1Buf, name1Len + 1, name1); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT; + allocCreateInfo.pUserData = name1Buf; + if(testIndex == 1) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + TEST(res == VK_SUCCESS); + TEST(allocInfo.pUserData != nullptr && allocInfo.pUserData != name1Buf); + TEST(strcmp(name1, (const char*)allocInfo.pUserData) == 0); + + delete[] name1Buf; + + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + TEST(strcmp(name1, (const char*)allocInfo.pUserData) == 0); + + vmaSetAllocationUserData(g_hAllocator, alloc, (void*)name2); + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + TEST(strcmp(name2, (const char*)allocInfo.pUserData) == 0); + + vmaSetAllocationUserData(g_hAllocator, alloc, nullptr); + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + TEST(allocInfo.pUserData == nullptr); + + vmaDestroyBuffer(g_hAllocator, buf, alloc); + } + } +} + +static void TestInvalidAllocations() +{ + VkResult res; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + + // Try to allocate 0 bytes. + { + VkMemoryRequirements memReq = {}; + memReq.size = 0; // !!! + memReq.alignment = 4; + memReq.memoryTypeBits = UINT32_MAX; + VmaAllocation alloc = VK_NULL_HANDLE; + res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr); + TEST(res == VK_ERROR_VALIDATION_FAILED_EXT && alloc == VK_NULL_HANDLE); + } + + // Try to create buffer with size = 0. + { + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufCreateInfo.size = 0; // !!! + VkBuffer buf = VK_NULL_HANDLE; + VmaAllocation alloc = VK_NULL_HANDLE; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, nullptr); + TEST(res == VK_ERROR_VALIDATION_FAILED_EXT && buf == VK_NULL_HANDLE && alloc == VK_NULL_HANDLE); + } + + // Try to create image with one dimension = 0. + { + VkImageCreateInfo imageCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imageCreateInfo.format = VK_FORMAT_B8G8R8A8_UNORM; + imageCreateInfo.extent.width = 128; + imageCreateInfo.extent.height = 0; // !!! + imageCreateInfo.extent.depth = 1; + imageCreateInfo.mipLevels = 1; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR; + imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + VkImage image = VK_NULL_HANDLE; + VmaAllocation alloc = VK_NULL_HANDLE; + res = vmaCreateImage(g_hAllocator, &imageCreateInfo, &allocCreateInfo, &image, &alloc, nullptr); + TEST(res == VK_ERROR_VALIDATION_FAILED_EXT && image == VK_NULL_HANDLE && alloc == VK_NULL_HANDLE); + } +} + +static void TestMemoryRequirements() +{ + VkResult res; + VkBuffer buf; + VmaAllocation alloc; + VmaAllocationInfo allocInfo; + + const VkPhysicalDeviceMemoryProperties* memProps; + vmaGetMemoryProperties(g_hAllocator, &memProps); + + VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufInfo.size = 128; + + VmaAllocationCreateInfo allocCreateInfo = {}; + + // No requirements. + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + TEST(res == VK_SUCCESS); + vmaDestroyBuffer(g_hAllocator, buf, alloc); + + // Usage. + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.requiredFlags = 0; + allocCreateInfo.preferredFlags = 0; + allocCreateInfo.memoryTypeBits = UINT32_MAX; + + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + TEST(res == VK_SUCCESS); + TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + vmaDestroyBuffer(g_hAllocator, buf, alloc); + + // Required flags, preferred flags. + allocCreateInfo.usage = VMA_MEMORY_USAGE_UNKNOWN; + allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + allocCreateInfo.memoryTypeBits = 0; + + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + TEST(res == VK_SUCCESS); + TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + TEST(memProps->memoryTypes[allocInfo.memoryType].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + vmaDestroyBuffer(g_hAllocator, buf, alloc); + + // memoryTypeBits. + const uint32_t memType = allocInfo.memoryType; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.requiredFlags = 0; + allocCreateInfo.preferredFlags = 0; + allocCreateInfo.memoryTypeBits = 1u << memType; + + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + TEST(res == VK_SUCCESS); + TEST(allocInfo.memoryType == memType); + vmaDestroyBuffer(g_hAllocator, buf, alloc); + +} + +static void TestGetAllocatorInfo() +{ + wprintf(L"Test vnaGetAllocatorInfo\n"); + + VmaAllocatorInfo allocInfo = {}; + vmaGetAllocatorInfo(g_hAllocator, &allocInfo); + TEST(allocInfo.instance == g_hVulkanInstance); + TEST(allocInfo.physicalDevice == g_hPhysicalDevice); + TEST(allocInfo.device == g_hDevice); +} + +static void TestBasics() +{ + wprintf(L"Test basics\n"); + + VkResult res; + + TestGetAllocatorInfo(); + + TestMemoryRequirements(); + + // Lost allocation + { + VmaAllocation alloc = VK_NULL_HANDLE; + vmaCreateLostAllocation(g_hAllocator, &alloc); + TEST(alloc != VK_NULL_HANDLE); + + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, alloc, &allocInfo); + TEST(allocInfo.deviceMemory == VK_NULL_HANDLE); + TEST(allocInfo.size == 0); + + vmaFreeMemory(g_hAllocator, alloc); + } + + // Allocation that is MAPPED and not necessarily HOST_VISIBLE. + { + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + bufCreateInfo.size = 128; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VkBuffer buf; VmaAllocation alloc; VmaAllocationInfo allocInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + TEST(res == VK_SUCCESS); + + vmaDestroyBuffer(g_hAllocator, buf, alloc); + + // Same with OWN_MEMORY. + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + TEST(res == VK_SUCCESS); + + vmaDestroyBuffer(g_hAllocator, buf, alloc); + } + + TestUserData(); + + TestInvalidAllocations(); +} + +static void TestAllocationVersusResourceSize() +{ + wprintf(L"Test allocation versus resource size\n"); + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = 22921; // Prime number + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + + for(uint32_t i = 0; i < 2; ++i) + { + allocCreateInfo.flags = (i == 1) ? VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT : 0; + + AllocInfo info; + info.CreateBuffer(bufCreateInfo, allocCreateInfo); + + VmaAllocationInfo allocInfo = {}; + vmaGetAllocationInfo(g_hAllocator, info.m_Allocation, &allocInfo); + //wprintf(L" Buffer size = %llu, allocation size = %llu\n", bufCreateInfo.size, allocInfo.size); + + // Map and test accessing entire area of the allocation, not only the buffer. + void* mappedPtr = nullptr; + VkResult res = vmaMapMemory(g_hAllocator, info.m_Allocation, &mappedPtr); + TEST(res == VK_SUCCESS); + + memset(mappedPtr, 0xCC, (size_t)allocInfo.size); + + vmaUnmapMemory(g_hAllocator, info.m_Allocation); + + info.Destroy(); + } +} + +static void TestPool_MinBlockCount() +{ +#if defined(VMA_DEBUG_MARGIN) && VMA_DEBUG_MARGIN > 0 + return; +#endif + + wprintf(L"Test Pool MinBlockCount\n"); + VkResult res; + + static const VkDeviceSize ALLOC_SIZE = 512ull * 1024; + static const VkDeviceSize BLOCK_SIZE = ALLOC_SIZE * 2; // Each block can fit 2 allocations. + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_COPY; + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufCreateInfo.size = ALLOC_SIZE; + + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.blockSize = BLOCK_SIZE; + poolCreateInfo.minBlockCount = 2; // At least 2 blocks always present. + res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + + VmaPool pool = VK_NULL_HANDLE; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS && pool != VK_NULL_HANDLE); + + // Check that there are 2 blocks preallocated as requested. + VmaPoolStats begPoolStats = {}; + vmaGetPoolStats(g_hAllocator, pool, &begPoolStats); + TEST(begPoolStats.blockCount == 2 && begPoolStats.allocationCount == 0 && begPoolStats.size == BLOCK_SIZE * 2); + + // Allocate 5 buffers to create 3 blocks. + static const uint32_t BUF_COUNT = 5; + allocCreateInfo.pool = pool; + std::vector<AllocInfo> allocs(BUF_COUNT); + for(uint32_t i = 0; i < BUF_COUNT; ++i) + { + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &allocs[i].m_Buffer, &allocs[i].m_Allocation, nullptr); + TEST(res == VK_SUCCESS && allocs[i].m_Buffer != VK_NULL_HANDLE && allocs[i].m_Allocation != VK_NULL_HANDLE); + } + + // Check that there are really 3 blocks. + VmaPoolStats poolStats2 = {}; + vmaGetPoolStats(g_hAllocator, pool, &poolStats2); + TEST(poolStats2.blockCount == 3 && poolStats2.allocationCount == BUF_COUNT && poolStats2.size == BLOCK_SIZE * 3); + + // Free two first allocations to make one block empty. + allocs[0].Destroy(); + allocs[1].Destroy(); + + // Check that there are still 3 blocks due to hysteresis. + VmaPoolStats poolStats3 = {}; + vmaGetPoolStats(g_hAllocator, pool, &poolStats3); + TEST(poolStats3.blockCount == 3 && poolStats3.allocationCount == BUF_COUNT - 2 && poolStats2.size == BLOCK_SIZE * 3); + + // Free the last allocation to make second block empty. + allocs[BUF_COUNT - 1].Destroy(); + + // Check that there are now 2 blocks only. + VmaPoolStats poolStats4 = {}; + vmaGetPoolStats(g_hAllocator, pool, &poolStats4); + TEST(poolStats4.blockCount == 2 && poolStats4.allocationCount == BUF_COUNT - 3 && poolStats4.size == BLOCK_SIZE * 2); + + // Cleanup. + for(size_t i = allocs.size(); i--; ) + { + allocs[i].Destroy(); + } + vmaDestroyPool(g_hAllocator, pool); +} + +static void TestPool_MinAllocationAlignment() +{ + wprintf(L"Test Pool MinAllocationAlignment\n"); + VkResult res; + + static const VkDeviceSize ALLOC_SIZE = 32; + static const VkDeviceSize BLOCK_SIZE = 1024 * 1024; + static const VkDeviceSize MIN_ALLOCATION_ALIGNMENT = 64 * 1024; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_COPY; + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufCreateInfo.size = ALLOC_SIZE; + + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.blockSize = BLOCK_SIZE; + poolCreateInfo.minAllocationAlignment = MIN_ALLOCATION_ALIGNMENT; + res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + + VmaPool pool = VK_NULL_HANDLE; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS && pool != VK_NULL_HANDLE); + + static const uint32_t BUF_COUNT = 4; + allocCreateInfo = {}; + allocCreateInfo.pool = pool; + std::vector<AllocInfo> allocs(BUF_COUNT); + for(uint32_t i = 0; i < BUF_COUNT; ++i) + { + VmaAllocationInfo allocInfo = {}; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &allocs[i].m_Buffer, &allocs[i].m_Allocation, &allocInfo); + TEST(res == VK_SUCCESS && allocs[i].m_Buffer != VK_NULL_HANDLE && allocs[i].m_Allocation != VK_NULL_HANDLE); + TEST(allocInfo.offset % MIN_ALLOCATION_ALIGNMENT == 0); + } + + // Cleanup. + for(size_t i = allocs.size(); i--; ) + { + allocs[i].Destroy(); + } + vmaDestroyPool(g_hAllocator, pool); +} + +void TestHeapSizeLimit() +{ + const VkDeviceSize HEAP_SIZE_LIMIT = 100ull * 1024 * 1024; // 100 MB + const VkDeviceSize BLOCK_SIZE = 10ull * 1024 * 1024; // 10 MB + + VkDeviceSize heapSizeLimit[VK_MAX_MEMORY_HEAPS]; + for(uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i) + { + heapSizeLimit[i] = HEAP_SIZE_LIMIT; + } + + VmaAllocatorCreateInfo allocatorCreateInfo = {}; + allocatorCreateInfo.physicalDevice = g_hPhysicalDevice; + allocatorCreateInfo.device = g_hDevice; + allocatorCreateInfo.instance = g_hVulkanInstance; + allocatorCreateInfo.pHeapSizeLimit = heapSizeLimit; + + VmaAllocator hAllocator; + VkResult res = vmaCreateAllocator(&allocatorCreateInfo, &hAllocator); + TEST(res == VK_SUCCESS); + + struct Item + { + VkBuffer hBuf; + VmaAllocation hAlloc; + }; + std::vector<Item> items; + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + // 1. Allocate two blocks of dedicated memory, half the size of BLOCK_SIZE. + VmaAllocationInfo dedicatedAllocInfo; + { + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + bufCreateInfo.size = BLOCK_SIZE / 2; + + for(size_t i = 0; i < 2; ++i) + { + Item item; + res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, &dedicatedAllocInfo); + TEST(res == VK_SUCCESS); + items.push_back(item); + } + } + + // Create pool to make sure allocations must be out of this memory type. + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.memoryTypeIndex = dedicatedAllocInfo.memoryType; + poolCreateInfo.blockSize = BLOCK_SIZE; + + VmaPool hPool; + res = vmaCreatePool(hAllocator, &poolCreateInfo, &hPool); + TEST(res == VK_SUCCESS); + + // 2. Allocate normal buffers from all the remaining memory. + { + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = hPool; + + bufCreateInfo.size = BLOCK_SIZE / 2; + + const size_t bufCount = ((HEAP_SIZE_LIMIT / BLOCK_SIZE) - 1) * 2; + for(size_t i = 0; i < bufCount; ++i) + { + Item item; + res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &item.hBuf, &item.hAlloc, nullptr); + TEST(res == VK_SUCCESS); + items.push_back(item); + } + } + + // 3. Allocation of one more (even small) buffer should fail. + { + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = hPool; + + bufCreateInfo.size = 128; + + VkBuffer hBuf; + VmaAllocation hAlloc; + res = vmaCreateBuffer(hAllocator, &bufCreateInfo, &allocCreateInfo, &hBuf, &hAlloc, nullptr); + TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY); + } + + // Destroy everything. + for(size_t i = items.size(); i--; ) + { + vmaDestroyBuffer(hAllocator, items[i].hBuf, items[i].hAlloc); + } + + vmaDestroyPool(hAllocator, hPool); + + vmaDestroyAllocator(hAllocator); +} + +#if VMA_DEBUG_MARGIN +static void TestDebugMargin() +{ + if(VMA_DEBUG_MARGIN == 0) + { + return; + } + + VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + + // Create few buffers of different size. + const size_t BUF_COUNT = 10; + BufferInfo buffers[BUF_COUNT]; + VmaAllocationInfo allocInfo[BUF_COUNT]; + for(size_t i = 0; i < 10; ++i) + { + bufInfo.size = (VkDeviceSize)(i + 1) * 64; + // Last one will be mapped. + allocCreateInfo.flags = (i == BUF_COUNT - 1) ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0; + + VkResult res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, &buffers[i].Buffer, &buffers[i].Allocation, &allocInfo[i]); + TEST(res == VK_SUCCESS); + // Margin is preserved also at the beginning of a block. + TEST(allocInfo[i].offset >= VMA_DEBUG_MARGIN); + + if(i == BUF_COUNT - 1) + { + // Fill with data. + TEST(allocInfo[i].pMappedData != nullptr); + // Uncomment this "+ 1" to overwrite past end of allocation and check corruption detection. + memset(allocInfo[i].pMappedData, 0xFF, bufInfo.size /* + 1 */); + } + } + + // Check if their offsets preserve margin between them. + std::sort(allocInfo, allocInfo + BUF_COUNT, [](const VmaAllocationInfo& lhs, const VmaAllocationInfo& rhs) -> bool + { + if(lhs.deviceMemory != rhs.deviceMemory) + { + return lhs.deviceMemory < rhs.deviceMemory; + } + return lhs.offset < rhs.offset; + }); + for(size_t i = 1; i < BUF_COUNT; ++i) + { + if(allocInfo[i].deviceMemory == allocInfo[i - 1].deviceMemory) + { + TEST(allocInfo[i].offset >= allocInfo[i - 1].offset + VMA_DEBUG_MARGIN); + } + } + + VkResult res = vmaCheckCorruption(g_hAllocator, UINT32_MAX); + TEST(res == VK_SUCCESS); + + // Destroy all buffers. + for(size_t i = BUF_COUNT; i--; ) + { + vmaDestroyBuffer(g_hAllocator, buffers[i].Buffer, buffers[i].Allocation); + } +} +#endif + +static void TestLinearAllocator() +{ + wprintf(L"Test linear allocator\n"); + + RandomNumberGenerator rand{645332}; + + VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + sampleBufCreateInfo.size = 1024; // Whatever. + sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + VmaAllocationCreateInfo sampleAllocCreateInfo = {}; + sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VmaPoolCreateInfo poolCreateInfo = {}; + VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + + poolCreateInfo.blockSize = 1024 * 300; + poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT; + poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; + + VmaPool pool = nullptr; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS); + + VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = pool; + + constexpr size_t maxBufCount = 100; + std::vector<BufferInfo> bufInfo; + + constexpr VkDeviceSize bufSizeMin = 16; + constexpr VkDeviceSize bufSizeMax = 1024; + VmaAllocationInfo allocInfo; + VkDeviceSize prevOffset = 0; + + // Test one-time free. + for(size_t i = 0; i < 2; ++i) + { + // Allocate number of buffers of varying size that surely fit into this block. + VkDeviceSize bufSumSize = 0; + for(size_t i = 0; i < maxBufCount; ++i) + { + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + TEST(i == 0 || allocInfo.offset > prevOffset); + bufInfo.push_back(newBufInfo); + prevOffset = allocInfo.offset; + bufSumSize += bufCreateInfo.size; + } + + // Validate pool stats. + VmaPoolStats stats; + vmaGetPoolStats(g_hAllocator, pool, &stats); + TEST(stats.size == poolCreateInfo.blockSize); + TEST(stats.unusedSize = poolCreateInfo.blockSize - bufSumSize); + TEST(stats.allocationCount == bufInfo.size()); + + // Destroy the buffers in random order. + while(!bufInfo.empty()) + { + const size_t indexToDestroy = rand.Generate() % bufInfo.size(); + const BufferInfo& currBufInfo = bufInfo[indexToDestroy]; + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.erase(bufInfo.begin() + indexToDestroy); + } + } + + // Test stack. + { + // Allocate number of buffers of varying size that surely fit into this block. + for(size_t i = 0; i < maxBufCount; ++i) + { + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + TEST(i == 0 || allocInfo.offset > prevOffset); + bufInfo.push_back(newBufInfo); + prevOffset = allocInfo.offset; + } + + // Destroy few buffers from top of the stack. + for(size_t i = 0; i < maxBufCount / 5; ++i) + { + const BufferInfo& currBufInfo = bufInfo.back(); + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.pop_back(); + } + + // Create some more + for(size_t i = 0; i < maxBufCount / 5; ++i) + { + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + TEST(i == 0 || allocInfo.offset > prevOffset); + bufInfo.push_back(newBufInfo); + prevOffset = allocInfo.offset; + } + + // Destroy the buffers in reverse order. + while(!bufInfo.empty()) + { + const BufferInfo& currBufInfo = bufInfo.back(); + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.pop_back(); + } + } + + // Test ring buffer. + { + // Allocate number of buffers that surely fit into this block. + bufCreateInfo.size = bufSizeMax; + for(size_t i = 0; i < maxBufCount; ++i) + { + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + TEST(i == 0 || allocInfo.offset > prevOffset); + bufInfo.push_back(newBufInfo); + prevOffset = allocInfo.offset; + } + + // Free and allocate new buffers so many times that we make sure we wrap-around at least once. + const size_t buffersPerIter = maxBufCount / 10 - 1; + const size_t iterCount = poolCreateInfo.blockSize / bufCreateInfo.size / buffersPerIter * 2; + for(size_t iter = 0; iter < iterCount; ++iter) + { + for(size_t bufPerIter = 0; bufPerIter < buffersPerIter; ++bufPerIter) + { + const BufferInfo& currBufInfo = bufInfo.front(); + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.erase(bufInfo.begin()); + } + for(size_t bufPerIter = 0; bufPerIter < buffersPerIter; ++bufPerIter) + { + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + } + } + + // Allocate buffers until we reach out-of-memory. + uint32_t debugIndex = 0; + while(res == VK_SUCCESS) + { + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + if(res == VK_SUCCESS) + { + bufInfo.push_back(newBufInfo); + } + else + { + TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY); + } + ++debugIndex; + } + + // Destroy the buffers in random order. + while(!bufInfo.empty()) + { + const size_t indexToDestroy = rand.Generate() % bufInfo.size(); + const BufferInfo& currBufInfo = bufInfo[indexToDestroy]; + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.erase(bufInfo.begin() + indexToDestroy); + } + } + + // Test double stack. + { + // Allocate number of buffers of varying size that surely fit into this block, alternate from bottom/top. + VkDeviceSize prevOffsetLower = 0; + VkDeviceSize prevOffsetUpper = poolCreateInfo.blockSize; + for(size_t i = 0; i < maxBufCount; ++i) + { + const bool upperAddress = (i % 2) != 0; + if(upperAddress) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; + else + allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + if(upperAddress) + { + TEST(allocInfo.offset < prevOffsetUpper); + prevOffsetUpper = allocInfo.offset; + } + else + { + TEST(allocInfo.offset >= prevOffsetLower); + prevOffsetLower = allocInfo.offset; + } + TEST(prevOffsetLower < prevOffsetUpper); + bufInfo.push_back(newBufInfo); + } + + // Destroy few buffers from top of the stack. + for(size_t i = 0; i < maxBufCount / 5; ++i) + { + const BufferInfo& currBufInfo = bufInfo.back(); + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.pop_back(); + } + + // Create some more + for(size_t i = 0; i < maxBufCount / 5; ++i) + { + const bool upperAddress = (i % 2) != 0; + if(upperAddress) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; + else + allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + } + + // Destroy the buffers in reverse order. + while(!bufInfo.empty()) + { + const BufferInfo& currBufInfo = bufInfo.back(); + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.pop_back(); + } + + // Create buffers on both sides until we reach out of memory. + prevOffsetLower = 0; + prevOffsetUpper = poolCreateInfo.blockSize; + res = VK_SUCCESS; + for(size_t i = 0; res == VK_SUCCESS; ++i) + { + const bool upperAddress = (i % 2) != 0; + if(upperAddress) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; + else + allocCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + if(res == VK_SUCCESS) + { + if(upperAddress) + { + TEST(allocInfo.offset < prevOffsetUpper); + prevOffsetUpper = allocInfo.offset; + } + else + { + TEST(allocInfo.offset >= prevOffsetLower); + prevOffsetLower = allocInfo.offset; + } + TEST(prevOffsetLower < prevOffsetUpper); + bufInfo.push_back(newBufInfo); + } + } + + // Destroy the buffers in random order. + while(!bufInfo.empty()) + { + const size_t indexToDestroy = rand.Generate() % bufInfo.size(); + const BufferInfo& currBufInfo = bufInfo[indexToDestroy]; + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.erase(bufInfo.begin() + indexToDestroy); + } + + // Create buffers on upper side only, constant size, until we reach out of memory. + prevOffsetUpper = poolCreateInfo.blockSize; + res = VK_SUCCESS; + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; + bufCreateInfo.size = bufSizeMax; + for(size_t i = 0; res == VK_SUCCESS; ++i) + { + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + if(res == VK_SUCCESS) + { + TEST(allocInfo.offset < prevOffsetUpper); + prevOffsetUpper = allocInfo.offset; + bufInfo.push_back(newBufInfo); + } + } + + // Destroy the buffers in reverse order. + while(!bufInfo.empty()) + { + const BufferInfo& currBufInfo = bufInfo.back(); + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.pop_back(); + } + } + + // Test ring buffer with lost allocations. + { + // Allocate number of buffers until pool is full. + // Notice CAN_BECOME_LOST flag and call to vmaSetCurrentFrameIndex. + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT; + res = VK_SUCCESS; + for(size_t i = 0; res == VK_SUCCESS; ++i) + { + vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); + + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); + + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + if(res == VK_SUCCESS) + bufInfo.push_back(newBufInfo); + } + + // Free first half of it. + { + const size_t buffersToDelete = bufInfo.size() / 2; + for(size_t i = 0; i < buffersToDelete; ++i) + { + vmaDestroyBuffer(g_hAllocator, bufInfo[i].Buffer, bufInfo[i].Allocation); + } + bufInfo.erase(bufInfo.begin(), bufInfo.begin() + buffersToDelete); + } + + // Allocate number of buffers until pool is full again. + // This way we make sure ring buffers wraps around, front in in the middle. + res = VK_SUCCESS; + for(size_t i = 0; res == VK_SUCCESS; ++i) + { + vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); + + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); + + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + if(res == VK_SUCCESS) + bufInfo.push_back(newBufInfo); + } + + VkDeviceSize firstNewOffset; + { + vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); + + // Allocate a large buffer with CAN_MAKE_OTHER_LOST. + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; + bufCreateInfo.size = bufSizeMax; + + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + firstNewOffset = allocInfo.offset; + + // Make sure at least one buffer from the beginning became lost. + vmaGetAllocationInfo(g_hAllocator, bufInfo[0].Allocation, &allocInfo); + TEST(allocInfo.deviceMemory == VK_NULL_HANDLE); + } + +#if 0 // TODO Fix and uncomment. Failing on Intel. + // Allocate more buffers that CAN_MAKE_OTHER_LOST until we wrap-around with this. + size_t newCount = 1; + for(;;) + { + vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); + + bufCreateInfo.size = align_up<VkDeviceSize>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); + + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + ++newCount; + if(allocInfo.offset < firstNewOffset) + break; + } +#endif + + // Delete buffers that are lost. + for(size_t i = bufInfo.size(); i--; ) + { + vmaGetAllocationInfo(g_hAllocator, bufInfo[i].Allocation, &allocInfo); + if(allocInfo.deviceMemory == VK_NULL_HANDLE) + { + vmaDestroyBuffer(g_hAllocator, bufInfo[i].Buffer, bufInfo[i].Allocation); + bufInfo.erase(bufInfo.begin() + i); + } + } + + // Test vmaMakePoolAllocationsLost + { + vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); + + size_t lostAllocCount = 0; + vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostAllocCount); + TEST(lostAllocCount > 0); + + size_t realLostAllocCount = 0; + for(size_t i = 0; i < bufInfo.size(); ++i) + { + vmaGetAllocationInfo(g_hAllocator, bufInfo[i].Allocation, &allocInfo); + if(allocInfo.deviceMemory == VK_NULL_HANDLE) + ++realLostAllocCount; + } + TEST(realLostAllocCount == lostAllocCount); + } + + // Destroy all the buffers in forward order. + for(size_t i = 0; i < bufInfo.size(); ++i) + vmaDestroyBuffer(g_hAllocator, bufInfo[i].Buffer, bufInfo[i].Allocation); + bufInfo.clear(); + } + + vmaDestroyPool(g_hAllocator, pool); +} + +static void TestLinearAllocatorMultiBlock() +{ + wprintf(L"Test linear allocator multi block\n"); + + RandomNumberGenerator rand{345673}; + + VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + sampleBufCreateInfo.size = 1024 * 1024; + sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo sampleAllocCreateInfo = {}; + sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT; + VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + + VmaPool pool = nullptr; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS); + + VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = pool; + + std::vector<BufferInfo> bufInfo; + VmaAllocationInfo allocInfo; + + // Test one-time free. + { + // Allocate buffers until we move to a second block. + VkDeviceMemory lastMem = VK_NULL_HANDLE; + for(uint32_t i = 0; ; ++i) + { + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + if(lastMem && allocInfo.deviceMemory != lastMem) + { + break; + } + lastMem = allocInfo.deviceMemory; + } + + TEST(bufInfo.size() > 2); + + // Make sure that pool has now two blocks. + VmaPoolStats poolStats = {}; + vmaGetPoolStats(g_hAllocator, pool, &poolStats); + TEST(poolStats.blockCount == 2); + + // Destroy all the buffers in random order. + while(!bufInfo.empty()) + { + const size_t indexToDestroy = rand.Generate() % bufInfo.size(); + const BufferInfo& currBufInfo = bufInfo[indexToDestroy]; + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.erase(bufInfo.begin() + indexToDestroy); + } + + // Make sure that pool has now at most one block. + vmaGetPoolStats(g_hAllocator, pool, &poolStats); + TEST(poolStats.blockCount <= 1); + } + + // Test stack. + { + // Allocate buffers until we move to a second block. + VkDeviceMemory lastMem = VK_NULL_HANDLE; + for(uint32_t i = 0; ; ++i) + { + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + if(lastMem && allocInfo.deviceMemory != lastMem) + { + break; + } + lastMem = allocInfo.deviceMemory; + } + + TEST(bufInfo.size() > 2); + + // Add few more buffers. + for(uint32_t i = 0; i < 5; ++i) + { + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + } + + // Make sure that pool has now two blocks. + VmaPoolStats poolStats = {}; + vmaGetPoolStats(g_hAllocator, pool, &poolStats); + TEST(poolStats.blockCount == 2); + + // Delete half of buffers, LIFO. + for(size_t i = 0, countToDelete = bufInfo.size() / 2; i < countToDelete; ++i) + { + const BufferInfo& currBufInfo = bufInfo.back(); + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.pop_back(); + } + + // Add one more buffer. + BufferInfo newBufInfo; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + // Make sure that pool has now one block. + vmaGetPoolStats(g_hAllocator, pool, &poolStats); + TEST(poolStats.blockCount == 1); + + // Delete all the remaining buffers, LIFO. + while(!bufInfo.empty()) + { + const BufferInfo& currBufInfo = bufInfo.back(); + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.pop_back(); + } + } + + vmaDestroyPool(g_hAllocator, pool); +} + +static void ManuallyTestLinearAllocator() +{ + VmaStats origStats; + vmaCalculateStats(g_hAllocator, &origStats); + + wprintf(L"Manually test linear allocator\n"); + + RandomNumberGenerator rand{645332}; + + VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + sampleBufCreateInfo.size = 1024; // Whatever. + sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + VmaAllocationCreateInfo sampleAllocCreateInfo = {}; + sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VmaPoolCreateInfo poolCreateInfo = {}; + VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + + poolCreateInfo.blockSize = 10 * 1024; + poolCreateInfo.flags = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT; + poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; + + VmaPool pool = nullptr; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS); + + VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = pool; + + std::vector<BufferInfo> bufInfo; + VmaAllocationInfo allocInfo; + BufferInfo newBufInfo; + + // Test double stack. + { + /* + Lower: Buffer 32 B, Buffer 1024 B, Buffer 32 B + Upper: Buffer 16 B, Buffer 1024 B, Buffer 128 B + + Totally: + 1 block allocated + 10240 Vulkan bytes + 6 new allocations + 2256 bytes in allocations + */ + + bufCreateInfo.size = 32; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + bufCreateInfo.size = 1024; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + bufCreateInfo.size = 32; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT; + + bufCreateInfo.size = 128; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + bufCreateInfo.size = 1024; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + bufCreateInfo.size = 16; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + VmaStats currStats; + vmaCalculateStats(g_hAllocator, &currStats); + VmaPoolStats poolStats; + vmaGetPoolStats(g_hAllocator, pool, &poolStats); + + char* statsStr = nullptr; + vmaBuildStatsString(g_hAllocator, &statsStr, VK_TRUE); + + // PUT BREAKPOINT HERE TO CHECK. + // Inspect: currStats versus origStats, poolStats, statsStr. + int I = 0; + + vmaFreeStatsString(g_hAllocator, statsStr); + + // Destroy the buffers in reverse order. + while(!bufInfo.empty()) + { + const BufferInfo& currBufInfo = bufInfo.back(); + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.pop_back(); + } + } + + vmaDestroyPool(g_hAllocator, pool); +} + +static void BenchmarkAlgorithmsCase(FILE* file, + uint32_t algorithm, + bool empty, + VmaAllocationCreateFlags allocStrategy, + FREE_ORDER freeOrder) +{ + RandomNumberGenerator rand{16223}; + + const VkDeviceSize bufSizeMin = 32; + const VkDeviceSize bufSizeMax = 1024; + const size_t maxBufCapacity = 10000; + const uint32_t iterationCount = 10; + + VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + sampleBufCreateInfo.size = bufSizeMax; + sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + VmaAllocationCreateInfo sampleAllocCreateInfo = {}; + sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VmaPoolCreateInfo poolCreateInfo = {}; + VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + + poolCreateInfo.blockSize = bufSizeMax * maxBufCapacity; + poolCreateInfo.flags |= algorithm; + poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; + + VmaPool pool = nullptr; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS); + + // Buffer created just to get memory requirements. Never bound to any memory. + VkBuffer dummyBuffer = VK_NULL_HANDLE; + res = vkCreateBuffer(g_hDevice, &sampleBufCreateInfo, g_Allocs, &dummyBuffer); + TEST(res == VK_SUCCESS && dummyBuffer); + + VkMemoryRequirements memReq = {}; + vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq); + + vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = pool; + allocCreateInfo.flags = allocStrategy; + + VmaAllocation alloc; + std::vector<VmaAllocation> baseAllocations; + + if(!empty) + { + // Make allocations up to 1/3 of pool size. + VkDeviceSize totalSize = 0; + while(totalSize < poolCreateInfo.blockSize / 3) + { + // This test intentionally allows sizes that are aligned to 4 or 16 bytes. + // This is theoretically allowed and already uncovered one bug. + memReq.size = bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin); + res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr); + TEST(res == VK_SUCCESS); + baseAllocations.push_back(alloc); + totalSize += memReq.size; + } + + // Delete half of them, choose randomly. + size_t allocsToDelete = baseAllocations.size() / 2; + for(size_t i = 0; i < allocsToDelete; ++i) + { + const size_t index = (size_t)rand.Generate() % baseAllocations.size(); + vmaFreeMemory(g_hAllocator, baseAllocations[index]); + baseAllocations.erase(baseAllocations.begin() + index); + } + } + + // BENCHMARK + const size_t allocCount = maxBufCapacity / 3; + std::vector<VmaAllocation> testAllocations; + testAllocations.reserve(allocCount); + duration allocTotalDuration = duration::zero(); + duration freeTotalDuration = duration::zero(); + for(uint32_t iterationIndex = 0; iterationIndex < iterationCount; ++iterationIndex) + { + // Allocations + time_point allocTimeBeg = std::chrono::high_resolution_clock::now(); + for(size_t i = 0; i < allocCount; ++i) + { + memReq.size = bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin); + res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr); + TEST(res == VK_SUCCESS); + testAllocations.push_back(alloc); + } + allocTotalDuration += std::chrono::high_resolution_clock::now() - allocTimeBeg; + + // Deallocations + switch(freeOrder) + { + case FREE_ORDER::FORWARD: + // Leave testAllocations unchanged. + break; + case FREE_ORDER::BACKWARD: + std::reverse(testAllocations.begin(), testAllocations.end()); + break; + case FREE_ORDER::RANDOM: + std::shuffle(testAllocations.begin(), testAllocations.end(), MyUniformRandomNumberGenerator(rand)); + break; + default: assert(0); + } + + time_point freeTimeBeg = std::chrono::high_resolution_clock::now(); + for(size_t i = 0; i < allocCount; ++i) + vmaFreeMemory(g_hAllocator, testAllocations[i]); + freeTotalDuration += std::chrono::high_resolution_clock::now() - freeTimeBeg; + + testAllocations.clear(); + } + + // Delete baseAllocations + while(!baseAllocations.empty()) + { + vmaFreeMemory(g_hAllocator, baseAllocations.back()); + baseAllocations.pop_back(); + } + + vmaDestroyPool(g_hAllocator, pool); + + const float allocTotalSeconds = ToFloatSeconds(allocTotalDuration); + const float freeTotalSeconds = ToFloatSeconds(freeTotalDuration); + + printf(" Algorithm=%s %s Allocation=%s FreeOrder=%s: allocations %g s, free %g s\n", + AlgorithmToStr(algorithm), + empty ? "Empty" : "Not empty", + GetAllocationStrategyName(allocStrategy), + FREE_ORDER_NAMES[(size_t)freeOrder], + allocTotalSeconds, + freeTotalSeconds); + + if(file) + { + std::string currTime; + CurrentTimeToStr(currTime); + + fprintf(file, "%s,%s,%s,%u,%s,%s,%g,%g\n", + CODE_DESCRIPTION, currTime.c_str(), + AlgorithmToStr(algorithm), + empty ? 1 : 0, + GetAllocationStrategyName(allocStrategy), + FREE_ORDER_NAMES[(uint32_t)freeOrder], + allocTotalSeconds, + freeTotalSeconds); + } +} + +static void TestBufferDeviceAddress() +{ + wprintf(L"Test buffer device address\n"); + + assert(VK_KHR_buffer_device_address_enabled); + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = 0x10000; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; // !!! + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + for(uint32_t testIndex = 0; testIndex < 2; ++testIndex) + { + // 1st is placed, 2nd is dedicated. + if(testIndex == 1) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + BufferInfo bufInfo = {}; + VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &bufInfo.Buffer, &bufInfo.Allocation, nullptr); + TEST(res == VK_SUCCESS); + + VkBufferDeviceAddressInfoEXT bufferDeviceAddressInfo = { VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_EXT }; + bufferDeviceAddressInfo.buffer = bufInfo.Buffer; + TEST(g_vkGetBufferDeviceAddressKHR != nullptr); + VkDeviceAddress addr = g_vkGetBufferDeviceAddressKHR(g_hDevice, &bufferDeviceAddressInfo); + TEST(addr != 0); + + vmaDestroyBuffer(g_hAllocator, bufInfo.Buffer, bufInfo.Allocation); + } +} + +static void TestMemoryPriority() +{ + wprintf(L"Test memory priority\n"); + + assert(VK_EXT_memory_priority_enabled); + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = 0x10000; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + allocCreateInfo.priority = 1.f; + + for(uint32_t testIndex = 0; testIndex < 2; ++testIndex) + { + // 1st is placed, 2nd is dedicated. + if(testIndex == 1) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + BufferInfo bufInfo = {}; + VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &bufInfo.Buffer, &bufInfo.Allocation, nullptr); + TEST(res == VK_SUCCESS); + + // There is nothing we can do to validate the priority. + + vmaDestroyBuffer(g_hAllocator, bufInfo.Buffer, bufInfo.Allocation); + } +} + +static void BenchmarkAlgorithms(FILE* file) +{ + wprintf(L"Benchmark algorithms\n"); + + if(file) + { + fprintf(file, + "Code,Time," + "Algorithm,Empty,Allocation strategy,Free order," + "Allocation time (s),Deallocation time (s)\n"); + } + + uint32_t freeOrderCount = 1; + if(ConfigType >= CONFIG_TYPE::CONFIG_TYPE_LARGE) + freeOrderCount = 3; + else if(ConfigType >= CONFIG_TYPE::CONFIG_TYPE_SMALL) + freeOrderCount = 2; + + const uint32_t emptyCount = ConfigType >= CONFIG_TYPE::CONFIG_TYPE_SMALL ? 2 : 1; + const uint32_t allocStrategyCount = GetAllocationStrategyCount(); + + for(uint32_t freeOrderIndex = 0; freeOrderIndex < freeOrderCount; ++freeOrderIndex) + { + FREE_ORDER freeOrder = FREE_ORDER::COUNT; + switch(freeOrderIndex) + { + case 0: freeOrder = FREE_ORDER::BACKWARD; break; + case 1: freeOrder = FREE_ORDER::FORWARD; break; + case 2: freeOrder = FREE_ORDER::RANDOM; break; + default: assert(0); + } + + for(uint32_t emptyIndex = 0; emptyIndex < emptyCount; ++emptyIndex) + { + for(uint32_t algorithmIndex = 0; algorithmIndex < 3; ++algorithmIndex) + { + uint32_t algorithm = 0; + switch(algorithmIndex) + { + case 0: + break; + case 1: + algorithm = VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT; + break; + case 2: + algorithm = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT; + break; + default: + assert(0); + } + + uint32_t currAllocStrategyCount = algorithm != 0 ? 1 : allocStrategyCount; + for(uint32_t allocStrategyIndex = 0; allocStrategyIndex < currAllocStrategyCount; ++allocStrategyIndex) + { + VmaAllocatorCreateFlags strategy = 0; + if(currAllocStrategyCount > 1) + { + switch(allocStrategyIndex) + { + case 0: strategy = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT; break; + case 1: strategy = VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT; break; + case 2: strategy = VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT; break; + default: assert(0); + } + } + + BenchmarkAlgorithmsCase( + file, + algorithm, + (emptyIndex == 0), // empty + strategy, + freeOrder); // freeOrder + } + } + } + } +} + +static void TestPool_SameSize() +{ + const VkDeviceSize BUF_SIZE = 1024 * 1024; + const size_t BUF_COUNT = 100; + VkResult res; + + RandomNumberGenerator rand{123}; + + VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferInfo.size = BUF_SIZE; + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + uint32_t memoryTypeBits = UINT32_MAX; + { + VkBuffer dummyBuffer; + res = vkCreateBuffer(g_hDevice, &bufferInfo, g_Allocs, &dummyBuffer); + TEST(res == VK_SUCCESS); + + VkMemoryRequirements memReq; + vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq); + memoryTypeBits = memReq.memoryTypeBits; + + vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs); + } + + VmaAllocationCreateInfo poolAllocInfo = {}; + poolAllocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + uint32_t memTypeIndex; + res = vmaFindMemoryTypeIndex( + g_hAllocator, + memoryTypeBits, + &poolAllocInfo, + &memTypeIndex); + + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.memoryTypeIndex = memTypeIndex; + poolCreateInfo.blockSize = BUF_SIZE * BUF_COUNT / 4; + poolCreateInfo.minBlockCount = 1; + poolCreateInfo.maxBlockCount = 4; + poolCreateInfo.frameInUseCount = 0; + + VmaPool pool; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS); + + // Test pool name + { + static const char* const POOL_NAME = "Pool name"; + vmaSetPoolName(g_hAllocator, pool, POOL_NAME); + + const char* fetchedPoolName = nullptr; + vmaGetPoolName(g_hAllocator, pool, &fetchedPoolName); + TEST(strcmp(fetchedPoolName, POOL_NAME) == 0); + + vmaSetPoolName(g_hAllocator, pool, nullptr); + } + + vmaSetCurrentFrameIndex(g_hAllocator, 1); + + VmaAllocationCreateInfo allocInfo = {}; + allocInfo.pool = pool; + allocInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT | + VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; + + struct BufItem + { + VkBuffer Buf; + VmaAllocation Alloc; + }; + std::vector<BufItem> items; + + // Fill entire pool. + for(size_t i = 0; i < BUF_COUNT; ++i) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + TEST(res == VK_SUCCESS); + items.push_back(item); + } + + // Make sure that another allocation would fail. + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY); + } + + // Validate that no buffer is lost. Also check that they are not mapped. + for(size_t i = 0; i < items.size(); ++i) + { + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); + TEST(allocInfo.deviceMemory != VK_NULL_HANDLE); + TEST(allocInfo.pMappedData == nullptr); + } + + // Free some percent of random items. + { + const size_t PERCENT_TO_FREE = 10; + size_t itemsToFree = items.size() * PERCENT_TO_FREE / 100; + for(size_t i = 0; i < itemsToFree; ++i) + { + size_t index = (size_t)rand.Generate() % items.size(); + vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc); + items.erase(items.begin() + index); + } + } + + // Randomly allocate and free items. + { + const size_t OPERATION_COUNT = BUF_COUNT; + for(size_t i = 0; i < OPERATION_COUNT; ++i) + { + bool allocate = rand.Generate() % 2 != 0; + if(allocate) + { + if(items.size() < BUF_COUNT) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + TEST(res == VK_SUCCESS); + items.push_back(item); + } + } + else // Free + { + if(!items.empty()) + { + size_t index = (size_t)rand.Generate() % items.size(); + vmaDestroyBuffer(g_hAllocator, items[index].Buf, items[index].Alloc); + items.erase(items.begin() + index); + } + } + } + } + + // Allocate up to maximum. + while(items.size() < BUF_COUNT) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + TEST(res == VK_SUCCESS); + items.push_back(item); + } + + // Validate that no buffer is lost. + for(size_t i = 0; i < items.size(); ++i) + { + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); + TEST(allocInfo.deviceMemory != VK_NULL_HANDLE); + } + + // Next frame. + vmaSetCurrentFrameIndex(g_hAllocator, 2); + + // Allocate another BUF_COUNT buffers. + for(size_t i = 0; i < BUF_COUNT; ++i) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + TEST(res == VK_SUCCESS); + items.push_back(item); + } + + // Make sure the first BUF_COUNT is lost. Delete them. + for(size_t i = 0; i < BUF_COUNT; ++i) + { + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); + TEST(allocInfo.deviceMemory == VK_NULL_HANDLE); + vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); + } + items.erase(items.begin(), items.begin() + BUF_COUNT); + + // Validate that no buffer is lost. + for(size_t i = 0; i < items.size(); ++i) + { + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); + TEST(allocInfo.deviceMemory != VK_NULL_HANDLE); + } + + // Free one item. + vmaDestroyBuffer(g_hAllocator, items.back().Buf, items.back().Alloc); + items.pop_back(); + + // Validate statistics. + { + VmaPoolStats poolStats = {}; + vmaGetPoolStats(g_hAllocator, pool, &poolStats); + TEST(poolStats.allocationCount == items.size()); + TEST(poolStats.size = BUF_COUNT * BUF_SIZE); + TEST(poolStats.unusedRangeCount == 1); + TEST(poolStats.unusedRangeSizeMax == BUF_SIZE); + TEST(poolStats.unusedSize == BUF_SIZE); + } + + // Free all remaining items. + for(size_t i = items.size(); i--; ) + vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); + items.clear(); + + // Allocate maximum items again. + for(size_t i = 0; i < BUF_COUNT; ++i) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + TEST(res == VK_SUCCESS); + items.push_back(item); + } + + // Delete every other item. + for(size_t i = 0; i < BUF_COUNT / 2; ++i) + { + vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); + items.erase(items.begin() + i); + } + + // Defragment! + { + std::vector<VmaAllocation> allocationsToDefragment(items.size()); + for(size_t i = 0; i < items.size(); ++i) + allocationsToDefragment[i] = items[i].Alloc; + + VmaDefragmentationStats defragmentationStats; + res = vmaDefragment(g_hAllocator, allocationsToDefragment.data(), items.size(), nullptr, nullptr, &defragmentationStats); + TEST(res == VK_SUCCESS); + TEST(defragmentationStats.deviceMemoryBlocksFreed == 2); + } + + // Free all remaining items. + for(size_t i = items.size(); i--; ) + vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); + items.clear(); + + //////////////////////////////////////////////////////////////////////////////// + // Test for vmaMakePoolAllocationsLost + + // Allocate 4 buffers on frame 10. + vmaSetCurrentFrameIndex(g_hAllocator, 10); + for(size_t i = 0; i < 4; ++i) + { + BufItem item; + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocInfo, &item.Buf, &item.Alloc, nullptr); + TEST(res == VK_SUCCESS); + items.push_back(item); + } + + // Touch first 2 of them on frame 11. + vmaSetCurrentFrameIndex(g_hAllocator, 11); + for(size_t i = 0; i < 2; ++i) + { + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, items[i].Alloc, &allocInfo); + } + + // vmaMakePoolAllocationsLost. Only remaining 2 should be lost. + size_t lostCount = 0xDEADC0DE; + vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount); + TEST(lostCount == 2); + + // Make another call. Now 0 should be lost. + vmaMakePoolAllocationsLost(g_hAllocator, pool, &lostCount); + TEST(lostCount == 0); + + // Make another call, with null count. Should not crash. + vmaMakePoolAllocationsLost(g_hAllocator, pool, nullptr); + + // END: Free all remaining items. + for(size_t i = items.size(); i--; ) + vmaDestroyBuffer(g_hAllocator, items[i].Buf, items[i].Alloc); + + items.clear(); + + //////////////////////////////////////////////////////////////////////////////// + // Test for allocation too large for pool + + { + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = pool; + + VkMemoryRequirements memReq; + memReq.memoryTypeBits = UINT32_MAX; + memReq.alignment = 1; + memReq.size = poolCreateInfo.blockSize + 4; + + VmaAllocation alloc = nullptr; + res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, &alloc, nullptr); + TEST(res == VK_ERROR_OUT_OF_DEVICE_MEMORY && alloc == nullptr); + } + + vmaDestroyPool(g_hAllocator, pool); +} + +static bool ValidatePattern(const void* pMemory, size_t size, uint8_t pattern) +{ + const uint8_t* pBytes = (const uint8_t*)pMemory; + for(size_t i = 0; i < size; ++i) + { + if(pBytes[i] != pattern) + { + return false; + } + } + return true; +} + +static void TestAllocationsInitialization() +{ + VkResult res; + + const size_t BUF_SIZE = 1024; + + // Create pool. + + VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufInfo.size = BUF_SIZE; + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo dummyBufAllocCreateInfo = {}; + dummyBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.blockSize = BUF_SIZE * 10; + poolCreateInfo.minBlockCount = 1; // To keep memory alive while pool exists. + poolCreateInfo.maxBlockCount = 1; + res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufInfo, &dummyBufAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + + VmaAllocationCreateInfo bufAllocCreateInfo = {}; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &bufAllocCreateInfo.pool); + TEST(res == VK_SUCCESS); + + // Create one persistently mapped buffer to keep memory of this block mapped, + // so that pointer to mapped data will remain (more or less...) valid even + // after destruction of other allocations. + + bufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + VkBuffer firstBuf; + VmaAllocation firstAlloc; + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &firstBuf, &firstAlloc, nullptr); + TEST(res == VK_SUCCESS); + + // Test buffers. + + for(uint32_t i = 0; i < 2; ++i) + { + const bool persistentlyMapped = i == 0; + bufAllocCreateInfo.flags = persistentlyMapped ? VMA_ALLOCATION_CREATE_MAPPED_BIT : 0; + VkBuffer buf; + VmaAllocation alloc; + VmaAllocationInfo allocInfo; + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &bufAllocCreateInfo, &buf, &alloc, &allocInfo); + TEST(res == VK_SUCCESS); + + void* pMappedData; + if(!persistentlyMapped) + { + res = vmaMapMemory(g_hAllocator, alloc, &pMappedData); + TEST(res == VK_SUCCESS); + } + else + { + pMappedData = allocInfo.pMappedData; + } + + // Validate initialized content + bool valid = ValidatePattern(pMappedData, BUF_SIZE, 0xDC); + TEST(valid); + + if(!persistentlyMapped) + { + vmaUnmapMemory(g_hAllocator, alloc); + } + + vmaDestroyBuffer(g_hAllocator, buf, alloc); + + // Validate freed content + valid = ValidatePattern(pMappedData, BUF_SIZE, 0xEF); + TEST(valid); + } + + vmaDestroyBuffer(g_hAllocator, firstBuf, firstAlloc); + vmaDestroyPool(g_hAllocator, bufAllocCreateInfo.pool); +} + +static void TestPool_Benchmark( + PoolTestResult& outResult, + const PoolTestConfig& config) +{ + TEST(config.ThreadCount > 0); + + RandomNumberGenerator mainRand{config.RandSeed}; + + uint32_t allocationSizeProbabilitySum = std::accumulate( + config.AllocationSizes.begin(), + config.AllocationSizes.end(), + 0u, + [](uint32_t sum, const AllocationSize& allocSize) { + return sum + allocSize.Probability; + }); + + VkBufferCreateInfo bufferTemplateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferTemplateInfo.size = 256; // Whatever. + bufferTemplateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + VkImageCreateInfo imageTemplateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imageTemplateInfo.imageType = VK_IMAGE_TYPE_2D; + imageTemplateInfo.extent.width = 256; // Whatever. + imageTemplateInfo.extent.height = 256; // Whatever. + imageTemplateInfo.extent.depth = 1; + imageTemplateInfo.mipLevels = 1; + imageTemplateInfo.arrayLayers = 1; + imageTemplateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageTemplateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // LINEAR if CPU memory. + imageTemplateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageTemplateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // TRANSFER_SRC if CPU memory. + imageTemplateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + uint32_t bufferMemoryTypeBits = UINT32_MAX; + { + VkBuffer dummyBuffer; + VkResult res = vkCreateBuffer(g_hDevice, &bufferTemplateInfo, g_Allocs, &dummyBuffer); + TEST(res == VK_SUCCESS); + + VkMemoryRequirements memReq; + vkGetBufferMemoryRequirements(g_hDevice, dummyBuffer, &memReq); + bufferMemoryTypeBits = memReq.memoryTypeBits; + + vkDestroyBuffer(g_hDevice, dummyBuffer, g_Allocs); + } + + uint32_t imageMemoryTypeBits = UINT32_MAX; + { + VkImage dummyImage; + VkResult res = vkCreateImage(g_hDevice, &imageTemplateInfo, g_Allocs, &dummyImage); + TEST(res == VK_SUCCESS); + + VkMemoryRequirements memReq; + vkGetImageMemoryRequirements(g_hDevice, dummyImage, &memReq); + imageMemoryTypeBits = memReq.memoryTypeBits; + + vkDestroyImage(g_hDevice, dummyImage, g_Allocs); + } + + uint32_t memoryTypeBits = 0; + if(config.UsesBuffers() && config.UsesImages()) + { + memoryTypeBits = bufferMemoryTypeBits & imageMemoryTypeBits; + if(memoryTypeBits == 0) + { + PrintWarning(L"Cannot test buffers + images in the same memory pool on this GPU."); + return; + } + } + else if(config.UsesBuffers()) + memoryTypeBits = bufferMemoryTypeBits; + else if(config.UsesImages()) + memoryTypeBits = imageMemoryTypeBits; + else + TEST(0); + + VmaPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.minBlockCount = 1; + poolCreateInfo.maxBlockCount = 1; + poolCreateInfo.blockSize = config.PoolSize; + poolCreateInfo.frameInUseCount = 1; + + const VkPhysicalDeviceMemoryProperties* memProps = nullptr; + vmaGetMemoryProperties(g_hAllocator, &memProps); + + VmaPool pool = VK_NULL_HANDLE; + VkResult res; + // Loop over memory types because we sometimes allocate a big block here, + // while the most eligible DEVICE_LOCAL heap may be only 256 MB on some GPUs. + while(memoryTypeBits) + { + VmaAllocationCreateInfo dummyAllocCreateInfo = {}; + dummyAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + vmaFindMemoryTypeIndex(g_hAllocator, memoryTypeBits, &dummyAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); + + const uint32_t heapIndex = memProps->memoryTypes[poolCreateInfo.memoryTypeIndex].heapIndex; + // Protection against validation layer error when trying to allocate a block larger than entire heap size, + // which may be only 256 MB on some platforms. + if(poolCreateInfo.blockSize * poolCreateInfo.minBlockCount < memProps->memoryHeaps[heapIndex].size) + { + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + if(res == VK_SUCCESS) + break; + } + memoryTypeBits &= ~(1u << poolCreateInfo.memoryTypeIndex); + } + TEST(pool); + + // Start time measurement - after creating pool and initializing data structures. + time_point timeBeg = std::chrono::high_resolution_clock::now(); + + //////////////////////////////////////////////////////////////////////////////// + // ThreadProc + auto ThreadProc = [&config, allocationSizeProbabilitySum, pool]( + PoolTestThreadResult* outThreadResult, + uint32_t randSeed, + HANDLE frameStartEvent, + HANDLE frameEndEvent) -> void + { + RandomNumberGenerator threadRand{randSeed}; + VkResult res = VK_SUCCESS; + + VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferInfo.size = 256; // Whatever. + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = 256; // Whatever. + imageInfo.extent.height = 256; // Whatever. + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; // LINEAR if CPU memory. + imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // TRANSFER_SRC if CPU memory. + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + outThreadResult->AllocationTimeMin = duration::max(); + outThreadResult->AllocationTimeSum = duration::zero(); + outThreadResult->AllocationTimeMax = duration::min(); + outThreadResult->DeallocationTimeMin = duration::max(); + outThreadResult->DeallocationTimeSum = duration::zero(); + outThreadResult->DeallocationTimeMax = duration::min(); + outThreadResult->AllocationCount = 0; + outThreadResult->DeallocationCount = 0; + outThreadResult->LostAllocationCount = 0; + outThreadResult->LostAllocationTotalSize = 0; + outThreadResult->FailedAllocationCount = 0; + outThreadResult->FailedAllocationTotalSize = 0; + + struct Item + { + VkDeviceSize BufferSize = 0; + VkExtent2D ImageSize = { 0, 0 }; + VkBuffer Buf = VK_NULL_HANDLE; + VkImage Image = VK_NULL_HANDLE; + VmaAllocation Alloc = VK_NULL_HANDLE; + + Item() { } + Item(Item&& src) : + BufferSize(src.BufferSize), ImageSize(src.ImageSize), Buf(src.Buf), Image(src.Image), Alloc(src.Alloc) + { + src.BufferSize = 0; + src.ImageSize = {0, 0}; + src.Buf = VK_NULL_HANDLE; + src.Image = VK_NULL_HANDLE; + src.Alloc = VK_NULL_HANDLE; + } + Item(const Item& src) = delete; + ~Item() + { + DestroyResources(); + } + Item& operator=(Item&& src) + { + if(&src != this) + { + DestroyResources(); + BufferSize = src.BufferSize; ImageSize = src.ImageSize; + Buf = src.Buf; Image = src.Image; Alloc = src.Alloc; + src.BufferSize = 0; + src.ImageSize = {0, 0}; + src.Buf = VK_NULL_HANDLE; + src.Image = VK_NULL_HANDLE; + src.Alloc = VK_NULL_HANDLE; + } + return *this; + } + Item& operator=(const Item& src) = delete; + void DestroyResources() + { + if(Buf) + { + assert(Image == VK_NULL_HANDLE); + vmaDestroyBuffer(g_hAllocator, Buf, Alloc); + Buf = VK_NULL_HANDLE; + } + else + { + vmaDestroyImage(g_hAllocator, Image, Alloc); + Image = VK_NULL_HANDLE; + } + Alloc = VK_NULL_HANDLE; + } + VkDeviceSize CalcSizeBytes() const + { + return BufferSize + + 4ull * ImageSize.width * ImageSize.height; + } + }; + std::vector<Item> unusedItems, usedItems; + + const size_t threadTotalItemCount = config.TotalItemCount / config.ThreadCount; + + // Create all items - all unused, not yet allocated. + for(size_t i = 0; i < threadTotalItemCount; ++i) + { + Item item = {}; + + uint32_t allocSizeIndex = 0; + uint32_t r = threadRand.Generate() % allocationSizeProbabilitySum; + while(r >= config.AllocationSizes[allocSizeIndex].Probability) + r -= config.AllocationSizes[allocSizeIndex++].Probability; + + const AllocationSize& allocSize = config.AllocationSizes[allocSizeIndex]; + if(allocSize.BufferSizeMax > 0) + { + TEST(allocSize.BufferSizeMin > 0); + TEST(allocSize.ImageSizeMin == 0 && allocSize.ImageSizeMax == 0); + if(allocSize.BufferSizeMax == allocSize.BufferSizeMin) + item.BufferSize = allocSize.BufferSizeMin; + else + { + item.BufferSize = allocSize.BufferSizeMin + threadRand.Generate() % (allocSize.BufferSizeMax - allocSize.BufferSizeMin); + item.BufferSize = item.BufferSize / 16 * 16; + } + } + else + { + TEST(allocSize.ImageSizeMin > 0 && allocSize.ImageSizeMax > 0); + if(allocSize.ImageSizeMax == allocSize.ImageSizeMin) + item.ImageSize.width = item.ImageSize.height = allocSize.ImageSizeMax; + else + { + item.ImageSize.width = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); + item.ImageSize.height = allocSize.ImageSizeMin + threadRand.Generate() % (allocSize.ImageSizeMax - allocSize.ImageSizeMin); + } + } + + unusedItems.push_back(std::move(item)); + } + + auto Allocate = [&](Item& item) -> VkResult + { + assert(item.Buf == VK_NULL_HANDLE && item.Image == VK_NULL_HANDLE && item.Alloc == VK_NULL_HANDLE); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = pool; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT | + VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; + + if(item.BufferSize) + { + bufferInfo.size = item.BufferSize; + VkResult res = VK_SUCCESS; + { + PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult); + res = vmaCreateBuffer(g_hAllocator, &bufferInfo, &allocCreateInfo, &item.Buf, &item.Alloc, nullptr); + } + if(res == VK_SUCCESS) + SetDebugUtilsObjectName(VK_OBJECT_TYPE_BUFFER, (uint64_t)item.Buf, "TestPool_Benchmark_Buffer"); + return res; + } + else + { + TEST(item.ImageSize.width && item.ImageSize.height); + + imageInfo.extent.width = item.ImageSize.width; + imageInfo.extent.height = item.ImageSize.height; + VkResult res = VK_SUCCESS; + { + PoolAllocationTimeRegisterObj timeRegisterObj(*outThreadResult); + res = vmaCreateImage(g_hAllocator, &imageInfo, &allocCreateInfo, &item.Image, &item.Alloc, nullptr); + } + if(res == VK_SUCCESS) + SetDebugUtilsObjectName(VK_OBJECT_TYPE_IMAGE, (uint64_t)item.Image, "TestPool_Benchmark_Image"); + return res; + } + }; + + //////////////////////////////////////////////////////////////////////////////// + // Frames + for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex) + { + WaitForSingleObject(frameStartEvent, INFINITE); + + // Always make some percent of used bufs unused, to choose different used ones. + const size_t bufsToMakeUnused = usedItems.size() * config.ItemsToMakeUnusedPercent / 100; + for(size_t i = 0; i < bufsToMakeUnused; ++i) + { + size_t index = threadRand.Generate() % usedItems.size(); + auto it = usedItems.begin() + index; + Item item = std::move(*it); + usedItems.erase(it); + unusedItems.push_back(std::move(item)); + } + + // Determine which bufs we want to use in this frame. + const size_t usedBufCount = (threadRand.Generate() % (config.UsedItemCountMax - config.UsedItemCountMin) + config.UsedItemCountMin) + / config.ThreadCount; + TEST(usedBufCount < usedItems.size() + unusedItems.size()); + // Move some used to unused. + while(usedBufCount < usedItems.size()) + { + size_t index = threadRand.Generate() % usedItems.size(); + auto it = usedItems.begin() + index; + Item item = std::move(*it); + usedItems.erase(it); + unusedItems.push_back(std::move(item)); + } + // Move some unused to used. + while(usedBufCount > usedItems.size()) + { + size_t index = threadRand.Generate() % unusedItems.size(); + auto it = unusedItems.begin() + index; + Item item = std::move(*it); + unusedItems.erase(it); + usedItems.push_back(std::move(item)); + } + + uint32_t touchExistingCount = 0; + uint32_t touchLostCount = 0; + uint32_t createSucceededCount = 0; + uint32_t createFailedCount = 0; + + // Touch all used bufs. If not created or lost, allocate. + for(size_t i = 0; i < usedItems.size(); ++i) + { + Item& item = usedItems[i]; + // Not yet created. + if(item.Alloc == VK_NULL_HANDLE) + { + res = Allocate(item); + ++outThreadResult->AllocationCount; + if(res != VK_SUCCESS) + { + assert(item.Alloc == VK_NULL_HANDLE && item.Buf == VK_NULL_HANDLE && item.Image == VK_NULL_HANDLE); + ++outThreadResult->FailedAllocationCount; + outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes(); + ++createFailedCount; + } + else + ++createSucceededCount; + } + else + { + // Touch. + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, item.Alloc, &allocInfo); + // Lost. + if(allocInfo.deviceMemory == VK_NULL_HANDLE) + { + ++touchLostCount; + + // Destroy. + { + PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult); + item.DestroyResources(); + ++outThreadResult->DeallocationCount; + } + + ++outThreadResult->LostAllocationCount; + outThreadResult->LostAllocationTotalSize += item.CalcSizeBytes(); + + // Recreate. + res = Allocate(item); + ++outThreadResult->AllocationCount; + // Creation failed. + if(res != VK_SUCCESS) + { + TEST(item.Alloc == VK_NULL_HANDLE && item.Buf == VK_NULL_HANDLE && item.Image == VK_NULL_HANDLE); + ++outThreadResult->FailedAllocationCount; + outThreadResult->FailedAllocationTotalSize += item.CalcSizeBytes(); + ++createFailedCount; + } + else + ++createSucceededCount; + } + else + ++touchExistingCount; + } + } + + /* + printf("Thread %u frame %u: Touch existing %u lost %u, create succeeded %u failed %u\n", + randSeed, frameIndex, + touchExistingCount, touchLostCount, + createSucceededCount, createFailedCount); + */ + + SetEvent(frameEndEvent); + } + + // Free all remaining items. + for(size_t i = usedItems.size(); i--; ) + { + PoolDeallocationTimeRegisterObj timeRegisterObj(*outThreadResult); + usedItems[i].DestroyResources(); + ++outThreadResult->DeallocationCount; + } + for(size_t i = unusedItems.size(); i--; ) + { + PoolDeallocationTimeRegisterObj timeRegisterOb(*outThreadResult); + unusedItems[i].DestroyResources(); + ++outThreadResult->DeallocationCount; + } + }; + + // Launch threads. + uint32_t threadRandSeed = mainRand.Generate(); + std::vector<HANDLE> frameStartEvents{config.ThreadCount}; + std::vector<HANDLE> frameEndEvents{config.ThreadCount}; + std::vector<std::thread> bkgThreads; + std::vector<PoolTestThreadResult> threadResults{config.ThreadCount}; + for(uint32_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex) + { + frameStartEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL); + frameEndEvents[threadIndex] = CreateEvent(NULL, FALSE, FALSE, NULL); + bkgThreads.emplace_back(std::bind( + ThreadProc, + &threadResults[threadIndex], + threadRandSeed + threadIndex, + frameStartEvents[threadIndex], + frameEndEvents[threadIndex])); + } + + // Execute frames. + TEST(config.ThreadCount <= MAXIMUM_WAIT_OBJECTS); + for(uint32_t frameIndex = 0; frameIndex < config.FrameCount; ++frameIndex) + { + vmaSetCurrentFrameIndex(g_hAllocator, frameIndex); + for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex) + SetEvent(frameStartEvents[threadIndex]); + WaitForMultipleObjects(config.ThreadCount, &frameEndEvents[0], TRUE, INFINITE); + } + + // Wait for threads finished + for(size_t i = 0; i < bkgThreads.size(); ++i) + { + bkgThreads[i].join(); + CloseHandle(frameEndEvents[i]); + CloseHandle(frameStartEvents[i]); + } + bkgThreads.clear(); + + // Finish time measurement - before destroying pool. + outResult.TotalTime = std::chrono::high_resolution_clock::now() - timeBeg; + + vmaDestroyPool(g_hAllocator, pool); + + outResult.AllocationTimeMin = duration::max(); + outResult.AllocationTimeAvg = duration::zero(); + outResult.AllocationTimeMax = duration::min(); + outResult.DeallocationTimeMin = duration::max(); + outResult.DeallocationTimeAvg = duration::zero(); + outResult.DeallocationTimeMax = duration::min(); + outResult.LostAllocationCount = 0; + outResult.LostAllocationTotalSize = 0; + outResult.FailedAllocationCount = 0; + outResult.FailedAllocationTotalSize = 0; + size_t allocationCount = 0; + size_t deallocationCount = 0; + for(size_t threadIndex = 0; threadIndex < config.ThreadCount; ++threadIndex) + { + const PoolTestThreadResult& threadResult = threadResults[threadIndex]; + outResult.AllocationTimeMin = std::min(outResult.AllocationTimeMin, threadResult.AllocationTimeMin); + outResult.AllocationTimeMax = std::max(outResult.AllocationTimeMax, threadResult.AllocationTimeMax); + outResult.AllocationTimeAvg += threadResult.AllocationTimeSum; + outResult.DeallocationTimeMin = std::min(outResult.DeallocationTimeMin, threadResult.DeallocationTimeMin); + outResult.DeallocationTimeMax = std::max(outResult.DeallocationTimeMax, threadResult.DeallocationTimeMax); + outResult.DeallocationTimeAvg += threadResult.DeallocationTimeSum; + allocationCount += threadResult.AllocationCount; + deallocationCount += threadResult.DeallocationCount; + outResult.FailedAllocationCount += threadResult.FailedAllocationCount; + outResult.FailedAllocationTotalSize += threadResult.FailedAllocationTotalSize; + outResult.LostAllocationCount += threadResult.LostAllocationCount; + outResult.LostAllocationTotalSize += threadResult.LostAllocationTotalSize; + } + if(allocationCount) + outResult.AllocationTimeAvg /= allocationCount; + if(deallocationCount) + outResult.DeallocationTimeAvg /= deallocationCount; +} + +static inline bool MemoryRegionsOverlap(char* ptr1, size_t size1, char* ptr2, size_t size2) +{ + if(ptr1 < ptr2) + return ptr1 + size1 > ptr2; + else if(ptr2 < ptr1) + return ptr2 + size2 > ptr1; + else + return true; +} + +static void TestMemoryUsage() +{ + wprintf(L"Testing memory usage:\n"); + + static const VmaMemoryUsage lastUsage = VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED; + for(uint32_t usage = 0; usage <= lastUsage; ++usage) + { + switch(usage) + { + case VMA_MEMORY_USAGE_UNKNOWN: printf(" VMA_MEMORY_USAGE_UNKNOWN:\n"); break; + case VMA_MEMORY_USAGE_GPU_ONLY: printf(" VMA_MEMORY_USAGE_GPU_ONLY:\n"); break; + case VMA_MEMORY_USAGE_CPU_ONLY: printf(" VMA_MEMORY_USAGE_CPU_ONLY:\n"); break; + case VMA_MEMORY_USAGE_CPU_TO_GPU: printf(" VMA_MEMORY_USAGE_CPU_TO_GPU:\n"); break; + case VMA_MEMORY_USAGE_GPU_TO_CPU: printf(" VMA_MEMORY_USAGE_GPU_TO_CPU:\n"); break; + case VMA_MEMORY_USAGE_CPU_COPY: printf(" VMA_MEMORY_USAGE_CPU_COPY:\n"); break; + case VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED: printf(" VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED:\n"); break; + default: assert(0); + } + + auto printResult = [](const char* testName, VkResult res, uint32_t memoryTypeBits, uint32_t memoryTypeIndex) + { + if(res == VK_SUCCESS) + printf(" %s: memoryTypeBits=0x%X, memoryTypeIndex=%u\n", testName, memoryTypeBits, memoryTypeIndex); + else + printf(" %s: memoryTypeBits=0x%X, FAILED with res=%d\n", testName, memoryTypeBits, (int32_t)res); + }; + + // 1: Buffer for copy + { + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = 65536; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VkBuffer buf = VK_NULL_HANDLE; + VkResult res = vkCreateBuffer(g_hDevice, &bufCreateInfo, g_Allocs, &buf); + TEST(res == VK_SUCCESS && buf != VK_NULL_HANDLE); + + VkMemoryRequirements memReq = {}; + vkGetBufferMemoryRequirements(g_hDevice, buf, &memReq); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = (VmaMemoryUsage)usage; + VmaAllocation alloc = VK_NULL_HANDLE; + VmaAllocationInfo allocInfo = {}; + res = vmaAllocateMemoryForBuffer(g_hAllocator, buf, &allocCreateInfo, &alloc, &allocInfo); + if(res == VK_SUCCESS) + { + TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0); + res = vkBindBufferMemory(g_hDevice, buf, allocInfo.deviceMemory, allocInfo.offset); + TEST(res == VK_SUCCESS); + } + printResult("Buffer TRANSFER_DST + TRANSFER_SRC", res, memReq.memoryTypeBits, allocInfo.memoryType); + vmaDestroyBuffer(g_hAllocator, buf, alloc); + } + + // 2: Vertex buffer + { + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = 65536; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + VkBuffer buf = VK_NULL_HANDLE; + VkResult res = vkCreateBuffer(g_hDevice, &bufCreateInfo, g_Allocs, &buf); + TEST(res == VK_SUCCESS && buf != VK_NULL_HANDLE); + + VkMemoryRequirements memReq = {}; + vkGetBufferMemoryRequirements(g_hDevice, buf, &memReq); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = (VmaMemoryUsage)usage; + VmaAllocation alloc = VK_NULL_HANDLE; + VmaAllocationInfo allocInfo = {}; + res = vmaAllocateMemoryForBuffer(g_hAllocator, buf, &allocCreateInfo, &alloc, &allocInfo); + if(res == VK_SUCCESS) + { + TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0); + res = vkBindBufferMemory(g_hDevice, buf, allocInfo.deviceMemory, allocInfo.offset); + TEST(res == VK_SUCCESS); + } + printResult("Buffer TRANSFER_DST + VERTEX_BUFFER", res, memReq.memoryTypeBits, allocInfo.memoryType); + vmaDestroyBuffer(g_hAllocator, buf, alloc); + } + + // 3: Image for copy, OPTIMAL + { + VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imgCreateInfo.extent.width = 256; + imgCreateInfo.extent.height = 256; + imgCreateInfo.extent.depth = 1; + imgCreateInfo.mipLevels = 1; + imgCreateInfo.arrayLayers = 1; + imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + VkImage img = VK_NULL_HANDLE; + VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img); + TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE); + + VkMemoryRequirements memReq = {}; + vkGetImageMemoryRequirements(g_hDevice, img, &memReq); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = (VmaMemoryUsage)usage; + VmaAllocation alloc = VK_NULL_HANDLE; + VmaAllocationInfo allocInfo = {}; + res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo); + if(res == VK_SUCCESS) + { + TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0); + res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset); + TEST(res == VK_SUCCESS); + } + printResult("Image OPTIMAL TRANSFER_DST + TRANSFER_SRC", res, memReq.memoryTypeBits, allocInfo.memoryType); + + vmaDestroyImage(g_hAllocator, img, alloc); + } + + // 4: Image SAMPLED, OPTIMAL + { + VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imgCreateInfo.extent.width = 256; + imgCreateInfo.extent.height = 256; + imgCreateInfo.extent.depth = 1; + imgCreateInfo.mipLevels = 1; + imgCreateInfo.arrayLayers = 1; + imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + VkImage img = VK_NULL_HANDLE; + VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img); + TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE); + + VkMemoryRequirements memReq = {}; + vkGetImageMemoryRequirements(g_hDevice, img, &memReq); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = (VmaMemoryUsage)usage; + VmaAllocation alloc = VK_NULL_HANDLE; + VmaAllocationInfo allocInfo = {}; + res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo); + if(res == VK_SUCCESS) + { + TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0); + res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset); + TEST(res == VK_SUCCESS); + } + printResult("Image OPTIMAL TRANSFER_DST + SAMPLED", res, memReq.memoryTypeBits, allocInfo.memoryType); + vmaDestroyImage(g_hAllocator, img, alloc); + } + + // 5: Image COLOR_ATTACHMENT, OPTIMAL + { + VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imgCreateInfo.extent.width = 256; + imgCreateInfo.extent.height = 256; + imgCreateInfo.extent.depth = 1; + imgCreateInfo.mipLevels = 1; + imgCreateInfo.arrayLayers = 1; + imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imgCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + VkImage img = VK_NULL_HANDLE; + VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img); + TEST(res == VK_SUCCESS && img != VK_NULL_HANDLE); + + VkMemoryRequirements memReq = {}; + vkGetImageMemoryRequirements(g_hDevice, img, &memReq); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = (VmaMemoryUsage)usage; + VmaAllocation alloc = VK_NULL_HANDLE; + VmaAllocationInfo allocInfo = {}; + res = vmaAllocateMemoryForImage(g_hAllocator, img, &allocCreateInfo, &alloc, &allocInfo); + if(res == VK_SUCCESS) + { + TEST((memReq.memoryTypeBits & (1u << allocInfo.memoryType)) != 0); + res = vkBindImageMemory(g_hDevice, img, allocInfo.deviceMemory, allocInfo.offset); + TEST(res == VK_SUCCESS); + } + printResult("Image OPTIMAL SAMPLED + COLOR_ATTACHMENT", res, memReq.memoryTypeBits, allocInfo.memoryType); + vmaDestroyImage(g_hAllocator, img, alloc); + } + } +} + +static uint32_t FindDeviceCoherentMemoryTypeBits() +{ + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(g_hPhysicalDevice, &memProps); + + uint32_t memTypeBits = 0; + for(uint32_t i = 0; i < memProps.memoryTypeCount; ++i) + { + if(memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD) + memTypeBits |= 1u << i; + } + return memTypeBits; +} + +static void TestDeviceCoherentMemory() +{ + if(!VK_AMD_device_coherent_memory_enabled) + return; + + uint32_t deviceCoherentMemoryTypeBits = FindDeviceCoherentMemoryTypeBits(); + // Extension is enabled, feature is enabled, and the device still doesn't support any such memory type? + // OK then, so it's just fake! + if(deviceCoherentMemoryTypeBits == 0) + return; + + wprintf(L"Testing device coherent memory...\n"); + + // 1. Try to allocate buffer from a memory type that is DEVICE_COHERENT. + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = 0x10000; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD; + + AllocInfo alloc = {}; + VmaAllocationInfo allocInfo = {}; + VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &alloc.m_Buffer, &alloc.m_Allocation, &allocInfo); + + // Make sure it succeeded and was really created in such memory type. + TEST(res == VK_SUCCESS); + TEST((1u << allocInfo.memoryType) & deviceCoherentMemoryTypeBits); + + alloc.Destroy(); + + // 2. Try to create a pool in such memory type. + { + VmaPoolCreateInfo poolCreateInfo = {}; + + res = vmaFindMemoryTypeIndex(g_hAllocator, UINT32_MAX, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + TEST((1u << poolCreateInfo.memoryTypeIndex) & deviceCoherentMemoryTypeBits); + + VmaPool pool = VK_NULL_HANDLE; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS); + + vmaDestroyPool(g_hAllocator, pool); + } + + // 3. Try the same with a local allocator created without VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT. + + VmaAllocatorCreateInfo allocatorCreateInfo = {}; + SetAllocatorCreateInfo(allocatorCreateInfo); + allocatorCreateInfo.flags &= ~VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT; + + VmaAllocator localAllocator = VK_NULL_HANDLE; + res = vmaCreateAllocator(&allocatorCreateInfo, &localAllocator); + TEST(res == VK_SUCCESS && localAllocator); + + res = vmaCreateBuffer(localAllocator, &bufCreateInfo, &allocCreateInfo, &alloc.m_Buffer, &alloc.m_Allocation, &allocInfo); + + // Make sure it failed. + TEST(res != VK_SUCCESS && !alloc.m_Buffer && !alloc.m_Allocation); + + // 4. Try to find memory type. + { + uint32_t memTypeIndex = UINT_MAX; + res = vmaFindMemoryTypeIndex(localAllocator, UINT32_MAX, &allocCreateInfo, &memTypeIndex); + TEST(res != VK_SUCCESS); + } + + vmaDestroyAllocator(localAllocator); +} + +static void TestBudget() +{ + wprintf(L"Testing budget...\n"); + + static const VkDeviceSize BUF_SIZE = 10ull * 1024 * 1024; + static const uint32_t BUF_COUNT = 4; + + const VkPhysicalDeviceMemoryProperties* memProps = {}; + vmaGetMemoryProperties(g_hAllocator, &memProps); + + for(uint32_t testIndex = 0; testIndex < 2; ++testIndex) + { + vmaSetCurrentFrameIndex(g_hAllocator, ++g_FrameIndex); + + VmaBudget budgetBeg[VK_MAX_MEMORY_HEAPS] = {}; + vmaGetBudget(g_hAllocator, budgetBeg); + + for(uint32_t i = 0; i < memProps->memoryHeapCount; ++i) + { + TEST(budgetBeg[i].budget > 0); + TEST(budgetBeg[i].budget <= memProps->memoryHeaps[i].size); + TEST(budgetBeg[i].allocationBytes <= budgetBeg[i].blockBytes); + } + + VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufInfo.size = BUF_SIZE; + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + if(testIndex == 0) + { + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + } + + // CREATE BUFFERS + uint32_t heapIndex = 0; + BufferInfo bufInfos[BUF_COUNT] = {}; + for(uint32_t bufIndex = 0; bufIndex < BUF_COUNT; ++bufIndex) + { + VmaAllocationInfo allocInfo; + VkResult res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, + &bufInfos[bufIndex].Buffer, &bufInfos[bufIndex].Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + if(bufIndex == 0) + { + heapIndex = MemoryTypeToHeap(allocInfo.memoryType); + } + else + { + // All buffers need to fall into the same heap. + TEST(MemoryTypeToHeap(allocInfo.memoryType) == heapIndex); + } + } + + VmaBudget budgetWithBufs[VK_MAX_MEMORY_HEAPS] = {}; + vmaGetBudget(g_hAllocator, budgetWithBufs); + + // DESTROY BUFFERS + for(size_t bufIndex = BUF_COUNT; bufIndex--; ) + { + vmaDestroyBuffer(g_hAllocator, bufInfos[bufIndex].Buffer, bufInfos[bufIndex].Allocation); + } + + VmaBudget budgetEnd[VK_MAX_MEMORY_HEAPS] = {}; + vmaGetBudget(g_hAllocator, budgetEnd); + + // CHECK + for(uint32_t i = 0; i < memProps->memoryHeapCount; ++i) + { + TEST(budgetEnd[i].allocationBytes <= budgetEnd[i].blockBytes); + if(i == heapIndex) + { + TEST(budgetEnd[i].allocationBytes == budgetBeg[i].allocationBytes); + TEST(budgetWithBufs[i].allocationBytes == budgetBeg[i].allocationBytes + BUF_SIZE * BUF_COUNT); + TEST(budgetWithBufs[i].blockBytes >= budgetEnd[i].blockBytes); + } + else + { + TEST(budgetEnd[i].allocationBytes == budgetEnd[i].allocationBytes && + budgetEnd[i].allocationBytes == budgetWithBufs[i].allocationBytes); + TEST(budgetEnd[i].blockBytes == budgetEnd[i].blockBytes && + budgetEnd[i].blockBytes == budgetWithBufs[i].blockBytes); + } + } + } +} + +static void TestAliasing() +{ + wprintf(L"Testing aliasing...\n"); + + /* + This is just a simple test, more like a code sample to demonstrate it's possible. + */ + + // A 512x512 texture to be sampled. + VkImageCreateInfo img1CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + img1CreateInfo.imageType = VK_IMAGE_TYPE_2D; + img1CreateInfo.extent.width = 512; + img1CreateInfo.extent.height = 512; + img1CreateInfo.extent.depth = 1; + img1CreateInfo.mipLevels = 10; + img1CreateInfo.arrayLayers = 1; + img1CreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; + img1CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + img1CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + img1CreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + img1CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + // A full screen texture to be used as color attachment. + VkImageCreateInfo img2CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + img2CreateInfo.imageType = VK_IMAGE_TYPE_2D; + img2CreateInfo.extent.width = 1920; + img2CreateInfo.extent.height = 1080; + img2CreateInfo.extent.depth = 1; + img2CreateInfo.mipLevels = 1; + img2CreateInfo.arrayLayers = 1; + img2CreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + img2CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + img2CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + img2CreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + img2CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + VkImage img1 = VK_NULL_HANDLE; + ERR_GUARD_VULKAN(vkCreateImage(g_hDevice, &img1CreateInfo, g_Allocs, &img1)); + VkImage img2 = VK_NULL_HANDLE; + ERR_GUARD_VULKAN(vkCreateImage(g_hDevice, &img2CreateInfo, g_Allocs, &img2)); + + VkMemoryRequirements img1MemReq = {}; + vkGetImageMemoryRequirements(g_hDevice, img1, &img1MemReq); + VkMemoryRequirements img2MemReq = {}; + vkGetImageMemoryRequirements(g_hDevice, img2, &img2MemReq); + + VkMemoryRequirements finalMemReq = {}; + finalMemReq.size = std::max(img1MemReq.size, img2MemReq.size); + finalMemReq.alignment = std::max(img1MemReq.alignment, img2MemReq.alignment); + finalMemReq.memoryTypeBits = img1MemReq.memoryTypeBits & img2MemReq.memoryTypeBits; + if(finalMemReq.memoryTypeBits != 0) + { + wprintf(L" size: max(%llu, %llu) = %llu\n", + img1MemReq.size, img2MemReq.size, finalMemReq.size); + wprintf(L" alignment: max(%llu, %llu) = %llu\n", + img1MemReq.alignment, img2MemReq.alignment, finalMemReq.alignment); + wprintf(L" memoryTypeBits: %u & %u = %u\n", + img1MemReq.memoryTypeBits, img2MemReq.memoryTypeBits, finalMemReq.memoryTypeBits); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VmaAllocation alloc = VK_NULL_HANDLE; + ERR_GUARD_VULKAN(vmaAllocateMemory(g_hAllocator, &finalMemReq, &allocCreateInfo, &alloc, nullptr)); + + ERR_GUARD_VULKAN(vmaBindImageMemory(g_hAllocator, alloc, img1)); + ERR_GUARD_VULKAN(vmaBindImageMemory(g_hAllocator, alloc, img2)); + + // You can use img1, img2 here, but not at the same time! + + vmaFreeMemory(g_hAllocator, alloc); + } + else + { + wprintf(L" Textures cannot alias!\n"); + } + + vkDestroyImage(g_hDevice, img2, g_Allocs); + vkDestroyImage(g_hDevice, img1, g_Allocs); +} + +static void TestMapping() +{ + wprintf(L"Testing mapping...\n"); + + VkResult res; + uint32_t memTypeIndex = UINT32_MAX; + + enum TEST + { + TEST_NORMAL, + TEST_POOL, + TEST_DEDICATED, + TEST_COUNT + }; + for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex) + { + VmaPool pool = nullptr; + if(testIndex == TEST_POOL) + { + TEST(memTypeIndex != UINT32_MAX); + VmaPoolCreateInfo poolInfo = {}; + poolInfo.memoryTypeIndex = memTypeIndex; + res = vmaCreatePool(g_hAllocator, &poolInfo, &pool); + TEST(res == VK_SUCCESS); + } + + VkBufferCreateInfo bufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufInfo.size = 0x10000; + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.pool = pool; + if(testIndex == TEST_DEDICATED) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + VmaAllocationInfo allocInfo; + + // Mapped manually + + // Create 2 buffers. + BufferInfo bufferInfos[3]; + for(size_t i = 0; i < 2; ++i) + { + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, + &bufferInfos[i].Buffer, &bufferInfos[i].Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + TEST(allocInfo.pMappedData == nullptr); + memTypeIndex = allocInfo.memoryType; + } + + // Map buffer 0. + char* data00 = nullptr; + res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data00); + TEST(res == VK_SUCCESS && data00 != nullptr); + data00[0xFFFF] = data00[0]; + + // Map buffer 0 second time. + char* data01 = nullptr; + res = vmaMapMemory(g_hAllocator, bufferInfos[0].Allocation, (void**)&data01); + TEST(res == VK_SUCCESS && data01 == data00); + + // Map buffer 1. + char* data1 = nullptr; + res = vmaMapMemory(g_hAllocator, bufferInfos[1].Allocation, (void**)&data1); + TEST(res == VK_SUCCESS && data1 != nullptr); + TEST(!MemoryRegionsOverlap(data00, (size_t)bufInfo.size, data1, (size_t)bufInfo.size)); + data1[0xFFFF] = data1[0]; + + // Unmap buffer 0 two times. + vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation); + vmaUnmapMemory(g_hAllocator, bufferInfos[0].Allocation); + vmaGetAllocationInfo(g_hAllocator, bufferInfos[0].Allocation, &allocInfo); + TEST(allocInfo.pMappedData == nullptr); + + // Unmap buffer 1. + vmaUnmapMemory(g_hAllocator, bufferInfos[1].Allocation); + vmaGetAllocationInfo(g_hAllocator, bufferInfos[1].Allocation, &allocInfo); + TEST(allocInfo.pMappedData == nullptr); + + // Create 3rd buffer - persistently mapped. + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT; + res = vmaCreateBuffer(g_hAllocator, &bufInfo, &allocCreateInfo, + &bufferInfos[2].Buffer, &bufferInfos[2].Allocation, &allocInfo); + TEST(res == VK_SUCCESS && allocInfo.pMappedData != nullptr); + + // Map buffer 2. + char* data2 = nullptr; + res = vmaMapMemory(g_hAllocator, bufferInfos[2].Allocation, (void**)&data2); + TEST(res == VK_SUCCESS && data2 == allocInfo.pMappedData); + data2[0xFFFF] = data2[0]; + + // Unmap buffer 2. + vmaUnmapMemory(g_hAllocator, bufferInfos[2].Allocation); + vmaGetAllocationInfo(g_hAllocator, bufferInfos[2].Allocation, &allocInfo); + TEST(allocInfo.pMappedData == data2); + + // Destroy all buffers. + for(size_t i = 3; i--; ) + vmaDestroyBuffer(g_hAllocator, bufferInfos[i].Buffer, bufferInfos[i].Allocation); + + vmaDestroyPool(g_hAllocator, pool); + } +} + +// Test CREATE_MAPPED with required DEVICE_LOCAL. There was a bug with it. +static void TestDeviceLocalMapped() +{ + VkResult res; + + for(uint32_t testIndex = 0; testIndex < 3; ++testIndex) + { + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + bufCreateInfo.size = 4096; + + VmaPool pool = VK_NULL_HANDLE; + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + if(testIndex == 2) + { + VmaPoolCreateInfo poolCreateInfo = {}; + res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS); + allocCreateInfo.pool = pool; + } + else if(testIndex == 1) + { + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT; + } + + VkBuffer buf = VK_NULL_HANDLE; + VmaAllocation alloc = VK_NULL_HANDLE; + VmaAllocationInfo allocInfo = {}; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + TEST(res == VK_SUCCESS && alloc); + + VkMemoryPropertyFlags memTypeFlags = 0; + vmaGetMemoryTypeProperties(g_hAllocator, allocInfo.memoryType, &memTypeFlags); + const bool shouldBeMapped = (memTypeFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0; + TEST((allocInfo.pMappedData != nullptr) == shouldBeMapped); + + vmaDestroyBuffer(g_hAllocator, buf, alloc); + vmaDestroyPool(g_hAllocator, pool); + } +} + +static void TestMappingMultithreaded() +{ + wprintf(L"Testing mapping multithreaded...\n"); + + static const uint32_t threadCount = 16; + static const uint32_t bufferCount = 1024; + static const uint32_t threadBufferCount = bufferCount / threadCount; + + VkResult res; + volatile uint32_t memTypeIndex = UINT32_MAX; + + enum TEST + { + TEST_NORMAL, + TEST_POOL, + TEST_DEDICATED, + TEST_COUNT + }; + for(uint32_t testIndex = 0; testIndex < TEST_COUNT; ++testIndex) + { + VmaPool pool = nullptr; + if(testIndex == TEST_POOL) + { + TEST(memTypeIndex != UINT32_MAX); + VmaPoolCreateInfo poolInfo = {}; + poolInfo.memoryTypeIndex = memTypeIndex; + res = vmaCreatePool(g_hAllocator, &poolInfo, &pool); + TEST(res == VK_SUCCESS); + } + + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = 0x10000; + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + allocCreateInfo.pool = pool; + if(testIndex == TEST_DEDICATED) + allocCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + std::thread threads[threadCount]; + for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex) + { + threads[threadIndex] = std::thread([=, &memTypeIndex](){ + // ======== THREAD FUNCTION ======== + + RandomNumberGenerator rand{threadIndex}; + + enum class MODE + { + // Don't map this buffer at all. + DONT_MAP, + // Map and quickly unmap. + MAP_FOR_MOMENT, + // Map and unmap before destruction. + MAP_FOR_LONGER, + // Map two times. Quickly unmap, second unmap before destruction. + MAP_TWO_TIMES, + // Create this buffer as persistently mapped. + PERSISTENTLY_MAPPED, + COUNT + }; + std::vector<BufferInfo> bufInfos{threadBufferCount}; + std::vector<MODE> bufModes{threadBufferCount}; + + for(uint32_t bufferIndex = 0; bufferIndex < threadBufferCount; ++bufferIndex) + { + BufferInfo& bufInfo = bufInfos[bufferIndex]; + const MODE mode = (MODE)(rand.Generate() % (uint32_t)MODE::COUNT); + bufModes[bufferIndex] = mode; + + VmaAllocationCreateInfo localAllocCreateInfo = allocCreateInfo; + if(mode == MODE::PERSISTENTLY_MAPPED) + localAllocCreateInfo.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VmaAllocationInfo allocInfo; + VkResult res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &localAllocCreateInfo, + &bufInfo.Buffer, &bufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + + if(memTypeIndex == UINT32_MAX) + memTypeIndex = allocInfo.memoryType; + + char* data = nullptr; + + if(mode == MODE::PERSISTENTLY_MAPPED) + { + data = (char*)allocInfo.pMappedData; + TEST(data != nullptr); + } + else if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_FOR_LONGER || + mode == MODE::MAP_TWO_TIMES) + { + TEST(data == nullptr); + res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data); + TEST(res == VK_SUCCESS && data != nullptr); + + if(mode == MODE::MAP_TWO_TIMES) + { + char* data2 = nullptr; + res = vmaMapMemory(g_hAllocator, bufInfo.Allocation, (void**)&data2); + TEST(res == VK_SUCCESS && data2 == data); + } + } + else if(mode == MODE::DONT_MAP) + { + TEST(allocInfo.pMappedData == nullptr); + } + else + TEST(0); + + // Test if reading and writing from the beginning and end of mapped memory doesn't crash. + if(data) + data[0xFFFF] = data[0]; + + if(mode == MODE::MAP_FOR_MOMENT || mode == MODE::MAP_TWO_TIMES) + { + vmaUnmapMemory(g_hAllocator, bufInfo.Allocation); + + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, bufInfo.Allocation, &allocInfo); + if(mode == MODE::MAP_FOR_MOMENT) + TEST(allocInfo.pMappedData == nullptr); + else + TEST(allocInfo.pMappedData == data); + } + + switch(rand.Generate() % 3) + { + case 0: Sleep(0); break; // Yield. + case 1: Sleep(10); break; // 10 ms + // default: No sleep. + } + + // Test if reading and writing from the beginning and end of mapped memory doesn't crash. + if(data) + data[0xFFFF] = data[0]; + } + + for(size_t bufferIndex = threadBufferCount; bufferIndex--; ) + { + if(bufModes[bufferIndex] == MODE::MAP_FOR_LONGER || + bufModes[bufferIndex] == MODE::MAP_TWO_TIMES) + { + vmaUnmapMemory(g_hAllocator, bufInfos[bufferIndex].Allocation); + + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(g_hAllocator, bufInfos[bufferIndex].Allocation, &allocInfo); + TEST(allocInfo.pMappedData == nullptr); + } + + vmaDestroyBuffer(g_hAllocator, bufInfos[bufferIndex].Buffer, bufInfos[bufferIndex].Allocation); + } + }); + } + + for(uint32_t threadIndex = 0; threadIndex < threadCount; ++threadIndex) + threads[threadIndex].join(); + + vmaDestroyPool(g_hAllocator, pool); + } +} + +static void WriteMainTestResultHeader(FILE* file) +{ + fprintf(file, + "Code,Time," + "Threads,Buffers and images,Sizes,Operations,Allocation strategy,Free order," + "Total Time (us)," + "Allocation Time Min (us)," + "Allocation Time Avg (us)," + "Allocation Time Max (us)," + "Deallocation Time Min (us)," + "Deallocation Time Avg (us)," + "Deallocation Time Max (us)," + "Total Memory Allocated (B)," + "Free Range Size Avg (B)," + "Free Range Size Max (B)\n"); +} + +static void WriteMainTestResult( + FILE* file, + const char* codeDescription, + const char* testDescription, + const Config& config, const Result& result) +{ + float totalTimeSeconds = ToFloatSeconds(result.TotalTime); + float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin); + float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg); + float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax); + float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin); + float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg); + float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax); + + std::string currTime; + CurrentTimeToStr(currTime); + + fprintf(file, + "%s,%s,%s," + "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u\n", + codeDescription, + currTime.c_str(), + testDescription, + totalTimeSeconds * 1e6f, + allocationTimeMinSeconds * 1e6f, + allocationTimeAvgSeconds * 1e6f, + allocationTimeMaxSeconds * 1e6f, + deallocationTimeMinSeconds * 1e6f, + deallocationTimeAvgSeconds * 1e6f, + deallocationTimeMaxSeconds * 1e6f, + result.TotalMemoryAllocated, + result.FreeRangeSizeAvg, + result.FreeRangeSizeMax); +} + +static void WritePoolTestResultHeader(FILE* file) +{ + fprintf(file, + "Code,Test,Time," + "Config," + "Total Time (us)," + "Allocation Time Min (us)," + "Allocation Time Avg (us)," + "Allocation Time Max (us)," + "Deallocation Time Min (us)," + "Deallocation Time Avg (us)," + "Deallocation Time Max (us)," + "Lost Allocation Count," + "Lost Allocation Total Size (B)," + "Failed Allocation Count," + "Failed Allocation Total Size (B)\n"); +} + +static void WritePoolTestResult( + FILE* file, + const char* codeDescription, + const char* testDescription, + const PoolTestConfig& config, + const PoolTestResult& result) +{ + float totalTimeSeconds = ToFloatSeconds(result.TotalTime); + float allocationTimeMinSeconds = ToFloatSeconds(result.AllocationTimeMin); + float allocationTimeAvgSeconds = ToFloatSeconds(result.AllocationTimeAvg); + float allocationTimeMaxSeconds = ToFloatSeconds(result.AllocationTimeMax); + float deallocationTimeMinSeconds = ToFloatSeconds(result.DeallocationTimeMin); + float deallocationTimeAvgSeconds = ToFloatSeconds(result.DeallocationTimeAvg); + float deallocationTimeMaxSeconds = ToFloatSeconds(result.DeallocationTimeMax); + + std::string currTime; + CurrentTimeToStr(currTime); + + fprintf(file, + "%s,%s,%s," + "ThreadCount=%u PoolSize=%llu FrameCount=%u TotalItemCount=%u UsedItemCount=%u...%u ItemsToMakeUnusedPercent=%u," + "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%I64u,%I64u,%I64u,%I64u\n", + // General + codeDescription, + testDescription, + currTime.c_str(), + // Config + config.ThreadCount, + (unsigned long long)config.PoolSize, + config.FrameCount, + config.TotalItemCount, + config.UsedItemCountMin, + config.UsedItemCountMax, + config.ItemsToMakeUnusedPercent, + // Results + totalTimeSeconds * 1e6f, + allocationTimeMinSeconds * 1e6f, + allocationTimeAvgSeconds * 1e6f, + allocationTimeMaxSeconds * 1e6f, + deallocationTimeMinSeconds * 1e6f, + deallocationTimeAvgSeconds * 1e6f, + deallocationTimeMaxSeconds * 1e6f, + result.LostAllocationCount, + result.LostAllocationTotalSize, + result.FailedAllocationCount, + result.FailedAllocationTotalSize); +} + +static void PerformCustomMainTest(FILE* file) +{ + Config config{}; + config.RandSeed = 65735476; + //config.MaxBytesToAllocate = 4ull * 1024 * 1024; // 4 MB + config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB + config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY + config.FreeOrder = FREE_ORDER::FORWARD; + config.ThreadCount = 16; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 50; + config.AllocationStrategy = 0; + + // Buffers + //config.AllocationSizes.push_back({4, 16, 1024}); + config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB + + // Images + //config.AllocationSizes.push_back({4, 0, 0, 4, 32}); + //config.AllocationSizes.push_back({4, 0, 0, 256, 2048}); + + config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100; + config.AdditionalOperationCount = 1024; + + Result result{}; + VkResult res = MainTest(result, config); + TEST(res == VK_SUCCESS); + WriteMainTestResult(file, "Foo", "CustomTest", config, result); +} + +static void PerformCustomPoolTest(FILE* file) +{ + PoolTestConfig config; + config.PoolSize = 100 * 1024 * 1024; + config.RandSeed = 2345764; + config.ThreadCount = 1; + config.FrameCount = 200; + config.ItemsToMakeUnusedPercent = 2; + + AllocationSize allocSize = {}; + allocSize.BufferSizeMin = 1024; + allocSize.BufferSizeMax = 1024 * 1024; + allocSize.Probability = 1; + config.AllocationSizes.push_back(allocSize); + + allocSize.BufferSizeMin = 0; + allocSize.BufferSizeMax = 0; + allocSize.ImageSizeMin = 128; + allocSize.ImageSizeMax = 1024; + allocSize.Probability = 1; + config.AllocationSizes.push_back(allocSize); + + config.PoolSize = config.CalcAvgResourceSize() * 200; + config.UsedItemCountMax = 160; + config.TotalItemCount = config.UsedItemCountMax * 10; + config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100; + + PoolTestResult result = {}; + TestPool_Benchmark(result, config); + + WritePoolTestResult(file, "Code desc", "Test desc", config, result); +} + +static void PerformMainTests(FILE* file) +{ + wprintf(L"MAIN TESTS:\n"); + + uint32_t repeatCount = 1; + if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3; + + Config config{}; + config.RandSeed = 65735476; + config.MemUsageProbability[0] = 1; // VMA_MEMORY_USAGE_GPU_ONLY + config.FreeOrder = FREE_ORDER::FORWARD; + + size_t threadCountCount = 1; + switch(ConfigType) + { + case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break; + case CONFIG_TYPE_SMALL: threadCountCount = 2; break; + case CONFIG_TYPE_AVERAGE: threadCountCount = 3; break; + case CONFIG_TYPE_LARGE: threadCountCount = 5; break; + case CONFIG_TYPE_MAXIMUM: threadCountCount = 7; break; + default: assert(0); + } + + const size_t strategyCount = GetAllocationStrategyCount(); + + for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex) + { + std::string desc1; + + switch(threadCountIndex) + { + case 0: + desc1 += "1_thread"; + config.ThreadCount = 1; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 0; + break; + case 1: + desc1 += "16_threads+0%_common"; + config.ThreadCount = 16; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 0; + break; + case 2: + desc1 += "16_threads+50%_common"; + config.ThreadCount = 16; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 50; + break; + case 3: + desc1 += "16_threads+100%_common"; + config.ThreadCount = 16; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 100; + break; + case 4: + desc1 += "2_threads+0%_common"; + config.ThreadCount = 2; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 0; + break; + case 5: + desc1 += "2_threads+50%_common"; + config.ThreadCount = 2; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 50; + break; + case 6: + desc1 += "2_threads+100%_common"; + config.ThreadCount = 2; + config.ThreadsUsingCommonAllocationsProbabilityPercent = 100; + break; + default: + assert(0); + } + + // 0 = buffers, 1 = images, 2 = buffers and images + size_t buffersVsImagesCount = 2; + if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount; + for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex) + { + std::string desc2 = desc1; + switch(buffersVsImagesIndex) + { + case 0: desc2 += ",Buffers"; break; + case 1: desc2 += ",Images"; break; + case 2: desc2 += ",Buffers+Images"; break; + default: assert(0); + } + + // 0 = small, 1 = large, 2 = small and large + size_t smallVsLargeCount = 2; + if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount; + for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex) + { + std::string desc3 = desc2; + switch(smallVsLargeIndex) + { + case 0: desc3 += ",Small"; break; + case 1: desc3 += ",Large"; break; + case 2: desc3 += ",Small+Large"; break; + default: assert(0); + } + + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + config.MaxBytesToAllocate = 4ull * 1024 * 1024 * 1024; // 4 GB + else + config.MaxBytesToAllocate = 4ull * 1024 * 1024; + + // 0 = varying sizes min...max, 1 = set of constant sizes + size_t constantSizesCount = 1; + if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount; + for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex) + { + std::string desc4 = desc3; + switch(constantSizesIndex) + { + case 0: desc4 += " Varying_sizes"; break; + case 1: desc4 += " Constant_sizes"; break; + default: assert(0); + } + + config.AllocationSizes.clear(); + // Buffers present + if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2) + { + // Small + if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 16, 1024}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 16, 16}); + config.AllocationSizes.push_back({1, 64, 64}); + config.AllocationSizes.push_back({1, 256, 256}); + config.AllocationSizes.push_back({1, 1024, 1024}); + } + } + // Large + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0x10000, 0x10000}); + config.AllocationSizes.push_back({1, 0x80000, 0x80000}); + config.AllocationSizes.push_back({1, 0x200000, 0x200000}); + config.AllocationSizes.push_back({1, 0xA00000, 0xA00000}); + } + } + } + // Images present + if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2) + { + // Small + if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0, 0, 4, 32}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0, 0, 4, 4}); + config.AllocationSizes.push_back({1, 0, 0, 8, 8}); + config.AllocationSizes.push_back({1, 0, 0, 16, 16}); + config.AllocationSizes.push_back({1, 0, 0, 32, 32}); + } + } + // Large + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0, 0, 256, 2048}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0, 0, 256, 256}); + config.AllocationSizes.push_back({1, 0, 0, 512, 512}); + config.AllocationSizes.push_back({1, 0, 0, 1024, 1024}); + config.AllocationSizes.push_back({1, 0, 0, 2048, 2048}); + } + } + } + + // 0 = 100%, additional_operations = 0, 1 = 50%, 2 = 5%, 3 = 95% additional_operations = a lot + size_t beginBytesToAllocateCount = 1; + if(ConfigType >= CONFIG_TYPE_SMALL) ++beginBytesToAllocateCount; + if(ConfigType >= CONFIG_TYPE_AVERAGE) ++beginBytesToAllocateCount; + if(ConfigType >= CONFIG_TYPE_LARGE) ++beginBytesToAllocateCount; + for(size_t beginBytesToAllocateIndex = 0; beginBytesToAllocateIndex < beginBytesToAllocateCount; ++beginBytesToAllocateIndex) + { + std::string desc5 = desc4; + + switch(beginBytesToAllocateIndex) + { + case 0: + desc5 += ",Allocate_100%"; + config.BeginBytesToAllocate = config.MaxBytesToAllocate; + config.AdditionalOperationCount = 0; + break; + case 1: + desc5 += ",Allocate_50%+Operations"; + config.BeginBytesToAllocate = config.MaxBytesToAllocate * 50 / 100; + config.AdditionalOperationCount = 1024; + break; + case 2: + desc5 += ",Allocate_5%+Operations"; + config.BeginBytesToAllocate = config.MaxBytesToAllocate * 5 / 100; + config.AdditionalOperationCount = 1024; + break; + case 3: + desc5 += ",Allocate_95%+Operations"; + config.BeginBytesToAllocate = config.MaxBytesToAllocate * 95 / 100; + config.AdditionalOperationCount = 1024; + break; + default: + assert(0); + } + + for(size_t strategyIndex = 0; strategyIndex < strategyCount; ++strategyIndex) + { + std::string desc6 = desc5; + switch(strategyIndex) + { + case 0: + desc6 += ",BestFit"; + config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT; + break; + case 1: + desc6 += ",WorstFit"; + config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT; + break; + case 2: + desc6 += ",FirstFit"; + config.AllocationStrategy = VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT; + break; + default: + assert(0); + } + + desc6 += ','; + desc6 += FREE_ORDER_NAMES[(uint32_t)config.FreeOrder]; + + const char* testDescription = desc6.c_str(); + + for(size_t repeat = 0; repeat < repeatCount; ++repeat) + { + printf("%s #%u\n", testDescription, (uint32_t)repeat); + + Result result{}; + VkResult res = MainTest(result, config); + TEST(res == VK_SUCCESS); + if(file) + { + WriteMainTestResult(file, CODE_DESCRIPTION, testDescription, config, result); + } + } + } + } + } + } + } + } +} + +static void PerformPoolTests(FILE* file) +{ + wprintf(L"POOL TESTS:\n"); + + const size_t AVG_RESOURCES_PER_POOL = 300; + + uint32_t repeatCount = 1; + if(ConfigType >= CONFIG_TYPE_MAXIMUM) repeatCount = 3; + + PoolTestConfig config{}; + config.RandSeed = 2346343; + config.FrameCount = 200; + config.ItemsToMakeUnusedPercent = 2; + + size_t threadCountCount = 1; + switch(ConfigType) + { + case CONFIG_TYPE_MINIMUM: threadCountCount = 1; break; + case CONFIG_TYPE_SMALL: threadCountCount = 2; break; + case CONFIG_TYPE_AVERAGE: threadCountCount = 2; break; + case CONFIG_TYPE_LARGE: threadCountCount = 3; break; + case CONFIG_TYPE_MAXIMUM: threadCountCount = 3; break; + default: assert(0); + } + for(size_t threadCountIndex = 0; threadCountIndex < threadCountCount; ++threadCountIndex) + { + std::string desc1; + + switch(threadCountIndex) + { + case 0: + desc1 += "1_thread"; + config.ThreadCount = 1; + break; + case 1: + desc1 += "16_threads"; + config.ThreadCount = 16; + break; + case 2: + desc1 += "2_threads"; + config.ThreadCount = 2; + break; + default: + assert(0); + } + + // 0 = buffers, 1 = images, 2 = buffers and images + size_t buffersVsImagesCount = 2; + if(ConfigType >= CONFIG_TYPE_LARGE) ++buffersVsImagesCount; + for(size_t buffersVsImagesIndex = 0; buffersVsImagesIndex < buffersVsImagesCount; ++buffersVsImagesIndex) + { + std::string desc2 = desc1; + switch(buffersVsImagesIndex) + { + case 0: desc2 += " Buffers"; break; + case 1: desc2 += " Images"; break; + case 2: desc2 += " Buffers+Images"; break; + default: assert(0); + } + + // 0 = small, 1 = large, 2 = small and large + size_t smallVsLargeCount = 2; + if(ConfigType >= CONFIG_TYPE_LARGE) ++smallVsLargeCount; + for(size_t smallVsLargeIndex = 0; smallVsLargeIndex < smallVsLargeCount; ++smallVsLargeIndex) + { + std::string desc3 = desc2; + switch(smallVsLargeIndex) + { + case 0: desc3 += " Small"; break; + case 1: desc3 += " Large"; break; + case 2: desc3 += " Small+Large"; break; + default: assert(0); + } + + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + config.PoolSize = 6ull * 1024 * 1024 * 1024; // 6 GB + else + config.PoolSize = 4ull * 1024 * 1024; + + // 0 = varying sizes min...max, 1 = set of constant sizes + size_t constantSizesCount = 1; + if(ConfigType >= CONFIG_TYPE_SMALL) ++constantSizesCount; + for(size_t constantSizesIndex = 0; constantSizesIndex < constantSizesCount; ++constantSizesIndex) + { + std::string desc4 = desc3; + switch(constantSizesIndex) + { + case 0: desc4 += " Varying_sizes"; break; + case 1: desc4 += " Constant_sizes"; break; + default: assert(0); + } + + config.AllocationSizes.clear(); + // Buffers present + if(buffersVsImagesIndex == 0 || buffersVsImagesIndex == 2) + { + // Small + if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 16, 1024}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 16, 16}); + config.AllocationSizes.push_back({1, 64, 64}); + config.AllocationSizes.push_back({1, 256, 256}); + config.AllocationSizes.push_back({1, 1024, 1024}); + } + } + // Large + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0x10000, 0xA00000}); // 64 KB ... 10 MB + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0x10000, 0x10000}); + config.AllocationSizes.push_back({1, 0x80000, 0x80000}); + config.AllocationSizes.push_back({1, 0x200000, 0x200000}); + config.AllocationSizes.push_back({1, 0xA00000, 0xA00000}); + } + } + } + // Images present + if(buffersVsImagesIndex == 1 || buffersVsImagesIndex == 2) + { + // Small + if(smallVsLargeIndex == 0 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0, 0, 4, 32}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0, 0, 4, 4}); + config.AllocationSizes.push_back({1, 0, 0, 8, 8}); + config.AllocationSizes.push_back({1, 0, 0, 16, 16}); + config.AllocationSizes.push_back({1, 0, 0, 32, 32}); + } + } + // Large + if(smallVsLargeIndex == 1 || smallVsLargeIndex == 2) + { + // Varying size + if(constantSizesIndex == 0) + config.AllocationSizes.push_back({4, 0, 0, 256, 2048}); + // Constant sizes + else + { + config.AllocationSizes.push_back({1, 0, 0, 256, 256}); + config.AllocationSizes.push_back({1, 0, 0, 512, 512}); + config.AllocationSizes.push_back({1, 0, 0, 1024, 1024}); + config.AllocationSizes.push_back({1, 0, 0, 2048, 2048}); + } + } + } + + const VkDeviceSize avgResourceSize = config.CalcAvgResourceSize(); + config.PoolSize = avgResourceSize * AVG_RESOURCES_PER_POOL; + + // 0 = 66%, 1 = 133%, 2 = 100%, 3 = 33%, 4 = 166% + size_t subscriptionModeCount; + switch(ConfigType) + { + case CONFIG_TYPE_MINIMUM: subscriptionModeCount = 2; break; + case CONFIG_TYPE_SMALL: subscriptionModeCount = 2; break; + case CONFIG_TYPE_AVERAGE: subscriptionModeCount = 3; break; + case CONFIG_TYPE_LARGE: subscriptionModeCount = 5; break; + case CONFIG_TYPE_MAXIMUM: subscriptionModeCount = 5; break; + default: assert(0); + } + for(size_t subscriptionModeIndex = 0; subscriptionModeIndex < subscriptionModeCount; ++subscriptionModeIndex) + { + std::string desc5 = desc4; + + switch(subscriptionModeIndex) + { + case 0: + desc5 += " Subscription_66%"; + config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 66 / 100; + break; + case 1: + desc5 += " Subscription_133%"; + config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 133 / 100; + break; + case 2: + desc5 += " Subscription_100%"; + config.UsedItemCountMax = AVG_RESOURCES_PER_POOL; + break; + case 3: + desc5 += " Subscription_33%"; + config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 33 / 100; + break; + case 4: + desc5 += " Subscription_166%"; + config.UsedItemCountMax = AVG_RESOURCES_PER_POOL * 166 / 100; + break; + default: + assert(0); + } + + config.TotalItemCount = config.UsedItemCountMax * 5; + config.UsedItemCountMin = config.UsedItemCountMax * 80 / 100; + + const char* testDescription = desc5.c_str(); + + for(size_t repeat = 0; repeat < repeatCount; ++repeat) + { + printf("%s #%u\n", testDescription, (uint32_t)repeat); + + PoolTestResult result{}; + TestPool_Benchmark(result, config); + WritePoolTestResult(file, CODE_DESCRIPTION, testDescription, config, result); + } + } + } + } + } + } +} + +static void BasicTestBuddyAllocator() +{ + wprintf(L"Basic test buddy allocator\n"); + + RandomNumberGenerator rand{76543}; + + VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + sampleBufCreateInfo.size = 1024; // Whatever. + sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + VmaAllocationCreateInfo sampleAllocCreateInfo = {}; + sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VmaPoolCreateInfo poolCreateInfo = {}; + VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + + // Deliberately adding 1023 to test usable size smaller than memory block size. + poolCreateInfo.blockSize = 1024 * 1024 + 1023; + poolCreateInfo.flags = VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT; + //poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; + + VmaPool pool = nullptr; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS); + + VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.pool = pool; + + std::vector<BufferInfo> bufInfo; + BufferInfo newBufInfo; + VmaAllocationInfo allocInfo; + + bufCreateInfo.size = 1024 * 256; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + bufCreateInfo.size = 1024 * 512; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + bufCreateInfo.size = 1024 * 128; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + // Test very small allocation, smaller than minimum node size. + bufCreateInfo.size = 1; + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + + // Test some small allocation with alignment requirement. + { + VkMemoryRequirements memReq; + memReq.alignment = 256; + memReq.memoryTypeBits = UINT32_MAX; + memReq.size = 32; + + newBufInfo.Buffer = VK_NULL_HANDLE; + res = vmaAllocateMemory(g_hAllocator, &memReq, &allocCreateInfo, + &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + TEST(allocInfo.offset % memReq.alignment == 0); + bufInfo.push_back(newBufInfo); + } + + //SaveAllocatorStatsToFile(L"TEST.json"); + + VmaPoolStats stats = {}; + vmaGetPoolStats(g_hAllocator, pool, &stats); + int DBG = 0; // Set breakpoint here to inspect `stats`. + + // Allocate enough new buffers to surely fall into second block. + for(uint32_t i = 0; i < 32; ++i) + { + bufCreateInfo.size = 1024 * (rand.Generate() % 32 + 1); + res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, + &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo); + TEST(res == VK_SUCCESS); + bufInfo.push_back(newBufInfo); + } + + SaveAllocatorStatsToFile(L"BuddyTest01.json"); + + // Destroy the buffers in random order. + while(!bufInfo.empty()) + { + const size_t indexToDestroy = rand.Generate() % bufInfo.size(); + const BufferInfo& currBufInfo = bufInfo[indexToDestroy]; + vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation); + bufInfo.erase(bufInfo.begin() + indexToDestroy); + } + + vmaDestroyPool(g_hAllocator, pool); +} + +static void BasicTestAllocatePages() +{ + wprintf(L"Basic test allocate pages\n"); + + RandomNumberGenerator rand{765461}; + + VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + sampleBufCreateInfo.size = 1024; // Whatever. + sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo sampleAllocCreateInfo = {}; + sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + + VmaPoolCreateInfo poolCreateInfo = {}; + VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex); + TEST(res == VK_SUCCESS); + + // 1 block of 1 MB. + poolCreateInfo.blockSize = 1024 * 1024; + poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1; + + // Create pool. + VmaPool pool = nullptr; + res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool); + TEST(res == VK_SUCCESS); + + // Make 100 allocations of 4 KB - they should fit into the pool. + VkMemoryRequirements memReq; + memReq.memoryTypeBits = UINT32_MAX; + memReq.alignment = 4 * 1024; + memReq.size = 4 * 1024; + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + allocCreateInfo.pool = pool; + + constexpr uint32_t allocCount = 100; + + std::vector<VmaAllocation> alloc{allocCount}; + std::vector<VmaAllocationInfo> allocInfo{allocCount}; + res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), allocInfo.data()); + TEST(res == VK_SUCCESS); + for(uint32_t i = 0; i < allocCount; ++i) + { + TEST(alloc[i] != VK_NULL_HANDLE && + allocInfo[i].pMappedData != nullptr && + allocInfo[i].deviceMemory == allocInfo[0].deviceMemory && + allocInfo[i].memoryType == allocInfo[0].memoryType); + } + + // Free the allocations. + vmaFreeMemoryPages(g_hAllocator, allocCount, alloc.data()); + std::fill(alloc.begin(), alloc.end(), nullptr); + std::fill(allocInfo.begin(), allocInfo.end(), VmaAllocationInfo{}); + + // Try to make 100 allocations of 100 KB. This call should fail due to not enough memory. + // Also test optional allocationInfo = null. + memReq.size = 100 * 1024; + res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), nullptr); + TEST(res != VK_SUCCESS); + TEST(std::find_if(alloc.begin(), alloc.end(), [](VmaAllocation alloc){ return alloc != VK_NULL_HANDLE; }) == alloc.end()); + + // Make 100 allocations of 4 KB, but with required alignment of 128 KB. This should also fail. + memReq.size = 4 * 1024; + memReq.alignment = 128 * 1024; + res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &allocCreateInfo, allocCount, alloc.data(), allocInfo.data()); + TEST(res != VK_SUCCESS); + + // Make 100 dedicated allocations of 4 KB. + memReq.alignment = 4 * 1024; + memReq.size = 4 * 1024; + + VmaAllocationCreateInfo dedicatedAllocCreateInfo = {}; + dedicatedAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + dedicatedAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + res = vmaAllocateMemoryPages(g_hAllocator, &memReq, &dedicatedAllocCreateInfo, allocCount, alloc.data(), allocInfo.data()); + TEST(res == VK_SUCCESS); + for(uint32_t i = 0; i < allocCount; ++i) + { + TEST(alloc[i] != VK_NULL_HANDLE && + allocInfo[i].pMappedData != nullptr && + allocInfo[i].memoryType == allocInfo[0].memoryType && + allocInfo[i].offset == 0); + if(i > 0) + { + TEST(allocInfo[i].deviceMemory != allocInfo[0].deviceMemory); + } + } + + // Free the allocations. + vmaFreeMemoryPages(g_hAllocator, allocCount, alloc.data()); + std::fill(alloc.begin(), alloc.end(), nullptr); + std::fill(allocInfo.begin(), allocInfo.end(), VmaAllocationInfo{}); + + vmaDestroyPool(g_hAllocator, pool); +} + +// Test the testing environment. +static void TestGpuData() +{ + RandomNumberGenerator rand = { 53434 }; + + std::vector<AllocInfo> allocInfo; + + for(size_t i = 0; i < 100; ++i) + { + AllocInfo info = {}; + + info.m_BufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + info.m_BufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + info.m_BufferInfo.size = 1024 * 1024 * (rand.Generate() % 9 + 1); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VkResult res = vmaCreateBuffer(g_hAllocator, &info.m_BufferInfo, &allocCreateInfo, &info.m_Buffer, &info.m_Allocation, nullptr); + TEST(res == VK_SUCCESS); + + info.m_StartValue = rand.Generate(); + + allocInfo.push_back(std::move(info)); + } + + UploadGpuData(allocInfo.data(), allocInfo.size()); + + ValidateGpuData(allocInfo.data(), allocInfo.size()); + + DestroyAllAllocations(allocInfo); +} + +void Test() +{ + wprintf(L"TESTING:\n"); + + if(false) + { + //////////////////////////////////////////////////////////////////////////////// + // Temporarily insert custom tests here: + return; + } + + // # Simple tests + + TestBasics(); + TestAllocationVersusResourceSize(); + //TestGpuData(); // Not calling this because it's just testing the testing environment. +#if VMA_DEBUG_MARGIN + TestDebugMargin(); +#else + TestPool_SameSize(); + TestPool_MinBlockCount(); + TestPool_MinAllocationAlignment(); + TestHeapSizeLimit(); +#endif +#if VMA_DEBUG_INITIALIZE_ALLOCATIONS + TestAllocationsInitialization(); +#endif + TestMemoryUsage(); + TestDeviceCoherentMemory(); + TestBudget(); + TestAliasing(); + TestMapping(); + TestDeviceLocalMapped(); + TestMappingMultithreaded(); + TestLinearAllocator(); + ManuallyTestLinearAllocator(); + TestLinearAllocatorMultiBlock(); + + BasicTestBuddyAllocator(); + BasicTestAllocatePages(); + + if(VK_KHR_buffer_device_address_enabled) + TestBufferDeviceAddress(); + if(VK_EXT_memory_priority_enabled) + TestMemoryPriority(); + + { + FILE* file; + fopen_s(&file, "Algorithms.csv", "w"); + assert(file != NULL); + BenchmarkAlgorithms(file); + fclose(file); + } + + TestDefragmentationSimple(); + TestDefragmentationFull(); + TestDefragmentationWholePool(); + TestDefragmentationGpu(); + TestDefragmentationIncrementalBasic(); + TestDefragmentationIncrementalComplex(); + + // # Detailed tests + FILE* file; + fopen_s(&file, "Results.csv", "w"); + assert(file != NULL); + + WriteMainTestResultHeader(file); + PerformMainTests(file); + //PerformCustomMainTest(file); + + WritePoolTestResultHeader(file); + PerformPoolTests(file); + //PerformCustomPoolTest(file); + + fclose(file); + + wprintf(L"Done, all PASSED.\n"); +} + +#endif // #ifdef _WIN32
diff --git a/src/Tests.h b/src/Tests.h index 01cf1a8..4b3cb87 100644 --- a/src/Tests.h +++ b/src/Tests.h
@@ -1,32 +1,32 @@ -// -// Copyright (c) 2017-2021 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. -// - -#ifndef TESTS_H_ -#define TESTS_H_ - -#ifdef _WIN32 - -void Test(); - -#endif // #ifdef _WIN32 - -#endif +// +// Copyright (c) 2017-2021 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. +// + +#ifndef TESTS_H_ +#define TESTS_H_ + +#ifdef _WIN32 + +void Test(); + +#endif // #ifdef _WIN32 + +#endif
diff --git a/src/VmaReplay/Common.cpp b/src/VmaReplay/Common.cpp index e7880a9..54039dd 100644 --- a/src/VmaReplay/Common.cpp +++ b/src/VmaReplay/Common.cpp
@@ -1,721 +1,721 @@ -// -// Copyright (c) 2017-2021 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 "Common.h" - -bool StrRangeToPtrList(const StrRange& s, std::vector<uint64_t>& out) -{ - out.clear(); - StrRange currRange = { s.beg, nullptr }; - while(currRange.beg < s.end) - { - currRange.end = currRange.beg; - while(currRange.end < s.end && *currRange.end != ' ') - { - ++currRange.end; - } - - uint64_t ptr = 0; - if(!StrRangeToPtr(currRange, ptr)) - { - return false; - } - out.push_back(ptr); - - currRange.beg = currRange.end + 1; - } - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// LineSplit class - -bool LineSplit::GetNextLine(StrRange& out) -{ - if(m_NextLineBeg < m_NumBytes) - { - out.beg = m_Data + m_NextLineBeg; - size_t currLineEnd = m_NextLineBeg; - while(currLineEnd < m_NumBytes && m_Data[currLineEnd] != '\n') - ++currLineEnd; - out.end = m_Data + currLineEnd; - // Ignore trailing '\r' to support Windows end of line. - if(out.end > out.beg && *(out.end - 1) == '\r') - { - --out.end; - } - m_NextLineBeg = currLineEnd + 1; // Past '\n' - ++m_NextLineIndex; - return true; - } - else - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// CsvSplit class - -void CsvSplit::Set(const StrRange& line, size_t maxCount) -{ - assert(maxCount <= RANGE_COUNT_MAX); - m_Line = line; - const size_t strLen = line.length(); - size_t rangeIndex = 0; - size_t charIndex = 0; - while(charIndex < strLen && rangeIndex < maxCount) - { - m_Ranges[rangeIndex * 2] = charIndex; - while(charIndex < strLen && (rangeIndex + 1 == maxCount || m_Line.beg[charIndex] != ',')) - ++charIndex; - m_Ranges[rangeIndex * 2 + 1] = charIndex; - ++rangeIndex; - ++charIndex; // Past ',' - } - m_Count = rangeIndex; -} - -//////////////////////////////////////////////////////////////////////////////// -// class CmdLineParser - -bool CmdLineParser::ReadNextArg(std::string *OutArg) -{ - if (m_argv != NULL) - { - if (m_ArgIndex >= (size_t)m_argc) return false; - - *OutArg = m_argv[m_ArgIndex]; - m_ArgIndex++; - return true; - } - else - { - if (m_ArgIndex >= m_CmdLineLength) return false; - - OutArg->clear(); - bool InsideQuotes = false; - while (m_ArgIndex < m_CmdLineLength) - { - char Ch = m_CmdLine[m_ArgIndex]; - if (Ch == '\\') - { - bool FollowedByQuote = false; - size_t BackslashCount = 1; - size_t TmpIndex = m_ArgIndex + 1; - while (TmpIndex < m_CmdLineLength) - { - char TmpCh = m_CmdLine[TmpIndex]; - if (TmpCh == '\\') - { - BackslashCount++; - TmpIndex++; - } - else if (TmpCh == '"') - { - FollowedByQuote = true; - break; - } - else - break; - } - - if (FollowedByQuote) - { - if (BackslashCount % 2 == 0) - { - for (size_t i = 0; i < BackslashCount / 2; i++) - *OutArg += '\\'; - m_ArgIndex += BackslashCount + 1; - InsideQuotes = !InsideQuotes; - } - else - { - for (size_t i = 0; i < BackslashCount / 2; i++) - *OutArg += '\\'; - *OutArg += '"'; - m_ArgIndex += BackslashCount + 1; - } - } - else - { - for (size_t i = 0; i < BackslashCount; i++) - *OutArg += '\\'; - m_ArgIndex += BackslashCount; - } - } - else if (Ch == '"') - { - InsideQuotes = !InsideQuotes; - m_ArgIndex++; - } - else if (isspace(Ch)) - { - if (InsideQuotes) - { - *OutArg += Ch; - m_ArgIndex++; - } - else - { - m_ArgIndex++; - break; - } - } - else - { - *OutArg += Ch; - m_ArgIndex++; - } - } - - while (m_ArgIndex < m_CmdLineLength && isspace(m_CmdLine[m_ArgIndex])) - m_ArgIndex++; - - return true; - } -} - -CmdLineParser::SHORT_OPT * CmdLineParser::FindShortOpt(char Opt) -{ - for (size_t i = 0; i < m_ShortOpts.size(); i++) - if (m_ShortOpts[i].Opt == Opt) - return &m_ShortOpts[i]; - return NULL; -} - -CmdLineParser::LONG_OPT * CmdLineParser::FindLongOpt(const std::string &Opt) -{ - for (size_t i = 0; i < m_LongOpts.size(); i++) - if (m_LongOpts[i].Opt == Opt) - return &m_LongOpts[i]; - return NULL; -} - -CmdLineParser::CmdLineParser(int argc, char **argv) : - m_argv(argv), - m_CmdLine(NULL), - m_argc(argc), - m_CmdLineLength(0), - m_ArgIndex(1), - m_InsideMultioption(false), - m_LastArgIndex(0), - m_LastOptId(0) -{ - assert(argc > 0); - assert(argv != NULL); -} - -CmdLineParser::CmdLineParser(const char *CmdLine) : - m_argv(NULL), - m_CmdLine(CmdLine), - m_argc(0), - m_ArgIndex(0), - m_InsideMultioption(false), - m_LastArgIndex(0), - m_LastOptId(0) -{ - assert(CmdLine != NULL); - - m_CmdLineLength = strlen(m_CmdLine); - - while (m_ArgIndex < m_CmdLineLength && isspace(m_CmdLine[m_ArgIndex])) - m_ArgIndex++; -} - -void CmdLineParser::RegisterOpt(uint32_t Id, char Opt, bool Parameter) -{ - assert(Opt != '\0'); - - m_ShortOpts.push_back(SHORT_OPT(Id, Opt, Parameter)); -} - -void CmdLineParser::RegisterOpt(uint32_t Id, const std::string &Opt, bool Parameter) -{ - assert(!Opt.empty()); - - m_LongOpts.push_back(LONG_OPT(Id, Opt, Parameter)); -} - -CmdLineParser::RESULT CmdLineParser::ReadNext() -{ - if (m_InsideMultioption) - { - assert(m_LastArgIndex < m_LastArg.length()); - SHORT_OPT *so = FindShortOpt(m_LastArg[m_LastArgIndex]); - if (so == NULL) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - if (so->Parameter) - { - if (m_LastArg.length() == m_LastArgIndex+1) - { - if (!ReadNextArg(&m_LastParameter)) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - m_InsideMultioption = false; - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - else if (m_LastArg[m_LastArgIndex+1] == '=') - { - m_InsideMultioption = false; - m_LastParameter = m_LastArg.substr(m_LastArgIndex+2); - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - else - { - m_InsideMultioption = false; - m_LastParameter = m_LastArg.substr(m_LastArgIndex+1); - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - } - else - { - if (m_LastArg.length() == m_LastArgIndex+1) - { - m_InsideMultioption = false; - m_LastParameter.clear(); - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - else - { - m_LastArgIndex++; - - m_LastParameter.clear(); - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - } - } - else - { - if (!ReadNextArg(&m_LastArg)) - { - m_LastParameter.clear(); - m_LastOptId = 0; - return CmdLineParser::RESULT_END; - } - - if (!m_LastArg.empty() && m_LastArg[0] == '-') - { - if (m_LastArg.length() > 1 && m_LastArg[1] == '-') - { - size_t EqualIndex = m_LastArg.find('=', 2); - if (EqualIndex != std::string::npos) - { - LONG_OPT *lo = FindLongOpt(m_LastArg.substr(2, EqualIndex-2)); - if (lo == NULL || lo->Parameter == false) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - m_LastParameter = m_LastArg.substr(EqualIndex+1); - m_LastOptId = lo->Id; - return CmdLineParser::RESULT_OPT; - } - else - { - LONG_OPT *lo = FindLongOpt(m_LastArg.substr(2)); - if (lo == NULL) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - if (lo->Parameter) - { - if (!ReadNextArg(&m_LastParameter)) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - } - else - m_LastParameter.clear(); - m_LastOptId = lo->Id; - return CmdLineParser::RESULT_OPT; - } - } - else - { - if (m_LastArg.length() < 2) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - SHORT_OPT *so = FindShortOpt(m_LastArg[1]); - if (so == NULL) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - if (so->Parameter) - { - if (m_LastArg.length() == 2) - { - if (!ReadNextArg(&m_LastParameter)) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - else if (m_LastArg[2] == '=') - { - m_LastParameter = m_LastArg.substr(3); - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - else - { - m_LastParameter = m_LastArg.substr(2); - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - } - else - { - if (m_LastArg.length() == 2) - { - m_LastParameter.clear(); - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - else - { - m_InsideMultioption = true; - m_LastArgIndex = 2; - - m_LastParameter.clear(); - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - } - } - } - else if (!m_LastArg.empty() && m_LastArg[0] == '/') - { - size_t EqualIndex = m_LastArg.find('=', 1); - if (EqualIndex != std::string::npos) - { - if (EqualIndex == 2) - { - SHORT_OPT *so = FindShortOpt(m_LastArg[1]); - if (so != NULL) - { - if (so->Parameter == false) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - m_LastParameter = m_LastArg.substr(EqualIndex+1); - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - } - LONG_OPT *lo = FindLongOpt(m_LastArg.substr(1, EqualIndex-1)); - if (lo == NULL || lo->Parameter == false) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - m_LastParameter = m_LastArg.substr(EqualIndex+1); - m_LastOptId = lo->Id; - return CmdLineParser::RESULT_OPT; - } - else - { - if (m_LastArg.length() == 2) - { - SHORT_OPT *so = FindShortOpt(m_LastArg[1]); - if (so != NULL) - { - if (so->Parameter) - { - if (!ReadNextArg(&m_LastParameter)) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - } - else - m_LastParameter.clear(); - m_LastOptId = so->Id; - return CmdLineParser::RESULT_OPT; - } - } - LONG_OPT *lo = FindLongOpt(m_LastArg.substr(1)); - if (lo == NULL) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - if (lo->Parameter) - { - if (!ReadNextArg(&m_LastParameter)) - { - m_LastOptId = 0; - m_LastParameter.clear(); - return CmdLineParser::RESULT_ERROR; - } - } - else - m_LastParameter.clear(); - m_LastOptId = lo->Id; - return CmdLineParser::RESULT_OPT; - } - } - else - { - m_LastOptId = 0; - m_LastParameter = m_LastArg; - return CmdLineParser::RESULT_PARAMETER; - } - } -} - -uint32_t CmdLineParser::GetOptId() -{ - return m_LastOptId; -} - -const std::string & CmdLineParser::GetParameter() -{ - return m_LastParameter; -} - -//////////////////////////////////////////////////////////////////////////////// -// Glolals - -/* - -void SetConsoleColor(CONSOLE_COLOR color) -{ - WORD attr = 0; - switch(color) - { - case CONSOLE_COLOR::INFO: - attr = FOREGROUND_INTENSITY;; - break; - case CONSOLE_COLOR::NORMAL: - attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - break; - case CONSOLE_COLOR::WARNING: - attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; - break; - case CONSOLE_COLOR::ERROR_: - attr = FOREGROUND_RED | FOREGROUND_INTENSITY; - break; - default: - assert(0); - } - - HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(out, attr); -} - -void PrintMessage(CONSOLE_COLOR color, const char* msg) -{ - if(color != CONSOLE_COLOR::NORMAL) - SetConsoleColor(color); - - printf("%s\n", msg); - - if (color != CONSOLE_COLOR::NORMAL) - SetConsoleColor(CONSOLE_COLOR::NORMAL); -} - -void PrintMessage(CONSOLE_COLOR color, const wchar_t* msg) -{ - if(color != CONSOLE_COLOR::NORMAL) - SetConsoleColor(color); - - wprintf(L"%s\n", msg); - - if (color != CONSOLE_COLOR::NORMAL) - SetConsoleColor(CONSOLE_COLOR::NORMAL); -} - -static const size_t CONSOLE_SMALL_BUF_SIZE = 256; - -void PrintMessageV(CONSOLE_COLOR color, const char* format, va_list argList) -{ - size_t dstLen = (size_t)::_vscprintf(format, argList); - if(dstLen) - { - bool useSmallBuf = dstLen < CONSOLE_SMALL_BUF_SIZE; - char smallBuf[CONSOLE_SMALL_BUF_SIZE]; - std::vector<char> bigBuf(useSmallBuf ? 0 : dstLen + 1); - char* bufPtr = useSmallBuf ? smallBuf : bigBuf.data(); - ::vsprintf_s(bufPtr, dstLen + 1, format, argList); - PrintMessage(color, bufPtr); - } -} - -void PrintMessageV(CONSOLE_COLOR color, const wchar_t* format, va_list argList) -{ - size_t dstLen = (size_t)::_vcwprintf(format, argList); - if(dstLen) - { - bool useSmallBuf = dstLen < CONSOLE_SMALL_BUF_SIZE; - wchar_t smallBuf[CONSOLE_SMALL_BUF_SIZE]; - std::vector<wchar_t> bigBuf(useSmallBuf ? 0 : dstLen + 1); - wchar_t* bufPtr = useSmallBuf ? smallBuf : bigBuf.data(); - ::vswprintf_s(bufPtr, dstLen + 1, format, argList); - PrintMessage(color, bufPtr); - } -} - -void PrintMessageF(CONSOLE_COLOR color, const char* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(color, format, argList); - va_end(argList); -} - -void PrintMessageF(CONSOLE_COLOR color, const wchar_t* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(color, format, argList); - va_end(argList); -} - -void PrintWarningF(const char* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); - va_end(argList); -} - -void PrintWarningF(const wchar_t* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); - va_end(argList); -} - -void PrintErrorF(const char* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); - va_end(argList); -} - -void PrintErrorF(const wchar_t* format, ...) -{ - va_list argList; - va_start(argList, format); - PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); - va_end(argList); -} -*/ - -void SecondsToFriendlyStr(float seconds, std::string& out) -{ - if(seconds == 0.f) - { - out = "0"; - return; - } - - if (seconds < 0.f) - { - out = "-"; - seconds = -seconds; - } - else - { - out.clear(); - } - - char s[32]; - - // #.### ns - if(seconds < 1e-6) - { - sprintf_s(s, "%.3f ns", seconds * 1e9); - out += s; - } - // #.### us - else if(seconds < 1e-3) - { - sprintf_s(s, "%.3f us", seconds * 1e6); - out += s; - } - // #.### ms - else if(seconds < 1.f) - { - sprintf_s(s, "%.3f ms", seconds * 1e3); - out += s; - } - // #.### s - else if(seconds < 60.f) - { - sprintf_s(s, "%.3f s", seconds); - out += s; - } - else - { - uint64_t seconds_u = (uint64_t)seconds; - // "#:## min" - if (seconds_u < 3600) - { - uint64_t minutes = seconds_u / 60; - seconds_u -= minutes * 60; - sprintf_s(s, "%llu:%02llu min", minutes, seconds_u); - out += s; - } - // "#:##:## h" - else - { - uint64_t minutes = seconds_u / 60; - seconds_u -= minutes * 60; - uint64_t hours = minutes / 60; - minutes -= hours * 60; - sprintf_s(s, "%llu:%02llu:%02llu h", hours, minutes, seconds_u); - out += s; - } - } -} +// +// Copyright (c) 2017-2021 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 "Common.h" + +bool StrRangeToPtrList(const StrRange& s, std::vector<uint64_t>& out) +{ + out.clear(); + StrRange currRange = { s.beg, nullptr }; + while(currRange.beg < s.end) + { + currRange.end = currRange.beg; + while(currRange.end < s.end && *currRange.end != ' ') + { + ++currRange.end; + } + + uint64_t ptr = 0; + if(!StrRangeToPtr(currRange, ptr)) + { + return false; + } + out.push_back(ptr); + + currRange.beg = currRange.end + 1; + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// LineSplit class + +bool LineSplit::GetNextLine(StrRange& out) +{ + if(m_NextLineBeg < m_NumBytes) + { + out.beg = m_Data + m_NextLineBeg; + size_t currLineEnd = m_NextLineBeg; + while(currLineEnd < m_NumBytes && m_Data[currLineEnd] != '\n') + ++currLineEnd; + out.end = m_Data + currLineEnd; + // Ignore trailing '\r' to support Windows end of line. + if(out.end > out.beg && *(out.end - 1) == '\r') + { + --out.end; + } + m_NextLineBeg = currLineEnd + 1; // Past '\n' + ++m_NextLineIndex; + return true; + } + else + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// CsvSplit class + +void CsvSplit::Set(const StrRange& line, size_t maxCount) +{ + assert(maxCount <= RANGE_COUNT_MAX); + m_Line = line; + const size_t strLen = line.length(); + size_t rangeIndex = 0; + size_t charIndex = 0; + while(charIndex < strLen && rangeIndex < maxCount) + { + m_Ranges[rangeIndex * 2] = charIndex; + while(charIndex < strLen && (rangeIndex + 1 == maxCount || m_Line.beg[charIndex] != ',')) + ++charIndex; + m_Ranges[rangeIndex * 2 + 1] = charIndex; + ++rangeIndex; + ++charIndex; // Past ',' + } + m_Count = rangeIndex; +} + +//////////////////////////////////////////////////////////////////////////////// +// class CmdLineParser + +bool CmdLineParser::ReadNextArg(std::string *OutArg) +{ + if (m_argv != NULL) + { + if (m_ArgIndex >= (size_t)m_argc) return false; + + *OutArg = m_argv[m_ArgIndex]; + m_ArgIndex++; + return true; + } + else + { + if (m_ArgIndex >= m_CmdLineLength) return false; + + OutArg->clear(); + bool InsideQuotes = false; + while (m_ArgIndex < m_CmdLineLength) + { + char Ch = m_CmdLine[m_ArgIndex]; + if (Ch == '\\') + { + bool FollowedByQuote = false; + size_t BackslashCount = 1; + size_t TmpIndex = m_ArgIndex + 1; + while (TmpIndex < m_CmdLineLength) + { + char TmpCh = m_CmdLine[TmpIndex]; + if (TmpCh == '\\') + { + BackslashCount++; + TmpIndex++; + } + else if (TmpCh == '"') + { + FollowedByQuote = true; + break; + } + else + break; + } + + if (FollowedByQuote) + { + if (BackslashCount % 2 == 0) + { + for (size_t i = 0; i < BackslashCount / 2; i++) + *OutArg += '\\'; + m_ArgIndex += BackslashCount + 1; + InsideQuotes = !InsideQuotes; + } + else + { + for (size_t i = 0; i < BackslashCount / 2; i++) + *OutArg += '\\'; + *OutArg += '"'; + m_ArgIndex += BackslashCount + 1; + } + } + else + { + for (size_t i = 0; i < BackslashCount; i++) + *OutArg += '\\'; + m_ArgIndex += BackslashCount; + } + } + else if (Ch == '"') + { + InsideQuotes = !InsideQuotes; + m_ArgIndex++; + } + else if (isspace(Ch)) + { + if (InsideQuotes) + { + *OutArg += Ch; + m_ArgIndex++; + } + else + { + m_ArgIndex++; + break; + } + } + else + { + *OutArg += Ch; + m_ArgIndex++; + } + } + + while (m_ArgIndex < m_CmdLineLength && isspace(m_CmdLine[m_ArgIndex])) + m_ArgIndex++; + + return true; + } +} + +CmdLineParser::SHORT_OPT * CmdLineParser::FindShortOpt(char Opt) +{ + for (size_t i = 0; i < m_ShortOpts.size(); i++) + if (m_ShortOpts[i].Opt == Opt) + return &m_ShortOpts[i]; + return NULL; +} + +CmdLineParser::LONG_OPT * CmdLineParser::FindLongOpt(const std::string &Opt) +{ + for (size_t i = 0; i < m_LongOpts.size(); i++) + if (m_LongOpts[i].Opt == Opt) + return &m_LongOpts[i]; + return NULL; +} + +CmdLineParser::CmdLineParser(int argc, char **argv) : + m_argv(argv), + m_CmdLine(NULL), + m_argc(argc), + m_CmdLineLength(0), + m_ArgIndex(1), + m_InsideMultioption(false), + m_LastArgIndex(0), + m_LastOptId(0) +{ + assert(argc > 0); + assert(argv != NULL); +} + +CmdLineParser::CmdLineParser(const char *CmdLine) : + m_argv(NULL), + m_CmdLine(CmdLine), + m_argc(0), + m_ArgIndex(0), + m_InsideMultioption(false), + m_LastArgIndex(0), + m_LastOptId(0) +{ + assert(CmdLine != NULL); + + m_CmdLineLength = strlen(m_CmdLine); + + while (m_ArgIndex < m_CmdLineLength && isspace(m_CmdLine[m_ArgIndex])) + m_ArgIndex++; +} + +void CmdLineParser::RegisterOpt(uint32_t Id, char Opt, bool Parameter) +{ + assert(Opt != '\0'); + + m_ShortOpts.push_back(SHORT_OPT(Id, Opt, Parameter)); +} + +void CmdLineParser::RegisterOpt(uint32_t Id, const std::string &Opt, bool Parameter) +{ + assert(!Opt.empty()); + + m_LongOpts.push_back(LONG_OPT(Id, Opt, Parameter)); +} + +CmdLineParser::RESULT CmdLineParser::ReadNext() +{ + if (m_InsideMultioption) + { + assert(m_LastArgIndex < m_LastArg.length()); + SHORT_OPT *so = FindShortOpt(m_LastArg[m_LastArgIndex]); + if (so == NULL) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + if (so->Parameter) + { + if (m_LastArg.length() == m_LastArgIndex+1) + { + if (!ReadNextArg(&m_LastParameter)) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + m_InsideMultioption = false; + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + else if (m_LastArg[m_LastArgIndex+1] == '=') + { + m_InsideMultioption = false; + m_LastParameter = m_LastArg.substr(m_LastArgIndex+2); + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + else + { + m_InsideMultioption = false; + m_LastParameter = m_LastArg.substr(m_LastArgIndex+1); + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + } + else + { + if (m_LastArg.length() == m_LastArgIndex+1) + { + m_InsideMultioption = false; + m_LastParameter.clear(); + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + else + { + m_LastArgIndex++; + + m_LastParameter.clear(); + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + } + } + else + { + if (!ReadNextArg(&m_LastArg)) + { + m_LastParameter.clear(); + m_LastOptId = 0; + return CmdLineParser::RESULT_END; + } + + if (!m_LastArg.empty() && m_LastArg[0] == '-') + { + if (m_LastArg.length() > 1 && m_LastArg[1] == '-') + { + size_t EqualIndex = m_LastArg.find('=', 2); + if (EqualIndex != std::string::npos) + { + LONG_OPT *lo = FindLongOpt(m_LastArg.substr(2, EqualIndex-2)); + if (lo == NULL || lo->Parameter == false) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + m_LastParameter = m_LastArg.substr(EqualIndex+1); + m_LastOptId = lo->Id; + return CmdLineParser::RESULT_OPT; + } + else + { + LONG_OPT *lo = FindLongOpt(m_LastArg.substr(2)); + if (lo == NULL) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + if (lo->Parameter) + { + if (!ReadNextArg(&m_LastParameter)) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + } + else + m_LastParameter.clear(); + m_LastOptId = lo->Id; + return CmdLineParser::RESULT_OPT; + } + } + else + { + if (m_LastArg.length() < 2) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + SHORT_OPT *so = FindShortOpt(m_LastArg[1]); + if (so == NULL) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + if (so->Parameter) + { + if (m_LastArg.length() == 2) + { + if (!ReadNextArg(&m_LastParameter)) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + else if (m_LastArg[2] == '=') + { + m_LastParameter = m_LastArg.substr(3); + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + else + { + m_LastParameter = m_LastArg.substr(2); + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + } + else + { + if (m_LastArg.length() == 2) + { + m_LastParameter.clear(); + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + else + { + m_InsideMultioption = true; + m_LastArgIndex = 2; + + m_LastParameter.clear(); + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + } + } + } + else if (!m_LastArg.empty() && m_LastArg[0] == '/') + { + size_t EqualIndex = m_LastArg.find('=', 1); + if (EqualIndex != std::string::npos) + { + if (EqualIndex == 2) + { + SHORT_OPT *so = FindShortOpt(m_LastArg[1]); + if (so != NULL) + { + if (so->Parameter == false) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + m_LastParameter = m_LastArg.substr(EqualIndex+1); + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + } + LONG_OPT *lo = FindLongOpt(m_LastArg.substr(1, EqualIndex-1)); + if (lo == NULL || lo->Parameter == false) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + m_LastParameter = m_LastArg.substr(EqualIndex+1); + m_LastOptId = lo->Id; + return CmdLineParser::RESULT_OPT; + } + else + { + if (m_LastArg.length() == 2) + { + SHORT_OPT *so = FindShortOpt(m_LastArg[1]); + if (so != NULL) + { + if (so->Parameter) + { + if (!ReadNextArg(&m_LastParameter)) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + } + else + m_LastParameter.clear(); + m_LastOptId = so->Id; + return CmdLineParser::RESULT_OPT; + } + } + LONG_OPT *lo = FindLongOpt(m_LastArg.substr(1)); + if (lo == NULL) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + if (lo->Parameter) + { + if (!ReadNextArg(&m_LastParameter)) + { + m_LastOptId = 0; + m_LastParameter.clear(); + return CmdLineParser::RESULT_ERROR; + } + } + else + m_LastParameter.clear(); + m_LastOptId = lo->Id; + return CmdLineParser::RESULT_OPT; + } + } + else + { + m_LastOptId = 0; + m_LastParameter = m_LastArg; + return CmdLineParser::RESULT_PARAMETER; + } + } +} + +uint32_t CmdLineParser::GetOptId() +{ + return m_LastOptId; +} + +const std::string & CmdLineParser::GetParameter() +{ + return m_LastParameter; +} + +//////////////////////////////////////////////////////////////////////////////// +// Glolals + +/* + +void SetConsoleColor(CONSOLE_COLOR color) +{ + WORD attr = 0; + switch(color) + { + case CONSOLE_COLOR::INFO: + attr = FOREGROUND_INTENSITY;; + break; + case CONSOLE_COLOR::NORMAL: + attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + break; + case CONSOLE_COLOR::WARNING: + attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; + break; + case CONSOLE_COLOR::ERROR_: + attr = FOREGROUND_RED | FOREGROUND_INTENSITY; + break; + default: + assert(0); + } + + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(out, attr); +} + +void PrintMessage(CONSOLE_COLOR color, const char* msg) +{ + if(color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(color); + + printf("%s\n", msg); + + if (color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(CONSOLE_COLOR::NORMAL); +} + +void PrintMessage(CONSOLE_COLOR color, const wchar_t* msg) +{ + if(color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(color); + + wprintf(L"%s\n", msg); + + if (color != CONSOLE_COLOR::NORMAL) + SetConsoleColor(CONSOLE_COLOR::NORMAL); +} + +static const size_t CONSOLE_SMALL_BUF_SIZE = 256; + +void PrintMessageV(CONSOLE_COLOR color, const char* format, va_list argList) +{ + size_t dstLen = (size_t)::_vscprintf(format, argList); + if(dstLen) + { + bool useSmallBuf = dstLen < CONSOLE_SMALL_BUF_SIZE; + char smallBuf[CONSOLE_SMALL_BUF_SIZE]; + std::vector<char> bigBuf(useSmallBuf ? 0 : dstLen + 1); + char* bufPtr = useSmallBuf ? smallBuf : bigBuf.data(); + ::vsprintf_s(bufPtr, dstLen + 1, format, argList); + PrintMessage(color, bufPtr); + } +} + +void PrintMessageV(CONSOLE_COLOR color, const wchar_t* format, va_list argList) +{ + size_t dstLen = (size_t)::_vcwprintf(format, argList); + if(dstLen) + { + bool useSmallBuf = dstLen < CONSOLE_SMALL_BUF_SIZE; + wchar_t smallBuf[CONSOLE_SMALL_BUF_SIZE]; + std::vector<wchar_t> bigBuf(useSmallBuf ? 0 : dstLen + 1); + wchar_t* bufPtr = useSmallBuf ? smallBuf : bigBuf.data(); + ::vswprintf_s(bufPtr, dstLen + 1, format, argList); + PrintMessage(color, bufPtr); + } +} + +void PrintMessageF(CONSOLE_COLOR color, const char* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(color, format, argList); + va_end(argList); +} + +void PrintMessageF(CONSOLE_COLOR color, const wchar_t* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(color, format, argList); + va_end(argList); +} + +void PrintWarningF(const char* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} + +void PrintWarningF(const wchar_t* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} + +void PrintErrorF(const char* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} + +void PrintErrorF(const wchar_t* format, ...) +{ + va_list argList; + va_start(argList, format); + PrintMessageV(CONSOLE_COLOR::WARNING, format, argList); + va_end(argList); +} +*/ + +void SecondsToFriendlyStr(float seconds, std::string& out) +{ + if(seconds == 0.f) + { + out = "0"; + return; + } + + if (seconds < 0.f) + { + out = "-"; + seconds = -seconds; + } + else + { + out.clear(); + } + + char s[32]; + + // #.### ns + if(seconds < 1e-6) + { + sprintf_s(s, "%.3f ns", seconds * 1e9); + out += s; + } + // #.### us + else if(seconds < 1e-3) + { + sprintf_s(s, "%.3f us", seconds * 1e6); + out += s; + } + // #.### ms + else if(seconds < 1.f) + { + sprintf_s(s, "%.3f ms", seconds * 1e3); + out += s; + } + // #.### s + else if(seconds < 60.f) + { + sprintf_s(s, "%.3f s", seconds); + out += s; + } + else + { + uint64_t seconds_u = (uint64_t)seconds; + // "#:## min" + if (seconds_u < 3600) + { + uint64_t minutes = seconds_u / 60; + seconds_u -= minutes * 60; + sprintf_s(s, "%llu:%02llu min", minutes, seconds_u); + out += s; + } + // "#:##:## h" + else + { + uint64_t minutes = seconds_u / 60; + seconds_u -= minutes * 60; + uint64_t hours = minutes / 60; + minutes -= hours * 60; + sprintf_s(s, "%llu:%02llu:%02llu h", hours, minutes, seconds_u); + out += s; + } + } +}
diff --git a/src/VmaReplay/Common.h b/src/VmaReplay/Common.h index e42b47b..8c839b0 100644 --- a/src/VmaReplay/Common.h +++ b/src/VmaReplay/Common.h
@@ -1,426 +1,426 @@ -// -// Copyright (c) 2017-2021 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. -// - -#pragma once - -#include "VmaUsage.h" - -#include <iostream> -#include <fstream> -#include <vector> -#include <memory> -#include <algorithm> -#include <numeric> -#include <array> -#include <type_traits> -#include <utility> -#include <chrono> -#include <string> -#include <limits> - -#include <cassert> -#include <cstdlib> -#include <cstdio> -#include <cstdarg> - -typedef std::chrono::high_resolution_clock::time_point time_point; -typedef std::chrono::high_resolution_clock::duration duration; - -inline float ToFloatSeconds(duration d) -{ - return std::chrono::duration_cast<std::chrono::duration<float>>(d).count(); -} - -void SecondsToFriendlyStr(float seconds, std::string& out); - -template <typename T> -T ceil_div(T x, T y) -{ - return (x+y-1) / y; -} -template <typename T> -inline T round_div(T x, T y) -{ - return (x+y/(T)2) / y; -} - -template <typename T> -inline T align_up(T val, T align) -{ - return (val + align - 1) / align * align; -} - -struct StrRange -{ - const char* beg; - const char* end; - - StrRange() { } - StrRange(const char* beg, const char* end) : beg(beg), end(end) { } - explicit StrRange(const char* sz) : beg(sz), end(sz + strlen(sz)) { } - explicit StrRange(const std::string& s) : beg(s.data()), end(s.data() + s.length()) { } - - size_t length() const { return end - beg; } - void to_str(std::string& out) const { out.assign(beg, end); } -}; - -inline bool StrRangeEq(const StrRange& lhs, const char* rhsSz) -{ - const size_t rhsLen = strlen(rhsSz); - return rhsLen == lhs.length() && - memcmp(lhs.beg, rhsSz, rhsLen) == 0; -} - -inline bool StrRangeToUint(const StrRange& s, uint32_t& out) -{ - char* end = (char*)s.end; - out = (uint32_t)strtoul(s.beg, &end, 10); - return end == s.end; -} -inline bool StrRangeToUint(const StrRange& s, uint64_t& out) -{ - char* end = (char*)s.end; - out = (uint64_t)strtoull(s.beg, &end, 10); - return end == s.end; -} -inline bool StrRangeToPtr(const StrRange& s, uint64_t& out) -{ - char* end = (char*)s.end; - out = (uint64_t)strtoull(s.beg, &end, 16); - return end == s.end; -} -inline bool StrRangeToFloat(const StrRange& s, float& out) -{ - char* end = (char*)s.end; - out = strtof(s.beg, &end); - return end == s.end; -} -inline bool StrRangeToBool(const StrRange& s, bool& out) -{ - if(s.end - s.beg == 1) - { - if(*s.beg == '1') - { - out = true; - } - else if(*s.beg == '0') - { - out = false; - } - else - { - return false; - } - } - else - { - return false; - } - - return true; -} -bool StrRangeToPtrList(const StrRange& s, std::vector<uint64_t>& out); - -class LineSplit -{ -public: - LineSplit(const char* data, size_t numBytes) : - m_Data(data), - m_NumBytes(numBytes), - m_NextLineBeg(0), - m_NextLineIndex(0) - { - } - - bool GetNextLine(StrRange& out); - size_t GetNextLineIndex() const { return m_NextLineIndex; } - -private: - const char* const m_Data; - const size_t m_NumBytes; - size_t m_NextLineBeg; - size_t m_NextLineIndex; -}; - -class CsvSplit -{ -public: - static const size_t RANGE_COUNT_MAX = 32; - - void Set(const StrRange& line, size_t maxCount = RANGE_COUNT_MAX); - - const StrRange& GetLine() const { return m_Line; } - - size_t GetCount() const { return m_Count; } - StrRange GetRange(size_t index) const - { - if(index < m_Count) - { - return StrRange { - m_Line.beg + m_Ranges[index * 2], - m_Line.beg + m_Ranges[index * 2 + 1] }; - } - else - { - return StrRange{0, 0}; - } - } - -private: - StrRange m_Line = { nullptr, nullptr }; - size_t m_Count = 0; - size_t m_Ranges[RANGE_COUNT_MAX * 2]; // Pairs of begin-end. -}; - -class CmdLineParser -{ -public: - enum RESULT - { - RESULT_OPT, - RESULT_PARAMETER, - RESULT_END, - RESULT_ERROR, - }; - - CmdLineParser(int argc, char **argv); - CmdLineParser(const char *CmdLine); - - void RegisterOpt(uint32_t Id, char Opt, bool Parameter); - void RegisterOpt(uint32_t Id, const std::string &Opt, bool Parameter); - - RESULT ReadNext(); - uint32_t GetOptId(); - const std::string & GetParameter(); - -private: - struct SHORT_OPT - { - uint32_t Id; - char Opt; - bool Parameter; - - SHORT_OPT(uint32_t Id, char Opt, bool Parameter) : Id(Id), Opt(Opt), Parameter(Parameter) { } - }; - - struct LONG_OPT - { - uint32_t Id; - std::string Opt; - bool Parameter; - - LONG_OPT(uint32_t Id, std::string Opt, bool Parameter) : Id(Id), Opt(Opt), Parameter(Parameter) { } - }; - - char **m_argv; - const char *m_CmdLine; - int m_argc; - size_t m_CmdLineLength; - size_t m_ArgIndex; - - bool ReadNextArg(std::string *OutArg); - - std::vector<SHORT_OPT> m_ShortOpts; - std::vector<LONG_OPT> m_LongOpts; - - SHORT_OPT * FindShortOpt(char Opt); - LONG_OPT * FindLongOpt(const std::string &Opt); - - bool m_InsideMultioption; - std::string m_LastArg; - size_t m_LastArgIndex; - uint32_t m_LastOptId; - std::string m_LastParameter; -}; - -/* -Parses and stores a sequence of ranges. - -Upper range is inclusive. - -Examples: - - "1" -> [ {1, 1} ] - "1,10" -> [ {1, 1}, {10, 10} ] - "2-6" -> [ {2, 6} ] - "-8" -> [ {MIN, 8} ] - "12-" -> [ {12, MAX} ] - "1-10,12,15-" -> [ {1, 10}, {12, 12}, {15, MAX} ] - -TODO: Optimize it: Do sorting and merging while parsing. Do binary search while -reading. -*/ -template<typename T> -class RangeSequence -{ -public: - typedef std::pair<T, T> RangeType; - - void Clear() { m_Ranges.clear(); } - bool Parse(const StrRange& str); - - bool IsEmpty() const { return m_Ranges.empty(); } - size_t GetCount() const { return m_Ranges.size(); } - const RangeType* GetRanges() const { return m_Ranges.data(); } - - bool Includes(T number) const; - -private: - std::vector<RangeType> m_Ranges; -}; - -template<typename T> -bool RangeSequence<T>::Parse(const StrRange& str) -{ - m_Ranges.clear(); - - StrRange currRange = { str.beg, str.beg }; - while(currRange.beg < str.end) - { - currRange.end = currRange.beg + 1; - // Find next ',' or the end. - while(currRange.end < str.end && *currRange.end != ',') - { - ++currRange.end; - } - - // Find '-' within this range. - const char* hyphenPos = currRange.beg; - while(hyphenPos < currRange.end && *hyphenPos != '-') - { - ++hyphenPos; - } - - // No hyphen - single number like '10'. - if(hyphenPos == currRange.end) - { - RangeType range; - if(!StrRangeToUint(currRange, range.first)) - { - return false; - } - range.second = range.first; - m_Ranges.push_back(range); - } - // Hyphen at the end, like '10-'. - else if(hyphenPos + 1 == currRange.end) - { - const StrRange numberRange = { currRange.beg, hyphenPos }; - RangeType range; - if(!StrRangeToUint(numberRange, range.first)) - { - return false; - } - range.second = std::numeric_limits<T>::max(); - m_Ranges.push_back(range); - } - // Hyphen at the beginning, like "-10". - else if(hyphenPos == currRange.beg) - { - const StrRange numberRange = { currRange.beg + 1, currRange.end }; - RangeType range; - range.first = std::numeric_limits<T>::min(); - if(!StrRangeToUint(numberRange, range.second)) - { - return false; - } - m_Ranges.push_back(range); - } - // Hyphen in the middle, like "1-10". - else - { - const StrRange numberRange1 = { currRange.beg, hyphenPos }; - const StrRange numberRange2 = { hyphenPos + 1, currRange.end }; - RangeType range; - if(!StrRangeToUint(numberRange1, range.first) || - !StrRangeToUint(numberRange2, range.second) || - range.second < range.first) - { - return false; - } - m_Ranges.push_back(range); - } - - // Skip ',' - currRange.beg = currRange.end + 1; - } - - return true; -} - -template<typename T> -bool RangeSequence<T>::Includes(T number) const -{ - for(const auto& it : m_Ranges) - { - if(number >= it.first && number <= it.second) - { - return true; - } - } - return false; -} - -/* -class RandomNumberGenerator -{ -public: - RandomNumberGenerator() : m_Value{GetTickCount()} {} - RandomNumberGenerator(uint32_t seed) : m_Value{seed} { } - void Seed(uint32_t seed) { m_Value = seed; } - uint32_t Generate() { return GenerateFast() ^ (GenerateFast() >> 7); } - -private: - uint32_t m_Value; - uint32_t GenerateFast() { return m_Value = (m_Value * 196314165 + 907633515); } -}; - -enum class CONSOLE_COLOR -{ - INFO, - NORMAL, - WARNING, - ERROR_, - COUNT -}; - -void SetConsoleColor(CONSOLE_COLOR color); - -void PrintMessage(CONSOLE_COLOR color, const char* msg); -void PrintMessage(CONSOLE_COLOR color, const wchar_t* msg); - -inline void Print(const char* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); } -inline void Print(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); } -inline void PrintWarning(const char* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); } -inline void PrintWarning(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); } -inline void PrintError(const char* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); } -inline void PrintError(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); } - -void PrintMessageV(CONSOLE_COLOR color, const char* format, va_list argList); -void PrintMessageV(CONSOLE_COLOR color, const wchar_t* format, va_list argList); -void PrintMessageF(CONSOLE_COLOR color, const char* format, ...); -void PrintMessageF(CONSOLE_COLOR color, const wchar_t* format, ...); -void PrintWarningF(const char* format, ...); -void PrintWarningF(const wchar_t* format, ...); -void PrintErrorF(const char* format, ...); -void PrintErrorF(const wchar_t* format, ...); -*/ +// +// Copyright (c) 2017-2021 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. +// + +#pragma once + +#include "VmaUsage.h" + +#include <iostream> +#include <fstream> +#include <vector> +#include <memory> +#include <algorithm> +#include <numeric> +#include <array> +#include <type_traits> +#include <utility> +#include <chrono> +#include <string> +#include <limits> + +#include <cassert> +#include <cstdlib> +#include <cstdio> +#include <cstdarg> + +typedef std::chrono::high_resolution_clock::time_point time_point; +typedef std::chrono::high_resolution_clock::duration duration; + +inline float ToFloatSeconds(duration d) +{ + return std::chrono::duration_cast<std::chrono::duration<float>>(d).count(); +} + +void SecondsToFriendlyStr(float seconds, std::string& out); + +template <typename T> +T ceil_div(T x, T y) +{ + return (x+y-1) / y; +} +template <typename T> +inline T round_div(T x, T y) +{ + return (x+y/(T)2) / y; +} + +template <typename T> +inline T align_up(T val, T align) +{ + return (val + align - 1) / align * align; +} + +struct StrRange +{ + const char* beg; + const char* end; + + StrRange() { } + StrRange(const char* beg, const char* end) : beg(beg), end(end) { } + explicit StrRange(const char* sz) : beg(sz), end(sz + strlen(sz)) { } + explicit StrRange(const std::string& s) : beg(s.data()), end(s.data() + s.length()) { } + + size_t length() const { return end - beg; } + void to_str(std::string& out) const { out.assign(beg, end); } +}; + +inline bool StrRangeEq(const StrRange& lhs, const char* rhsSz) +{ + const size_t rhsLen = strlen(rhsSz); + return rhsLen == lhs.length() && + memcmp(lhs.beg, rhsSz, rhsLen) == 0; +} + +inline bool StrRangeToUint(const StrRange& s, uint32_t& out) +{ + char* end = (char*)s.end; + out = (uint32_t)strtoul(s.beg, &end, 10); + return end == s.end; +} +inline bool StrRangeToUint(const StrRange& s, uint64_t& out) +{ + char* end = (char*)s.end; + out = (uint64_t)strtoull(s.beg, &end, 10); + return end == s.end; +} +inline bool StrRangeToPtr(const StrRange& s, uint64_t& out) +{ + char* end = (char*)s.end; + out = (uint64_t)strtoull(s.beg, &end, 16); + return end == s.end; +} +inline bool StrRangeToFloat(const StrRange& s, float& out) +{ + char* end = (char*)s.end; + out = strtof(s.beg, &end); + return end == s.end; +} +inline bool StrRangeToBool(const StrRange& s, bool& out) +{ + if(s.end - s.beg == 1) + { + if(*s.beg == '1') + { + out = true; + } + else if(*s.beg == '0') + { + out = false; + } + else + { + return false; + } + } + else + { + return false; + } + + return true; +} +bool StrRangeToPtrList(const StrRange& s, std::vector<uint64_t>& out); + +class LineSplit +{ +public: + LineSplit(const char* data, size_t numBytes) : + m_Data(data), + m_NumBytes(numBytes), + m_NextLineBeg(0), + m_NextLineIndex(0) + { + } + + bool GetNextLine(StrRange& out); + size_t GetNextLineIndex() const { return m_NextLineIndex; } + +private: + const char* const m_Data; + const size_t m_NumBytes; + size_t m_NextLineBeg; + size_t m_NextLineIndex; +}; + +class CsvSplit +{ +public: + static const size_t RANGE_COUNT_MAX = 32; + + void Set(const StrRange& line, size_t maxCount = RANGE_COUNT_MAX); + + const StrRange& GetLine() const { return m_Line; } + + size_t GetCount() const { return m_Count; } + StrRange GetRange(size_t index) const + { + if(index < m_Count) + { + return StrRange { + m_Line.beg + m_Ranges[index * 2], + m_Line.beg + m_Ranges[index * 2 + 1] }; + } + else + { + return StrRange{0, 0}; + } + } + +private: + StrRange m_Line = { nullptr, nullptr }; + size_t m_Count = 0; + size_t m_Ranges[RANGE_COUNT_MAX * 2]; // Pairs of begin-end. +}; + +class CmdLineParser +{ +public: + enum RESULT + { + RESULT_OPT, + RESULT_PARAMETER, + RESULT_END, + RESULT_ERROR, + }; + + CmdLineParser(int argc, char **argv); + CmdLineParser(const char *CmdLine); + + void RegisterOpt(uint32_t Id, char Opt, bool Parameter); + void RegisterOpt(uint32_t Id, const std::string &Opt, bool Parameter); + + RESULT ReadNext(); + uint32_t GetOptId(); + const std::string & GetParameter(); + +private: + struct SHORT_OPT + { + uint32_t Id; + char Opt; + bool Parameter; + + SHORT_OPT(uint32_t Id, char Opt, bool Parameter) : Id(Id), Opt(Opt), Parameter(Parameter) { } + }; + + struct LONG_OPT + { + uint32_t Id; + std::string Opt; + bool Parameter; + + LONG_OPT(uint32_t Id, std::string Opt, bool Parameter) : Id(Id), Opt(Opt), Parameter(Parameter) { } + }; + + char **m_argv; + const char *m_CmdLine; + int m_argc; + size_t m_CmdLineLength; + size_t m_ArgIndex; + + bool ReadNextArg(std::string *OutArg); + + std::vector<SHORT_OPT> m_ShortOpts; + std::vector<LONG_OPT> m_LongOpts; + + SHORT_OPT * FindShortOpt(char Opt); + LONG_OPT * FindLongOpt(const std::string &Opt); + + bool m_InsideMultioption; + std::string m_LastArg; + size_t m_LastArgIndex; + uint32_t m_LastOptId; + std::string m_LastParameter; +}; + +/* +Parses and stores a sequence of ranges. + +Upper range is inclusive. + +Examples: + + "1" -> [ {1, 1} ] + "1,10" -> [ {1, 1}, {10, 10} ] + "2-6" -> [ {2, 6} ] + "-8" -> [ {MIN, 8} ] + "12-" -> [ {12, MAX} ] + "1-10,12,15-" -> [ {1, 10}, {12, 12}, {15, MAX} ] + +TODO: Optimize it: Do sorting and merging while parsing. Do binary search while +reading. +*/ +template<typename T> +class RangeSequence +{ +public: + typedef std::pair<T, T> RangeType; + + void Clear() { m_Ranges.clear(); } + bool Parse(const StrRange& str); + + bool IsEmpty() const { return m_Ranges.empty(); } + size_t GetCount() const { return m_Ranges.size(); } + const RangeType* GetRanges() const { return m_Ranges.data(); } + + bool Includes(T number) const; + +private: + std::vector<RangeType> m_Ranges; +}; + +template<typename T> +bool RangeSequence<T>::Parse(const StrRange& str) +{ + m_Ranges.clear(); + + StrRange currRange = { str.beg, str.beg }; + while(currRange.beg < str.end) + { + currRange.end = currRange.beg + 1; + // Find next ',' or the end. + while(currRange.end < str.end && *currRange.end != ',') + { + ++currRange.end; + } + + // Find '-' within this range. + const char* hyphenPos = currRange.beg; + while(hyphenPos < currRange.end && *hyphenPos != '-') + { + ++hyphenPos; + } + + // No hyphen - single number like '10'. + if(hyphenPos == currRange.end) + { + RangeType range; + if(!StrRangeToUint(currRange, range.first)) + { + return false; + } + range.second = range.first; + m_Ranges.push_back(range); + } + // Hyphen at the end, like '10-'. + else if(hyphenPos + 1 == currRange.end) + { + const StrRange numberRange = { currRange.beg, hyphenPos }; + RangeType range; + if(!StrRangeToUint(numberRange, range.first)) + { + return false; + } + range.second = std::numeric_limits<T>::max(); + m_Ranges.push_back(range); + } + // Hyphen at the beginning, like "-10". + else if(hyphenPos == currRange.beg) + { + const StrRange numberRange = { currRange.beg + 1, currRange.end }; + RangeType range; + range.first = std::numeric_limits<T>::min(); + if(!StrRangeToUint(numberRange, range.second)) + { + return false; + } + m_Ranges.push_back(range); + } + // Hyphen in the middle, like "1-10". + else + { + const StrRange numberRange1 = { currRange.beg, hyphenPos }; + const StrRange numberRange2 = { hyphenPos + 1, currRange.end }; + RangeType range; + if(!StrRangeToUint(numberRange1, range.first) || + !StrRangeToUint(numberRange2, range.second) || + range.second < range.first) + { + return false; + } + m_Ranges.push_back(range); + } + + // Skip ',' + currRange.beg = currRange.end + 1; + } + + return true; +} + +template<typename T> +bool RangeSequence<T>::Includes(T number) const +{ + for(const auto& it : m_Ranges) + { + if(number >= it.first && number <= it.second) + { + return true; + } + } + return false; +} + +/* +class RandomNumberGenerator +{ +public: + RandomNumberGenerator() : m_Value{GetTickCount()} {} + RandomNumberGenerator(uint32_t seed) : m_Value{seed} { } + void Seed(uint32_t seed) { m_Value = seed; } + uint32_t Generate() { return GenerateFast() ^ (GenerateFast() >> 7); } + +private: + uint32_t m_Value; + uint32_t GenerateFast() { return m_Value = (m_Value * 196314165 + 907633515); } +}; + +enum class CONSOLE_COLOR +{ + INFO, + NORMAL, + WARNING, + ERROR_, + COUNT +}; + +void SetConsoleColor(CONSOLE_COLOR color); + +void PrintMessage(CONSOLE_COLOR color, const char* msg); +void PrintMessage(CONSOLE_COLOR color, const wchar_t* msg); + +inline void Print(const char* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); } +inline void Print(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); } +inline void PrintWarning(const char* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); } +inline void PrintWarning(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); } +inline void PrintError(const char* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); } +inline void PrintError(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); } + +void PrintMessageV(CONSOLE_COLOR color, const char* format, va_list argList); +void PrintMessageV(CONSOLE_COLOR color, const wchar_t* format, va_list argList); +void PrintMessageF(CONSOLE_COLOR color, const char* format, ...); +void PrintMessageF(CONSOLE_COLOR color, const wchar_t* format, ...); +void PrintWarningF(const char* format, ...); +void PrintWarningF(const wchar_t* format, ...); +void PrintErrorF(const char* format, ...); +void PrintErrorF(const wchar_t* format, ...); +*/
diff --git a/src/VmaReplay/Constants.cpp b/src/VmaReplay/Constants.cpp index 6d2b15a..150b346 100644 --- a/src/VmaReplay/Constants.cpp +++ b/src/VmaReplay/Constants.cpp
@@ -1,795 +1,795 @@ -// -// Copyright (c) 2017-2021 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 "Common.h" -#include "Constants.h" - -const int RESULT_EXCEPTION = -1000; -const int RESULT_ERROR_COMMAND_LINE = -1; -const int RESULT_ERROR_SOURCE_FILE = -2; -const int RESULT_ERROR_FORMAT = -3; -const int RESULT_ERROR_VULKAN = -4; - -const char* VMA_FUNCTION_NAMES[] = { - "vmaCreatePool", - "vmaDestroyPool", - "vmaSetAllocationUserData", - "vmaCreateBuffer", - "vmaDestroyBuffer", - "vmaCreateImage", - "vmaDestroyImage", - "vmaFreeMemory", - "vmaFreeMemoryPages", - "vmaCreateLostAllocation", - "vmaAllocateMemory", - "vmaAllocateMemoryPages", - "vmaAllocateMemoryForBuffer", - "vmaAllocateMemoryForImage", - "vmaMapMemory", - "vmaUnmapMemory", - "vmaFlushAllocation", - "vmaInvalidateAllocation", - "vmaTouchAllocation", - "vmaGetAllocationInfo", - "vmaMakePoolAllocationsLost", - "vmaResizeAllocation", - "vmaDefragmentationBegin", - "vmaDefragmentationEnd", - "vmaSetPoolName", -}; -static_assert( - _countof(VMA_FUNCTION_NAMES) == (size_t)VMA_FUNCTION::Count, - "VMA_FUNCTION_NAMES array doesn't match VMA_FUNCTION enum."); - -const char* VMA_POOL_CREATE_FLAG_NAMES[] = { - "VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT", - "VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT", - "VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT", -}; -const uint32_t VMA_POOL_CREATE_FLAG_VALUES[] = { - VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT, - VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT, - VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT, -}; -const size_t VMA_POOL_CREATE_FLAG_COUNT = _countof(VMA_POOL_CREATE_FLAG_NAMES); -static_assert( - _countof(VMA_POOL_CREATE_FLAG_NAMES) == _countof(VMA_POOL_CREATE_FLAG_VALUES), - "VMA_POOL_CREATE_FLAG_NAMES array doesn't match VMA_POOL_CREATE_FLAG_VALUES."); - -const char* VK_BUFFER_CREATE_FLAG_NAMES[] = { - "VK_BUFFER_CREATE_SPARSE_BINDING_BIT", - "VK_BUFFER_CREATE_SPARSE_RESIDENCY_BIT", - "VK_BUFFER_CREATE_SPARSE_ALIASED_BIT", - "VK_BUFFER_CREATE_PROTECTED_BIT", -}; -const uint32_t VK_BUFFER_CREATE_FLAG_VALUES[] = { - VK_BUFFER_CREATE_SPARSE_BINDING_BIT, - VK_BUFFER_CREATE_SPARSE_RESIDENCY_BIT, - VK_BUFFER_CREATE_SPARSE_ALIASED_BIT, - VK_BUFFER_CREATE_PROTECTED_BIT, -}; -const size_t VK_BUFFER_CREATE_FLAG_COUNT = _countof(VK_BUFFER_CREATE_FLAG_NAMES); -static_assert( - _countof(VK_BUFFER_CREATE_FLAG_NAMES) == _countof(VK_BUFFER_CREATE_FLAG_VALUES), - "VK_BUFFER_CREATE_FLAG_NAMES array doesn't match VK_BUFFER_CREATE_FLAG_VALUES."); - -const char* VK_BUFFER_USAGE_FLAG_NAMES[] = { - "VK_BUFFER_USAGE_TRANSFER_SRC_BIT", - "VK_BUFFER_USAGE_TRANSFER_DST_BIT", - "VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT", - "VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT", - "VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT", - "VK_BUFFER_USAGE_STORAGE_BUFFER_BIT", - "VK_BUFFER_USAGE_INDEX_BUFFER_BIT", - "VK_BUFFER_USAGE_VERTEX_BUFFER_BIT", - "VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT", - "VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT", - //"VK_BUFFER_USAGE_RAYTRACING_BIT_NVX", -}; -const uint32_t VK_BUFFER_USAGE_FLAG_VALUES[] = { - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, - VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT, - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, - VK_BUFFER_USAGE_INDEX_BUFFER_BIT, - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, - VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT, - //VK_BUFFER_USAGE_RAYTRACING_BIT_NVX, -}; -const size_t VK_BUFFER_USAGE_FLAG_COUNT = _countof(VK_BUFFER_USAGE_FLAG_NAMES); -static_assert( - _countof(VK_BUFFER_USAGE_FLAG_NAMES) == _countof(VK_BUFFER_USAGE_FLAG_VALUES), - "VK_BUFFER_USAGE_FLAG_NAMES array doesn't match VK_BUFFER_USAGE_FLAG_VALUES."); - -const char* VK_SHARING_MODE_NAMES[] = { - "VK_SHARING_MODE_EXCLUSIVE", - "VK_SHARING_MODE_CONCURRENT", -}; -const size_t VK_SHARING_MODE_COUNT = _countof(VK_SHARING_MODE_NAMES); - -const char* VK_IMAGE_CREATE_FLAG_NAMES[] = { - "VK_IMAGE_CREATE_SPARSE_BINDING_BIT", - "VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT", - "VK_IMAGE_CREATE_SPARSE_ALIASED_BIT", - "VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT", - "VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT", - "VK_IMAGE_CREATE_ALIAS_BIT", - "VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT", - "VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT", - "VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT", - "VK_IMAGE_CREATE_EXTENDED_USAGE_BIT", - "VK_IMAGE_CREATE_PROTECTED_BIT", - "VK_IMAGE_CREATE_DISJOINT_BIT", - //"VK_IMAGE_CREATE_CORNER_SAMPLED_BIT_NV", - "VK_IMAGE_CREATE_SAMPLE_LOCATIONS_COMPATIBLE_DEPTH_BIT_EXT", -}; -const uint32_t VK_IMAGE_CREATE_FLAG_VALUES[] = { - VK_IMAGE_CREATE_SPARSE_BINDING_BIT, - VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT, - VK_IMAGE_CREATE_SPARSE_ALIASED_BIT, - VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, - VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT, - VK_IMAGE_CREATE_ALIAS_BIT, - VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT, - VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT, - VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT, - VK_IMAGE_CREATE_EXTENDED_USAGE_BIT, - VK_IMAGE_CREATE_PROTECTED_BIT, - VK_IMAGE_CREATE_DISJOINT_BIT, - //VK_IMAGE_CREATE_CORNER_SAMPLED_BIT_NV, - VK_IMAGE_CREATE_SAMPLE_LOCATIONS_COMPATIBLE_DEPTH_BIT_EXT, -}; -const size_t VK_IMAGE_CREATE_FLAG_COUNT = _countof(VK_IMAGE_CREATE_FLAG_NAMES); -static_assert( - _countof(VK_IMAGE_CREATE_FLAG_NAMES) == _countof(VK_IMAGE_CREATE_FLAG_VALUES), - "VK_IMAGE_CREATE_FLAG_NAMES array doesn't match VK_IMAGE_CREATE_FLAG_VALUES."); - -const char* VK_IMAGE_TYPE_NAMES[] = { - "VK_IMAGE_TYPE_1D", - "VK_IMAGE_TYPE_2D", - "VK_IMAGE_TYPE_3D", -}; -const size_t VK_IMAGE_TYPE_COUNT = _countof(VK_IMAGE_TYPE_NAMES); - -const char* VK_FORMAT_NAMES[] = { - "VK_FORMAT_UNDEFINED", - "VK_FORMAT_R4G4_UNORM_PACK8", - "VK_FORMAT_R4G4B4A4_UNORM_PACK16", - "VK_FORMAT_B4G4R4A4_UNORM_PACK16", - "VK_FORMAT_R5G6B5_UNORM_PACK16", - "VK_FORMAT_B5G6R5_UNORM_PACK16", - "VK_FORMAT_R5G5B5A1_UNORM_PACK16", - "VK_FORMAT_B5G5R5A1_UNORM_PACK16", - "VK_FORMAT_A1R5G5B5_UNORM_PACK16", - "VK_FORMAT_R8_UNORM", - "VK_FORMAT_R8_SNORM", - "VK_FORMAT_R8_USCALED", - "VK_FORMAT_R8_SSCALED", - "VK_FORMAT_R8_UINT", - "VK_FORMAT_R8_SINT", - "VK_FORMAT_R8_SRGB", - "VK_FORMAT_R8G8_UNORM", - "VK_FORMAT_R8G8_SNORM", - "VK_FORMAT_R8G8_USCALED", - "VK_FORMAT_R8G8_SSCALED", - "VK_FORMAT_R8G8_UINT", - "VK_FORMAT_R8G8_SINT", - "VK_FORMAT_R8G8_SRGB", - "VK_FORMAT_R8G8B8_UNORM", - "VK_FORMAT_R8G8B8_SNORM", - "VK_FORMAT_R8G8B8_USCALED", - "VK_FORMAT_R8G8B8_SSCALED", - "VK_FORMAT_R8G8B8_UINT", - "VK_FORMAT_R8G8B8_SINT", - "VK_FORMAT_R8G8B8_SRGB", - "VK_FORMAT_B8G8R8_UNORM", - "VK_FORMAT_B8G8R8_SNORM", - "VK_FORMAT_B8G8R8_USCALED", - "VK_FORMAT_B8G8R8_SSCALED", - "VK_FORMAT_B8G8R8_UINT", - "VK_FORMAT_B8G8R8_SINT", - "VK_FORMAT_B8G8R8_SRGB", - "VK_FORMAT_R8G8B8A8_UNORM", - "VK_FORMAT_R8G8B8A8_SNORM", - "VK_FORMAT_R8G8B8A8_USCALED", - "VK_FORMAT_R8G8B8A8_SSCALED", - "VK_FORMAT_R8G8B8A8_UINT", - "VK_FORMAT_R8G8B8A8_SINT", - "VK_FORMAT_R8G8B8A8_SRGB", - "VK_FORMAT_B8G8R8A8_UNORM", - "VK_FORMAT_B8G8R8A8_SNORM", - "VK_FORMAT_B8G8R8A8_USCALED", - "VK_FORMAT_B8G8R8A8_SSCALED", - "VK_FORMAT_B8G8R8A8_UINT", - "VK_FORMAT_B8G8R8A8_SINT", - "VK_FORMAT_B8G8R8A8_SRGB", - "VK_FORMAT_A8B8G8R8_UNORM_PACK32", - "VK_FORMAT_A8B8G8R8_SNORM_PACK32", - "VK_FORMAT_A8B8G8R8_USCALED_PACK32", - "VK_FORMAT_A8B8G8R8_SSCALED_PACK32", - "VK_FORMAT_A8B8G8R8_UINT_PACK32", - "VK_FORMAT_A8B8G8R8_SINT_PACK32", - "VK_FORMAT_A8B8G8R8_SRGB_PACK32", - "VK_FORMAT_A2R10G10B10_UNORM_PACK32", - "VK_FORMAT_A2R10G10B10_SNORM_PACK32", - "VK_FORMAT_A2R10G10B10_USCALED_PACK32", - "VK_FORMAT_A2R10G10B10_SSCALED_PACK32", - "VK_FORMAT_A2R10G10B10_UINT_PACK32", - "VK_FORMAT_A2R10G10B10_SINT_PACK32", - "VK_FORMAT_A2B10G10R10_UNORM_PACK32", - "VK_FORMAT_A2B10G10R10_SNORM_PACK32", - "VK_FORMAT_A2B10G10R10_USCALED_PACK32", - "VK_FORMAT_A2B10G10R10_SSCALED_PACK32", - "VK_FORMAT_A2B10G10R10_UINT_PACK32", - "VK_FORMAT_A2B10G10R10_SINT_PACK32", - "VK_FORMAT_R16_UNORM", - "VK_FORMAT_R16_SNORM", - "VK_FORMAT_R16_USCALED", - "VK_FORMAT_R16_SSCALED", - "VK_FORMAT_R16_UINT", - "VK_FORMAT_R16_SINT", - "VK_FORMAT_R16_SFLOAT", - "VK_FORMAT_R16G16_UNORM", - "VK_FORMAT_R16G16_SNORM", - "VK_FORMAT_R16G16_USCALED", - "VK_FORMAT_R16G16_SSCALED", - "VK_FORMAT_R16G16_UINT", - "VK_FORMAT_R16G16_SINT", - "VK_FORMAT_R16G16_SFLOAT", - "VK_FORMAT_R16G16B16_UNORM", - "VK_FORMAT_R16G16B16_SNORM", - "VK_FORMAT_R16G16B16_USCALED", - "VK_FORMAT_R16G16B16_SSCALED", - "VK_FORMAT_R16G16B16_UINT", - "VK_FORMAT_R16G16B16_SINT", - "VK_FORMAT_R16G16B16_SFLOAT", - "VK_FORMAT_R16G16B16A16_UNORM", - "VK_FORMAT_R16G16B16A16_SNORM", - "VK_FORMAT_R16G16B16A16_USCALED", - "VK_FORMAT_R16G16B16A16_SSCALED", - "VK_FORMAT_R16G16B16A16_UINT", - "VK_FORMAT_R16G16B16A16_SINT", - "VK_FORMAT_R16G16B16A16_SFLOAT", - "VK_FORMAT_R32_UINT", - "VK_FORMAT_R32_SINT", - "VK_FORMAT_R32_SFLOAT", - "VK_FORMAT_R32G32_UINT", - "VK_FORMAT_R32G32_SINT", - "VK_FORMAT_R32G32_SFLOAT", - "VK_FORMAT_R32G32B32_UINT", - "VK_FORMAT_R32G32B32_SINT", - "VK_FORMAT_R32G32B32_SFLOAT", - "VK_FORMAT_R32G32B32A32_UINT", - "VK_FORMAT_R32G32B32A32_SINT", - "VK_FORMAT_R32G32B32A32_SFLOAT", - "VK_FORMAT_R64_UINT", - "VK_FORMAT_R64_SINT", - "VK_FORMAT_R64_SFLOAT", - "VK_FORMAT_R64G64_UINT", - "VK_FORMAT_R64G64_SINT", - "VK_FORMAT_R64G64_SFLOAT", - "VK_FORMAT_R64G64B64_UINT", - "VK_FORMAT_R64G64B64_SINT", - "VK_FORMAT_R64G64B64_SFLOAT", - "VK_FORMAT_R64G64B64A64_UINT", - "VK_FORMAT_R64G64B64A64_SINT", - "VK_FORMAT_R64G64B64A64_SFLOAT", - "VK_FORMAT_B10G11R11_UFLOAT_PACK32", - "VK_FORMAT_E5B9G9R9_UFLOAT_PACK32", - "VK_FORMAT_D16_UNORM", - "VK_FORMAT_X8_D24_UNORM_PACK32", - "VK_FORMAT_D32_SFLOAT", - "VK_FORMAT_S8_UINT", - "VK_FORMAT_D16_UNORM_S8_UINT", - "VK_FORMAT_D24_UNORM_S8_UINT", - "VK_FORMAT_D32_SFLOAT_S8_UINT", - "VK_FORMAT_BC1_RGB_UNORM_BLOCK", - "VK_FORMAT_BC1_RGB_SRGB_BLOCK", - "VK_FORMAT_BC1_RGBA_UNORM_BLOCK", - "VK_FORMAT_BC1_RGBA_SRGB_BLOCK", - "VK_FORMAT_BC2_UNORM_BLOCK", - "VK_FORMAT_BC2_SRGB_BLOCK", - "VK_FORMAT_BC3_UNORM_BLOCK", - "VK_FORMAT_BC3_SRGB_BLOCK", - "VK_FORMAT_BC4_UNORM_BLOCK", - "VK_FORMAT_BC4_SNORM_BLOCK", - "VK_FORMAT_BC5_UNORM_BLOCK", - "VK_FORMAT_BC5_SNORM_BLOCK", - "VK_FORMAT_BC6H_UFLOAT_BLOCK", - "VK_FORMAT_BC6H_SFLOAT_BLOCK", - "VK_FORMAT_BC7_UNORM_BLOCK", - "VK_FORMAT_BC7_SRGB_BLOCK", - "VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK", - "VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK", - "VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK", - "VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK", - "VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK", - "VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK", - "VK_FORMAT_EAC_R11_UNORM_BLOCK", - "VK_FORMAT_EAC_R11_SNORM_BLOCK", - "VK_FORMAT_EAC_R11G11_UNORM_BLOCK", - "VK_FORMAT_EAC_R11G11_SNORM_BLOCK", - "VK_FORMAT_ASTC_4x4_UNORM_BLOCK", - "VK_FORMAT_ASTC_4x4_SRGB_BLOCK", - "VK_FORMAT_ASTC_5x4_UNORM_BLOCK", - "VK_FORMAT_ASTC_5x4_SRGB_BLOCK", - "VK_FORMAT_ASTC_5x5_UNORM_BLOCK", - "VK_FORMAT_ASTC_5x5_SRGB_BLOCK", - "VK_FORMAT_ASTC_6x5_UNORM_BLOCK", - "VK_FORMAT_ASTC_6x5_SRGB_BLOCK", - "VK_FORMAT_ASTC_6x6_UNORM_BLOCK", - "VK_FORMAT_ASTC_6x6_SRGB_BLOCK", - "VK_FORMAT_ASTC_8x5_UNORM_BLOCK", - "VK_FORMAT_ASTC_8x5_SRGB_BLOCK", - "VK_FORMAT_ASTC_8x6_UNORM_BLOCK", - "VK_FORMAT_ASTC_8x6_SRGB_BLOCK", - "VK_FORMAT_ASTC_8x8_UNORM_BLOCK", - "VK_FORMAT_ASTC_8x8_SRGB_BLOCK", - "VK_FORMAT_ASTC_10x5_UNORM_BLOCK", - "VK_FORMAT_ASTC_10x5_SRGB_BLOCK", - "VK_FORMAT_ASTC_10x6_UNORM_BLOCK", - "VK_FORMAT_ASTC_10x6_SRGB_BLOCK", - "VK_FORMAT_ASTC_10x8_UNORM_BLOCK", - "VK_FORMAT_ASTC_10x8_SRGB_BLOCK", - "VK_FORMAT_ASTC_10x10_UNORM_BLOCK", - "VK_FORMAT_ASTC_10x10_SRGB_BLOCK", - "VK_FORMAT_ASTC_12x10_UNORM_BLOCK", - "VK_FORMAT_ASTC_12x10_SRGB_BLOCK", - "VK_FORMAT_ASTC_12x12_UNORM_BLOCK", - "VK_FORMAT_ASTC_12x12_SRGB_BLOCK", - "VK_FORMAT_G8B8G8R8_422_UNORM", - "VK_FORMAT_B8G8R8G8_422_UNORM", - "VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM", - "VK_FORMAT_G8_B8R8_2PLANE_420_UNORM", - "VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM", - "VK_FORMAT_G8_B8R8_2PLANE_422_UNORM", - "VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM", - "VK_FORMAT_R10X6_UNORM_PACK16", - "VK_FORMAT_R10X6G10X6_UNORM_2PACK16", - "VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16", - "VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16", - "VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16", - "VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16", - "VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16", - "VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16", - "VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16", - "VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16", - "VK_FORMAT_R12X4_UNORM_PACK16", - "VK_FORMAT_R12X4G12X4_UNORM_2PACK16", - "VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16", - "VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16", - "VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16", - "VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16", - "VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16", - "VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16", - "VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16", - "VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16", - "VK_FORMAT_G16B16G16R16_422_UNORM", - "VK_FORMAT_B16G16R16G16_422_UNORM", - "VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM", - "VK_FORMAT_G16_B16R16_2PLANE_420_UNORM", - "VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM", - "VK_FORMAT_G16_B16R16_2PLANE_422_UNORM", - "VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM", - "VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG", - "VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG", - "VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG", - "VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG", - "VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG", - "VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG", - "VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG", - "VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG", -}; -const uint32_t VK_FORMAT_VALUES[] = { - VK_FORMAT_UNDEFINED, - VK_FORMAT_R4G4_UNORM_PACK8, - VK_FORMAT_R4G4B4A4_UNORM_PACK16, - VK_FORMAT_B4G4R4A4_UNORM_PACK16, - VK_FORMAT_R5G6B5_UNORM_PACK16, - VK_FORMAT_B5G6R5_UNORM_PACK16, - VK_FORMAT_R5G5B5A1_UNORM_PACK16, - VK_FORMAT_B5G5R5A1_UNORM_PACK16, - VK_FORMAT_A1R5G5B5_UNORM_PACK16, - VK_FORMAT_R8_UNORM, - VK_FORMAT_R8_SNORM, - VK_FORMAT_R8_USCALED, - VK_FORMAT_R8_SSCALED, - VK_FORMAT_R8_UINT, - VK_FORMAT_R8_SINT, - VK_FORMAT_R8_SRGB, - VK_FORMAT_R8G8_UNORM, - VK_FORMAT_R8G8_SNORM, - VK_FORMAT_R8G8_USCALED, - VK_FORMAT_R8G8_SSCALED, - VK_FORMAT_R8G8_UINT, - VK_FORMAT_R8G8_SINT, - VK_FORMAT_R8G8_SRGB, - VK_FORMAT_R8G8B8_UNORM, - VK_FORMAT_R8G8B8_SNORM, - VK_FORMAT_R8G8B8_USCALED, - VK_FORMAT_R8G8B8_SSCALED, - VK_FORMAT_R8G8B8_UINT, - VK_FORMAT_R8G8B8_SINT, - VK_FORMAT_R8G8B8_SRGB, - VK_FORMAT_B8G8R8_UNORM, - VK_FORMAT_B8G8R8_SNORM, - VK_FORMAT_B8G8R8_USCALED, - VK_FORMAT_B8G8R8_SSCALED, - VK_FORMAT_B8G8R8_UINT, - VK_FORMAT_B8G8R8_SINT, - VK_FORMAT_B8G8R8_SRGB, - VK_FORMAT_R8G8B8A8_UNORM, - VK_FORMAT_R8G8B8A8_SNORM, - VK_FORMAT_R8G8B8A8_USCALED, - VK_FORMAT_R8G8B8A8_SSCALED, - VK_FORMAT_R8G8B8A8_UINT, - VK_FORMAT_R8G8B8A8_SINT, - VK_FORMAT_R8G8B8A8_SRGB, - VK_FORMAT_B8G8R8A8_UNORM, - VK_FORMAT_B8G8R8A8_SNORM, - VK_FORMAT_B8G8R8A8_USCALED, - VK_FORMAT_B8G8R8A8_SSCALED, - VK_FORMAT_B8G8R8A8_UINT, - VK_FORMAT_B8G8R8A8_SINT, - VK_FORMAT_B8G8R8A8_SRGB, - VK_FORMAT_A8B8G8R8_UNORM_PACK32, - VK_FORMAT_A8B8G8R8_SNORM_PACK32, - VK_FORMAT_A8B8G8R8_USCALED_PACK32, - VK_FORMAT_A8B8G8R8_SSCALED_PACK32, - VK_FORMAT_A8B8G8R8_UINT_PACK32, - VK_FORMAT_A8B8G8R8_SINT_PACK32, - VK_FORMAT_A8B8G8R8_SRGB_PACK32, - VK_FORMAT_A2R10G10B10_UNORM_PACK32, - VK_FORMAT_A2R10G10B10_SNORM_PACK32, - VK_FORMAT_A2R10G10B10_USCALED_PACK32, - VK_FORMAT_A2R10G10B10_SSCALED_PACK32, - VK_FORMAT_A2R10G10B10_UINT_PACK32, - VK_FORMAT_A2R10G10B10_SINT_PACK32, - VK_FORMAT_A2B10G10R10_UNORM_PACK32, - VK_FORMAT_A2B10G10R10_SNORM_PACK32, - VK_FORMAT_A2B10G10R10_USCALED_PACK32, - VK_FORMAT_A2B10G10R10_SSCALED_PACK32, - VK_FORMAT_A2B10G10R10_UINT_PACK32, - VK_FORMAT_A2B10G10R10_SINT_PACK32, - VK_FORMAT_R16_UNORM, - VK_FORMAT_R16_SNORM, - VK_FORMAT_R16_USCALED, - VK_FORMAT_R16_SSCALED, - VK_FORMAT_R16_UINT, - VK_FORMAT_R16_SINT, - VK_FORMAT_R16_SFLOAT, - VK_FORMAT_R16G16_UNORM, - VK_FORMAT_R16G16_SNORM, - VK_FORMAT_R16G16_USCALED, - VK_FORMAT_R16G16_SSCALED, - VK_FORMAT_R16G16_UINT, - VK_FORMAT_R16G16_SINT, - VK_FORMAT_R16G16_SFLOAT, - VK_FORMAT_R16G16B16_UNORM, - VK_FORMAT_R16G16B16_SNORM, - VK_FORMAT_R16G16B16_USCALED, - VK_FORMAT_R16G16B16_SSCALED, - VK_FORMAT_R16G16B16_UINT, - VK_FORMAT_R16G16B16_SINT, - VK_FORMAT_R16G16B16_SFLOAT, - VK_FORMAT_R16G16B16A16_UNORM, - VK_FORMAT_R16G16B16A16_SNORM, - VK_FORMAT_R16G16B16A16_USCALED, - VK_FORMAT_R16G16B16A16_SSCALED, - VK_FORMAT_R16G16B16A16_UINT, - VK_FORMAT_R16G16B16A16_SINT, - VK_FORMAT_R16G16B16A16_SFLOAT, - VK_FORMAT_R32_UINT, - VK_FORMAT_R32_SINT, - VK_FORMAT_R32_SFLOAT, - VK_FORMAT_R32G32_UINT, - VK_FORMAT_R32G32_SINT, - VK_FORMAT_R32G32_SFLOAT, - VK_FORMAT_R32G32B32_UINT, - VK_FORMAT_R32G32B32_SINT, - VK_FORMAT_R32G32B32_SFLOAT, - VK_FORMAT_R32G32B32A32_UINT, - VK_FORMAT_R32G32B32A32_SINT, - VK_FORMAT_R32G32B32A32_SFLOAT, - VK_FORMAT_R64_UINT, - VK_FORMAT_R64_SINT, - VK_FORMAT_R64_SFLOAT, - VK_FORMAT_R64G64_UINT, - VK_FORMAT_R64G64_SINT, - VK_FORMAT_R64G64_SFLOAT, - VK_FORMAT_R64G64B64_UINT, - VK_FORMAT_R64G64B64_SINT, - VK_FORMAT_R64G64B64_SFLOAT, - VK_FORMAT_R64G64B64A64_UINT, - VK_FORMAT_R64G64B64A64_SINT, - VK_FORMAT_R64G64B64A64_SFLOAT, - VK_FORMAT_B10G11R11_UFLOAT_PACK32, - VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, - VK_FORMAT_D16_UNORM, - VK_FORMAT_X8_D24_UNORM_PACK32, - VK_FORMAT_D32_SFLOAT, - VK_FORMAT_S8_UINT, - VK_FORMAT_D16_UNORM_S8_UINT, - VK_FORMAT_D24_UNORM_S8_UINT, - VK_FORMAT_D32_SFLOAT_S8_UINT, - VK_FORMAT_BC1_RGB_UNORM_BLOCK, - VK_FORMAT_BC1_RGB_SRGB_BLOCK, - VK_FORMAT_BC1_RGBA_UNORM_BLOCK, - VK_FORMAT_BC1_RGBA_SRGB_BLOCK, - VK_FORMAT_BC2_UNORM_BLOCK, - VK_FORMAT_BC2_SRGB_BLOCK, - VK_FORMAT_BC3_UNORM_BLOCK, - VK_FORMAT_BC3_SRGB_BLOCK, - VK_FORMAT_BC4_UNORM_BLOCK, - VK_FORMAT_BC4_SNORM_BLOCK, - VK_FORMAT_BC5_UNORM_BLOCK, - VK_FORMAT_BC5_SNORM_BLOCK, - VK_FORMAT_BC6H_UFLOAT_BLOCK, - VK_FORMAT_BC6H_SFLOAT_BLOCK, - VK_FORMAT_BC7_UNORM_BLOCK, - VK_FORMAT_BC7_SRGB_BLOCK, - VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, - VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, - VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, - VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, - VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, - VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, - VK_FORMAT_EAC_R11_UNORM_BLOCK, - VK_FORMAT_EAC_R11_SNORM_BLOCK, - VK_FORMAT_EAC_R11G11_UNORM_BLOCK, - VK_FORMAT_EAC_R11G11_SNORM_BLOCK, - VK_FORMAT_ASTC_4x4_UNORM_BLOCK, - VK_FORMAT_ASTC_4x4_SRGB_BLOCK, - VK_FORMAT_ASTC_5x4_UNORM_BLOCK, - VK_FORMAT_ASTC_5x4_SRGB_BLOCK, - VK_FORMAT_ASTC_5x5_UNORM_BLOCK, - VK_FORMAT_ASTC_5x5_SRGB_BLOCK, - VK_FORMAT_ASTC_6x5_UNORM_BLOCK, - VK_FORMAT_ASTC_6x5_SRGB_BLOCK, - VK_FORMAT_ASTC_6x6_UNORM_BLOCK, - VK_FORMAT_ASTC_6x6_SRGB_BLOCK, - VK_FORMAT_ASTC_8x5_UNORM_BLOCK, - VK_FORMAT_ASTC_8x5_SRGB_BLOCK, - VK_FORMAT_ASTC_8x6_UNORM_BLOCK, - VK_FORMAT_ASTC_8x6_SRGB_BLOCK, - VK_FORMAT_ASTC_8x8_UNORM_BLOCK, - VK_FORMAT_ASTC_8x8_SRGB_BLOCK, - VK_FORMAT_ASTC_10x5_UNORM_BLOCK, - VK_FORMAT_ASTC_10x5_SRGB_BLOCK, - VK_FORMAT_ASTC_10x6_UNORM_BLOCK, - VK_FORMAT_ASTC_10x6_SRGB_BLOCK, - VK_FORMAT_ASTC_10x8_UNORM_BLOCK, - VK_FORMAT_ASTC_10x8_SRGB_BLOCK, - VK_FORMAT_ASTC_10x10_UNORM_BLOCK, - VK_FORMAT_ASTC_10x10_SRGB_BLOCK, - VK_FORMAT_ASTC_12x10_UNORM_BLOCK, - VK_FORMAT_ASTC_12x10_SRGB_BLOCK, - VK_FORMAT_ASTC_12x12_UNORM_BLOCK, - VK_FORMAT_ASTC_12x12_SRGB_BLOCK, - VK_FORMAT_G8B8G8R8_422_UNORM, - VK_FORMAT_B8G8R8G8_422_UNORM, - VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, - VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, - VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM, - VK_FORMAT_G8_B8R8_2PLANE_422_UNORM, - VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM, - VK_FORMAT_R10X6_UNORM_PACK16, - VK_FORMAT_R10X6G10X6_UNORM_2PACK16, - VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16, - VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, - VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, - VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, - VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, - VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, - VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, - VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, - VK_FORMAT_R12X4_UNORM_PACK16, - VK_FORMAT_R12X4G12X4_UNORM_2PACK16, - VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16, - VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, - VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, - VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, - VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, - VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, - VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, - VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, - VK_FORMAT_G16B16G16R16_422_UNORM, - VK_FORMAT_B16G16R16G16_422_UNORM, - VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, - VK_FORMAT_G16_B16R16_2PLANE_420_UNORM, - VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM, - VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, - VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM, - VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG, - VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG, - VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG, - VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG, - VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG, - VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG, - VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG, - VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG, -}; -const size_t VK_FORMAT_COUNT = _countof(VK_FORMAT_NAMES); -static_assert( - _countof(VK_FORMAT_NAMES) == _countof(VK_FORMAT_VALUES), - "VK_FORMAT_NAMES array doesn't match VK_FORMAT_VALUES."); - -const char* VK_SAMPLE_COUNT_NAMES[] = { - "VK_SAMPLE_COUNT_1_BIT", - "VK_SAMPLE_COUNT_2_BIT", - "VK_SAMPLE_COUNT_4_BIT", - "VK_SAMPLE_COUNT_8_BIT", - "VK_SAMPLE_COUNT_16_BIT", - "VK_SAMPLE_COUNT_32_BIT", - "VK_SAMPLE_COUNT_64_BIT", -}; -const uint32_t VK_SAMPLE_COUNT_VALUES[] = { - VK_SAMPLE_COUNT_1_BIT, - VK_SAMPLE_COUNT_2_BIT, - VK_SAMPLE_COUNT_4_BIT, - VK_SAMPLE_COUNT_8_BIT, - VK_SAMPLE_COUNT_16_BIT, - VK_SAMPLE_COUNT_32_BIT, - VK_SAMPLE_COUNT_64_BIT, -}; -const size_t VK_SAMPLE_COUNT_COUNT = _countof(VK_SAMPLE_COUNT_NAMES); -static_assert( - _countof(VK_SAMPLE_COUNT_NAMES) == _countof(VK_SAMPLE_COUNT_VALUES), - "VK_SAMPLE_COUNT_NAMES array doesn't match VK_SAMPLE_COUNT_VALUES."); - -const char* VK_IMAGE_TILING_NAMES[] = { - "VK_IMAGE_TILING_OPTIMAL", - "VK_IMAGE_TILING_LINEAR", -}; -const size_t VK_IMAGE_TILING_COUNT = _countof(VK_IMAGE_TILING_NAMES); - -const char* VK_IMAGE_USAGE_FLAG_NAMES[] = { - "VK_IMAGE_USAGE_TRANSFER_SRC_BIT", - "VK_IMAGE_USAGE_TRANSFER_DST_BIT", - "VK_IMAGE_USAGE_SAMPLED_BIT", - "VK_IMAGE_USAGE_STORAGE_BIT", - "VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT", - "VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT", - "VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT", - "VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT", - //"VK_IMAGE_USAGE_SHADING_RATE_IMAGE_BIT_NV", -}; -const uint32_t VK_IMAGE_USAGE_FLAG_VALUES[] = { - VK_IMAGE_USAGE_TRANSFER_SRC_BIT, - VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_USAGE_STORAGE_BIT, - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, - VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, - VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT, - VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT, - //VK_IMAGE_USAGE_SHADING_RATE_IMAGE_BIT_NV, -}; -const size_t VK_IMAGE_USAGE_FLAG_COUNT = _countof(VK_IMAGE_USAGE_FLAG_NAMES); -static_assert( - _countof(VK_IMAGE_USAGE_FLAG_NAMES) == _countof(VK_IMAGE_USAGE_FLAG_VALUES), - "VK_IMAGE_USAGE_FLAG_NAMES array doesn't match VK_IMAGE_USAGE_FLAG_VALUES."); - -const char* VK_IMAGE_LAYOUT_NAMES[] = { - "VK_IMAGE_LAYOUT_UNDEFINED", - "VK_IMAGE_LAYOUT_GENERAL", - "VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL", - "VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL", - "VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL", - "VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL", - "VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL", - "VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL", - "VK_IMAGE_LAYOUT_PREINITIALIZED", - "VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL", - "VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL", - "VK_IMAGE_LAYOUT_PRESENT_SRC_KHR", - "VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR", - //"VK_IMAGE_LAYOUT_SHADING_RATE_OPTIMAL_NV", -}; -const uint32_t VK_IMAGE_LAYOUT_VALUES[] = { - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_GENERAL, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_LAYOUT_PREINITIALIZED, - VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL, - VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL, - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, - //VK_IMAGE_LAYOUT_SHADING_RATE_OPTIMAL_NV, -}; -const size_t VK_IMAGE_LAYOUT_COUNT = _countof(VK_IMAGE_LAYOUT_NAMES); -static_assert( - _countof(VK_IMAGE_LAYOUT_NAMES) == _countof(VK_IMAGE_LAYOUT_VALUES), - "VK_IMAGE_LAYOUT_NAMES array doesn't match VK_IMAGE_LAYOUT_VALUES."); - -const char* VMA_ALLOCATION_CREATE_FLAG_NAMES[] = { - "VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT", - "VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT", - "VMA_ALLOCATION_CREATE_MAPPED_BIT", - "VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT", - "VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT", - "VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT", - "VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT", - "VMA_ALLOCATION_CREATE_DONT_BIND_BIT", - "VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT", - "VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT", - "VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT", - "VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT", -}; -const uint32_t VMA_ALLOCATION_CREATE_FLAG_VALUES[] = { - VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, - VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT, - VMA_ALLOCATION_CREATE_MAPPED_BIT, - VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT, - VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT, - VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT, - VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT, - VMA_ALLOCATION_CREATE_DONT_BIND_BIT, - VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT, - VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT, - VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT, - VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT, -}; -const size_t VMA_ALLOCATION_CREATE_FLAG_COUNT = _countof(VMA_ALLOCATION_CREATE_FLAG_NAMES); -static_assert( - _countof(VMA_ALLOCATION_CREATE_FLAG_NAMES) == _countof(VMA_ALLOCATION_CREATE_FLAG_VALUES), - "VMA_ALLOCATION_CREATE_FLAG_NAMES array doesn't match VMA_ALLOCATION_CREATE_FLAG_VALUES."); - -const char* VMA_MEMORY_USAGE_NAMES[] = { - "VMA_MEMORY_USAGE_UNKNOWN", - "VMA_MEMORY_USAGE_GPU_ONLY", - "VMA_MEMORY_USAGE_CPU_ONLY", - "VMA_MEMORY_USAGE_CPU_TO_GPU", - "VMA_MEMORY_USAGE_GPU_TO_CPU", - "VMA_MEMORY_USAGE_CPU_COPY", - "VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED", -}; -const size_t VMA_MEMORY_USAGE_COUNT = _countof(VMA_MEMORY_USAGE_NAMES); - -const char* VK_MEMORY_PROPERTY_FLAG_NAMES[] = { - "VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT", - "VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT", - "VK_MEMORY_PROPERTY_HOST_COHERENT_BIT", - "VK_MEMORY_PROPERTY_HOST_CACHED_BIT", - "VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT", - "VK_MEMORY_PROPERTY_PROTECTED_BIT", -}; -const uint32_t VK_MEMORY_PROPERTY_FLAG_VALUES[] = { - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - VK_MEMORY_PROPERTY_HOST_CACHED_BIT, - VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT, - VK_MEMORY_PROPERTY_PROTECTED_BIT, -}; -const size_t VK_MEMORY_PROPERTY_FLAG_COUNT = _countof(VK_MEMORY_PROPERTY_FLAG_NAMES); -static_assert( - _countof(VK_MEMORY_PROPERTY_FLAG_NAMES) == _countof(VK_MEMORY_PROPERTY_FLAG_VALUES), - "VK_MEMORY_PROPERTY_FLAG_NAMES array doesn't match VK_MEMORY_PROPERTY_FLAG_VALUES."); +// +// Copyright (c) 2017-2021 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 "Common.h" +#include "Constants.h" + +const int RESULT_EXCEPTION = -1000; +const int RESULT_ERROR_COMMAND_LINE = -1; +const int RESULT_ERROR_SOURCE_FILE = -2; +const int RESULT_ERROR_FORMAT = -3; +const int RESULT_ERROR_VULKAN = -4; + +const char* VMA_FUNCTION_NAMES[] = { + "vmaCreatePool", + "vmaDestroyPool", + "vmaSetAllocationUserData", + "vmaCreateBuffer", + "vmaDestroyBuffer", + "vmaCreateImage", + "vmaDestroyImage", + "vmaFreeMemory", + "vmaFreeMemoryPages", + "vmaCreateLostAllocation", + "vmaAllocateMemory", + "vmaAllocateMemoryPages", + "vmaAllocateMemoryForBuffer", + "vmaAllocateMemoryForImage", + "vmaMapMemory", + "vmaUnmapMemory", + "vmaFlushAllocation", + "vmaInvalidateAllocation", + "vmaTouchAllocation", + "vmaGetAllocationInfo", + "vmaMakePoolAllocationsLost", + "vmaResizeAllocation", + "vmaDefragmentationBegin", + "vmaDefragmentationEnd", + "vmaSetPoolName", +}; +static_assert( + _countof(VMA_FUNCTION_NAMES) == (size_t)VMA_FUNCTION::Count, + "VMA_FUNCTION_NAMES array doesn't match VMA_FUNCTION enum."); + +const char* VMA_POOL_CREATE_FLAG_NAMES[] = { + "VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT", + "VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT", + "VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT", +}; +const uint32_t VMA_POOL_CREATE_FLAG_VALUES[] = { + VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT, + VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT, + VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT, +}; +const size_t VMA_POOL_CREATE_FLAG_COUNT = _countof(VMA_POOL_CREATE_FLAG_NAMES); +static_assert( + _countof(VMA_POOL_CREATE_FLAG_NAMES) == _countof(VMA_POOL_CREATE_FLAG_VALUES), + "VMA_POOL_CREATE_FLAG_NAMES array doesn't match VMA_POOL_CREATE_FLAG_VALUES."); + +const char* VK_BUFFER_CREATE_FLAG_NAMES[] = { + "VK_BUFFER_CREATE_SPARSE_BINDING_BIT", + "VK_BUFFER_CREATE_SPARSE_RESIDENCY_BIT", + "VK_BUFFER_CREATE_SPARSE_ALIASED_BIT", + "VK_BUFFER_CREATE_PROTECTED_BIT", +}; +const uint32_t VK_BUFFER_CREATE_FLAG_VALUES[] = { + VK_BUFFER_CREATE_SPARSE_BINDING_BIT, + VK_BUFFER_CREATE_SPARSE_RESIDENCY_BIT, + VK_BUFFER_CREATE_SPARSE_ALIASED_BIT, + VK_BUFFER_CREATE_PROTECTED_BIT, +}; +const size_t VK_BUFFER_CREATE_FLAG_COUNT = _countof(VK_BUFFER_CREATE_FLAG_NAMES); +static_assert( + _countof(VK_BUFFER_CREATE_FLAG_NAMES) == _countof(VK_BUFFER_CREATE_FLAG_VALUES), + "VK_BUFFER_CREATE_FLAG_NAMES array doesn't match VK_BUFFER_CREATE_FLAG_VALUES."); + +const char* VK_BUFFER_USAGE_FLAG_NAMES[] = { + "VK_BUFFER_USAGE_TRANSFER_SRC_BIT", + "VK_BUFFER_USAGE_TRANSFER_DST_BIT", + "VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT", + "VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT", + "VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT", + "VK_BUFFER_USAGE_STORAGE_BUFFER_BIT", + "VK_BUFFER_USAGE_INDEX_BUFFER_BIT", + "VK_BUFFER_USAGE_VERTEX_BUFFER_BIT", + "VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT", + "VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT", + //"VK_BUFFER_USAGE_RAYTRACING_BIT_NVX", +}; +const uint32_t VK_BUFFER_USAGE_FLAG_VALUES[] = { + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, + VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, + VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT, + //VK_BUFFER_USAGE_RAYTRACING_BIT_NVX, +}; +const size_t VK_BUFFER_USAGE_FLAG_COUNT = _countof(VK_BUFFER_USAGE_FLAG_NAMES); +static_assert( + _countof(VK_BUFFER_USAGE_FLAG_NAMES) == _countof(VK_BUFFER_USAGE_FLAG_VALUES), + "VK_BUFFER_USAGE_FLAG_NAMES array doesn't match VK_BUFFER_USAGE_FLAG_VALUES."); + +const char* VK_SHARING_MODE_NAMES[] = { + "VK_SHARING_MODE_EXCLUSIVE", + "VK_SHARING_MODE_CONCURRENT", +}; +const size_t VK_SHARING_MODE_COUNT = _countof(VK_SHARING_MODE_NAMES); + +const char* VK_IMAGE_CREATE_FLAG_NAMES[] = { + "VK_IMAGE_CREATE_SPARSE_BINDING_BIT", + "VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT", + "VK_IMAGE_CREATE_SPARSE_ALIASED_BIT", + "VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT", + "VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT", + "VK_IMAGE_CREATE_ALIAS_BIT", + "VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT", + "VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT", + "VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT", + "VK_IMAGE_CREATE_EXTENDED_USAGE_BIT", + "VK_IMAGE_CREATE_PROTECTED_BIT", + "VK_IMAGE_CREATE_DISJOINT_BIT", + //"VK_IMAGE_CREATE_CORNER_SAMPLED_BIT_NV", + "VK_IMAGE_CREATE_SAMPLE_LOCATIONS_COMPATIBLE_DEPTH_BIT_EXT", +}; +const uint32_t VK_IMAGE_CREATE_FLAG_VALUES[] = { + VK_IMAGE_CREATE_SPARSE_BINDING_BIT, + VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT, + VK_IMAGE_CREATE_SPARSE_ALIASED_BIT, + VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, + VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT, + VK_IMAGE_CREATE_ALIAS_BIT, + VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT, + VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT, + VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT, + VK_IMAGE_CREATE_EXTENDED_USAGE_BIT, + VK_IMAGE_CREATE_PROTECTED_BIT, + VK_IMAGE_CREATE_DISJOINT_BIT, + //VK_IMAGE_CREATE_CORNER_SAMPLED_BIT_NV, + VK_IMAGE_CREATE_SAMPLE_LOCATIONS_COMPATIBLE_DEPTH_BIT_EXT, +}; +const size_t VK_IMAGE_CREATE_FLAG_COUNT = _countof(VK_IMAGE_CREATE_FLAG_NAMES); +static_assert( + _countof(VK_IMAGE_CREATE_FLAG_NAMES) == _countof(VK_IMAGE_CREATE_FLAG_VALUES), + "VK_IMAGE_CREATE_FLAG_NAMES array doesn't match VK_IMAGE_CREATE_FLAG_VALUES."); + +const char* VK_IMAGE_TYPE_NAMES[] = { + "VK_IMAGE_TYPE_1D", + "VK_IMAGE_TYPE_2D", + "VK_IMAGE_TYPE_3D", +}; +const size_t VK_IMAGE_TYPE_COUNT = _countof(VK_IMAGE_TYPE_NAMES); + +const char* VK_FORMAT_NAMES[] = { + "VK_FORMAT_UNDEFINED", + "VK_FORMAT_R4G4_UNORM_PACK8", + "VK_FORMAT_R4G4B4A4_UNORM_PACK16", + "VK_FORMAT_B4G4R4A4_UNORM_PACK16", + "VK_FORMAT_R5G6B5_UNORM_PACK16", + "VK_FORMAT_B5G6R5_UNORM_PACK16", + "VK_FORMAT_R5G5B5A1_UNORM_PACK16", + "VK_FORMAT_B5G5R5A1_UNORM_PACK16", + "VK_FORMAT_A1R5G5B5_UNORM_PACK16", + "VK_FORMAT_R8_UNORM", + "VK_FORMAT_R8_SNORM", + "VK_FORMAT_R8_USCALED", + "VK_FORMAT_R8_SSCALED", + "VK_FORMAT_R8_UINT", + "VK_FORMAT_R8_SINT", + "VK_FORMAT_R8_SRGB", + "VK_FORMAT_R8G8_UNORM", + "VK_FORMAT_R8G8_SNORM", + "VK_FORMAT_R8G8_USCALED", + "VK_FORMAT_R8G8_SSCALED", + "VK_FORMAT_R8G8_UINT", + "VK_FORMAT_R8G8_SINT", + "VK_FORMAT_R8G8_SRGB", + "VK_FORMAT_R8G8B8_UNORM", + "VK_FORMAT_R8G8B8_SNORM", + "VK_FORMAT_R8G8B8_USCALED", + "VK_FORMAT_R8G8B8_SSCALED", + "VK_FORMAT_R8G8B8_UINT", + "VK_FORMAT_R8G8B8_SINT", + "VK_FORMAT_R8G8B8_SRGB", + "VK_FORMAT_B8G8R8_UNORM", + "VK_FORMAT_B8G8R8_SNORM", + "VK_FORMAT_B8G8R8_USCALED", + "VK_FORMAT_B8G8R8_SSCALED", + "VK_FORMAT_B8G8R8_UINT", + "VK_FORMAT_B8G8R8_SINT", + "VK_FORMAT_B8G8R8_SRGB", + "VK_FORMAT_R8G8B8A8_UNORM", + "VK_FORMAT_R8G8B8A8_SNORM", + "VK_FORMAT_R8G8B8A8_USCALED", + "VK_FORMAT_R8G8B8A8_SSCALED", + "VK_FORMAT_R8G8B8A8_UINT", + "VK_FORMAT_R8G8B8A8_SINT", + "VK_FORMAT_R8G8B8A8_SRGB", + "VK_FORMAT_B8G8R8A8_UNORM", + "VK_FORMAT_B8G8R8A8_SNORM", + "VK_FORMAT_B8G8R8A8_USCALED", + "VK_FORMAT_B8G8R8A8_SSCALED", + "VK_FORMAT_B8G8R8A8_UINT", + "VK_FORMAT_B8G8R8A8_SINT", + "VK_FORMAT_B8G8R8A8_SRGB", + "VK_FORMAT_A8B8G8R8_UNORM_PACK32", + "VK_FORMAT_A8B8G8R8_SNORM_PACK32", + "VK_FORMAT_A8B8G8R8_USCALED_PACK32", + "VK_FORMAT_A8B8G8R8_SSCALED_PACK32", + "VK_FORMAT_A8B8G8R8_UINT_PACK32", + "VK_FORMAT_A8B8G8R8_SINT_PACK32", + "VK_FORMAT_A8B8G8R8_SRGB_PACK32", + "VK_FORMAT_A2R10G10B10_UNORM_PACK32", + "VK_FORMAT_A2R10G10B10_SNORM_PACK32", + "VK_FORMAT_A2R10G10B10_USCALED_PACK32", + "VK_FORMAT_A2R10G10B10_SSCALED_PACK32", + "VK_FORMAT_A2R10G10B10_UINT_PACK32", + "VK_FORMAT_A2R10G10B10_SINT_PACK32", + "VK_FORMAT_A2B10G10R10_UNORM_PACK32", + "VK_FORMAT_A2B10G10R10_SNORM_PACK32", + "VK_FORMAT_A2B10G10R10_USCALED_PACK32", + "VK_FORMAT_A2B10G10R10_SSCALED_PACK32", + "VK_FORMAT_A2B10G10R10_UINT_PACK32", + "VK_FORMAT_A2B10G10R10_SINT_PACK32", + "VK_FORMAT_R16_UNORM", + "VK_FORMAT_R16_SNORM", + "VK_FORMAT_R16_USCALED", + "VK_FORMAT_R16_SSCALED", + "VK_FORMAT_R16_UINT", + "VK_FORMAT_R16_SINT", + "VK_FORMAT_R16_SFLOAT", + "VK_FORMAT_R16G16_UNORM", + "VK_FORMAT_R16G16_SNORM", + "VK_FORMAT_R16G16_USCALED", + "VK_FORMAT_R16G16_SSCALED", + "VK_FORMAT_R16G16_UINT", + "VK_FORMAT_R16G16_SINT", + "VK_FORMAT_R16G16_SFLOAT", + "VK_FORMAT_R16G16B16_UNORM", + "VK_FORMAT_R16G16B16_SNORM", + "VK_FORMAT_R16G16B16_USCALED", + "VK_FORMAT_R16G16B16_SSCALED", + "VK_FORMAT_R16G16B16_UINT", + "VK_FORMAT_R16G16B16_SINT", + "VK_FORMAT_R16G16B16_SFLOAT", + "VK_FORMAT_R16G16B16A16_UNORM", + "VK_FORMAT_R16G16B16A16_SNORM", + "VK_FORMAT_R16G16B16A16_USCALED", + "VK_FORMAT_R16G16B16A16_SSCALED", + "VK_FORMAT_R16G16B16A16_UINT", + "VK_FORMAT_R16G16B16A16_SINT", + "VK_FORMAT_R16G16B16A16_SFLOAT", + "VK_FORMAT_R32_UINT", + "VK_FORMAT_R32_SINT", + "VK_FORMAT_R32_SFLOAT", + "VK_FORMAT_R32G32_UINT", + "VK_FORMAT_R32G32_SINT", + "VK_FORMAT_R32G32_SFLOAT", + "VK_FORMAT_R32G32B32_UINT", + "VK_FORMAT_R32G32B32_SINT", + "VK_FORMAT_R32G32B32_SFLOAT", + "VK_FORMAT_R32G32B32A32_UINT", + "VK_FORMAT_R32G32B32A32_SINT", + "VK_FORMAT_R32G32B32A32_SFLOAT", + "VK_FORMAT_R64_UINT", + "VK_FORMAT_R64_SINT", + "VK_FORMAT_R64_SFLOAT", + "VK_FORMAT_R64G64_UINT", + "VK_FORMAT_R64G64_SINT", + "VK_FORMAT_R64G64_SFLOAT", + "VK_FORMAT_R64G64B64_UINT", + "VK_FORMAT_R64G64B64_SINT", + "VK_FORMAT_R64G64B64_SFLOAT", + "VK_FORMAT_R64G64B64A64_UINT", + "VK_FORMAT_R64G64B64A64_SINT", + "VK_FORMAT_R64G64B64A64_SFLOAT", + "VK_FORMAT_B10G11R11_UFLOAT_PACK32", + "VK_FORMAT_E5B9G9R9_UFLOAT_PACK32", + "VK_FORMAT_D16_UNORM", + "VK_FORMAT_X8_D24_UNORM_PACK32", + "VK_FORMAT_D32_SFLOAT", + "VK_FORMAT_S8_UINT", + "VK_FORMAT_D16_UNORM_S8_UINT", + "VK_FORMAT_D24_UNORM_S8_UINT", + "VK_FORMAT_D32_SFLOAT_S8_UINT", + "VK_FORMAT_BC1_RGB_UNORM_BLOCK", + "VK_FORMAT_BC1_RGB_SRGB_BLOCK", + "VK_FORMAT_BC1_RGBA_UNORM_BLOCK", + "VK_FORMAT_BC1_RGBA_SRGB_BLOCK", + "VK_FORMAT_BC2_UNORM_BLOCK", + "VK_FORMAT_BC2_SRGB_BLOCK", + "VK_FORMAT_BC3_UNORM_BLOCK", + "VK_FORMAT_BC3_SRGB_BLOCK", + "VK_FORMAT_BC4_UNORM_BLOCK", + "VK_FORMAT_BC4_SNORM_BLOCK", + "VK_FORMAT_BC5_UNORM_BLOCK", + "VK_FORMAT_BC5_SNORM_BLOCK", + "VK_FORMAT_BC6H_UFLOAT_BLOCK", + "VK_FORMAT_BC6H_SFLOAT_BLOCK", + "VK_FORMAT_BC7_UNORM_BLOCK", + "VK_FORMAT_BC7_SRGB_BLOCK", + "VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK", + "VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK", + "VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK", + "VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK", + "VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK", + "VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK", + "VK_FORMAT_EAC_R11_UNORM_BLOCK", + "VK_FORMAT_EAC_R11_SNORM_BLOCK", + "VK_FORMAT_EAC_R11G11_UNORM_BLOCK", + "VK_FORMAT_EAC_R11G11_SNORM_BLOCK", + "VK_FORMAT_ASTC_4x4_UNORM_BLOCK", + "VK_FORMAT_ASTC_4x4_SRGB_BLOCK", + "VK_FORMAT_ASTC_5x4_UNORM_BLOCK", + "VK_FORMAT_ASTC_5x4_SRGB_BLOCK", + "VK_FORMAT_ASTC_5x5_UNORM_BLOCK", + "VK_FORMAT_ASTC_5x5_SRGB_BLOCK", + "VK_FORMAT_ASTC_6x5_UNORM_BLOCK", + "VK_FORMAT_ASTC_6x5_SRGB_BLOCK", + "VK_FORMAT_ASTC_6x6_UNORM_BLOCK", + "VK_FORMAT_ASTC_6x6_SRGB_BLOCK", + "VK_FORMAT_ASTC_8x5_UNORM_BLOCK", + "VK_FORMAT_ASTC_8x5_SRGB_BLOCK", + "VK_FORMAT_ASTC_8x6_UNORM_BLOCK", + "VK_FORMAT_ASTC_8x6_SRGB_BLOCK", + "VK_FORMAT_ASTC_8x8_UNORM_BLOCK", + "VK_FORMAT_ASTC_8x8_SRGB_BLOCK", + "VK_FORMAT_ASTC_10x5_UNORM_BLOCK", + "VK_FORMAT_ASTC_10x5_SRGB_BLOCK", + "VK_FORMAT_ASTC_10x6_UNORM_BLOCK", + "VK_FORMAT_ASTC_10x6_SRGB_BLOCK", + "VK_FORMAT_ASTC_10x8_UNORM_BLOCK", + "VK_FORMAT_ASTC_10x8_SRGB_BLOCK", + "VK_FORMAT_ASTC_10x10_UNORM_BLOCK", + "VK_FORMAT_ASTC_10x10_SRGB_BLOCK", + "VK_FORMAT_ASTC_12x10_UNORM_BLOCK", + "VK_FORMAT_ASTC_12x10_SRGB_BLOCK", + "VK_FORMAT_ASTC_12x12_UNORM_BLOCK", + "VK_FORMAT_ASTC_12x12_SRGB_BLOCK", + "VK_FORMAT_G8B8G8R8_422_UNORM", + "VK_FORMAT_B8G8R8G8_422_UNORM", + "VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM", + "VK_FORMAT_G8_B8R8_2PLANE_420_UNORM", + "VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM", + "VK_FORMAT_G8_B8R8_2PLANE_422_UNORM", + "VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM", + "VK_FORMAT_R10X6_UNORM_PACK16", + "VK_FORMAT_R10X6G10X6_UNORM_2PACK16", + "VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16", + "VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16", + "VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16", + "VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16", + "VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16", + "VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16", + "VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16", + "VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16", + "VK_FORMAT_R12X4_UNORM_PACK16", + "VK_FORMAT_R12X4G12X4_UNORM_2PACK16", + "VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16", + "VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16", + "VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16", + "VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16", + "VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16", + "VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16", + "VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16", + "VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16", + "VK_FORMAT_G16B16G16R16_422_UNORM", + "VK_FORMAT_B16G16R16G16_422_UNORM", + "VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM", + "VK_FORMAT_G16_B16R16_2PLANE_420_UNORM", + "VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM", + "VK_FORMAT_G16_B16R16_2PLANE_422_UNORM", + "VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM", + "VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG", + "VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG", + "VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG", + "VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG", + "VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG", + "VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG", + "VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG", + "VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG", +}; +const uint32_t VK_FORMAT_VALUES[] = { + VK_FORMAT_UNDEFINED, + VK_FORMAT_R4G4_UNORM_PACK8, + VK_FORMAT_R4G4B4A4_UNORM_PACK16, + VK_FORMAT_B4G4R4A4_UNORM_PACK16, + VK_FORMAT_R5G6B5_UNORM_PACK16, + VK_FORMAT_B5G6R5_UNORM_PACK16, + VK_FORMAT_R5G5B5A1_UNORM_PACK16, + VK_FORMAT_B5G5R5A1_UNORM_PACK16, + VK_FORMAT_A1R5G5B5_UNORM_PACK16, + VK_FORMAT_R8_UNORM, + VK_FORMAT_R8_SNORM, + VK_FORMAT_R8_USCALED, + VK_FORMAT_R8_SSCALED, + VK_FORMAT_R8_UINT, + VK_FORMAT_R8_SINT, + VK_FORMAT_R8_SRGB, + VK_FORMAT_R8G8_UNORM, + VK_FORMAT_R8G8_SNORM, + VK_FORMAT_R8G8_USCALED, + VK_FORMAT_R8G8_SSCALED, + VK_FORMAT_R8G8_UINT, + VK_FORMAT_R8G8_SINT, + VK_FORMAT_R8G8_SRGB, + VK_FORMAT_R8G8B8_UNORM, + VK_FORMAT_R8G8B8_SNORM, + VK_FORMAT_R8G8B8_USCALED, + VK_FORMAT_R8G8B8_SSCALED, + VK_FORMAT_R8G8B8_UINT, + VK_FORMAT_R8G8B8_SINT, + VK_FORMAT_R8G8B8_SRGB, + VK_FORMAT_B8G8R8_UNORM, + VK_FORMAT_B8G8R8_SNORM, + VK_FORMAT_B8G8R8_USCALED, + VK_FORMAT_B8G8R8_SSCALED, + VK_FORMAT_B8G8R8_UINT, + VK_FORMAT_B8G8R8_SINT, + VK_FORMAT_B8G8R8_SRGB, + VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_R8G8B8A8_SNORM, + VK_FORMAT_R8G8B8A8_USCALED, + VK_FORMAT_R8G8B8A8_SSCALED, + VK_FORMAT_R8G8B8A8_UINT, + VK_FORMAT_R8G8B8A8_SINT, + VK_FORMAT_R8G8B8A8_SRGB, + VK_FORMAT_B8G8R8A8_UNORM, + VK_FORMAT_B8G8R8A8_SNORM, + VK_FORMAT_B8G8R8A8_USCALED, + VK_FORMAT_B8G8R8A8_SSCALED, + VK_FORMAT_B8G8R8A8_UINT, + VK_FORMAT_B8G8R8A8_SINT, + VK_FORMAT_B8G8R8A8_SRGB, + VK_FORMAT_A8B8G8R8_UNORM_PACK32, + VK_FORMAT_A8B8G8R8_SNORM_PACK32, + VK_FORMAT_A8B8G8R8_USCALED_PACK32, + VK_FORMAT_A8B8G8R8_SSCALED_PACK32, + VK_FORMAT_A8B8G8R8_UINT_PACK32, + VK_FORMAT_A8B8G8R8_SINT_PACK32, + VK_FORMAT_A8B8G8R8_SRGB_PACK32, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, + VK_FORMAT_A2R10G10B10_SNORM_PACK32, + VK_FORMAT_A2R10G10B10_USCALED_PACK32, + VK_FORMAT_A2R10G10B10_SSCALED_PACK32, + VK_FORMAT_A2R10G10B10_UINT_PACK32, + VK_FORMAT_A2R10G10B10_SINT_PACK32, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, + VK_FORMAT_A2B10G10R10_SNORM_PACK32, + VK_FORMAT_A2B10G10R10_USCALED_PACK32, + VK_FORMAT_A2B10G10R10_SSCALED_PACK32, + VK_FORMAT_A2B10G10R10_UINT_PACK32, + VK_FORMAT_A2B10G10R10_SINT_PACK32, + VK_FORMAT_R16_UNORM, + VK_FORMAT_R16_SNORM, + VK_FORMAT_R16_USCALED, + VK_FORMAT_R16_SSCALED, + VK_FORMAT_R16_UINT, + VK_FORMAT_R16_SINT, + VK_FORMAT_R16_SFLOAT, + VK_FORMAT_R16G16_UNORM, + VK_FORMAT_R16G16_SNORM, + VK_FORMAT_R16G16_USCALED, + VK_FORMAT_R16G16_SSCALED, + VK_FORMAT_R16G16_UINT, + VK_FORMAT_R16G16_SINT, + VK_FORMAT_R16G16_SFLOAT, + VK_FORMAT_R16G16B16_UNORM, + VK_FORMAT_R16G16B16_SNORM, + VK_FORMAT_R16G16B16_USCALED, + VK_FORMAT_R16G16B16_SSCALED, + VK_FORMAT_R16G16B16_UINT, + VK_FORMAT_R16G16B16_SINT, + VK_FORMAT_R16G16B16_SFLOAT, + VK_FORMAT_R16G16B16A16_UNORM, + VK_FORMAT_R16G16B16A16_SNORM, + VK_FORMAT_R16G16B16A16_USCALED, + VK_FORMAT_R16G16B16A16_SSCALED, + VK_FORMAT_R16G16B16A16_UINT, + VK_FORMAT_R16G16B16A16_SINT, + VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_R32_UINT, + VK_FORMAT_R32_SINT, + VK_FORMAT_R32_SFLOAT, + VK_FORMAT_R32G32_UINT, + VK_FORMAT_R32G32_SINT, + VK_FORMAT_R32G32_SFLOAT, + VK_FORMAT_R32G32B32_UINT, + VK_FORMAT_R32G32B32_SINT, + VK_FORMAT_R32G32B32_SFLOAT, + VK_FORMAT_R32G32B32A32_UINT, + VK_FORMAT_R32G32B32A32_SINT, + VK_FORMAT_R32G32B32A32_SFLOAT, + VK_FORMAT_R64_UINT, + VK_FORMAT_R64_SINT, + VK_FORMAT_R64_SFLOAT, + VK_FORMAT_R64G64_UINT, + VK_FORMAT_R64G64_SINT, + VK_FORMAT_R64G64_SFLOAT, + VK_FORMAT_R64G64B64_UINT, + VK_FORMAT_R64G64B64_SINT, + VK_FORMAT_R64G64B64_SFLOAT, + VK_FORMAT_R64G64B64A64_UINT, + VK_FORMAT_R64G64B64A64_SINT, + VK_FORMAT_R64G64B64A64_SFLOAT, + VK_FORMAT_B10G11R11_UFLOAT_PACK32, + VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, + VK_FORMAT_D16_UNORM, + VK_FORMAT_X8_D24_UNORM_PACK32, + VK_FORMAT_D32_SFLOAT, + VK_FORMAT_S8_UINT, + VK_FORMAT_D16_UNORM_S8_UINT, + VK_FORMAT_D24_UNORM_S8_UINT, + VK_FORMAT_D32_SFLOAT_S8_UINT, + VK_FORMAT_BC1_RGB_UNORM_BLOCK, + VK_FORMAT_BC1_RGB_SRGB_BLOCK, + VK_FORMAT_BC1_RGBA_UNORM_BLOCK, + VK_FORMAT_BC1_RGBA_SRGB_BLOCK, + VK_FORMAT_BC2_UNORM_BLOCK, + VK_FORMAT_BC2_SRGB_BLOCK, + VK_FORMAT_BC3_UNORM_BLOCK, + VK_FORMAT_BC3_SRGB_BLOCK, + VK_FORMAT_BC4_UNORM_BLOCK, + VK_FORMAT_BC4_SNORM_BLOCK, + VK_FORMAT_BC5_UNORM_BLOCK, + VK_FORMAT_BC5_SNORM_BLOCK, + VK_FORMAT_BC6H_UFLOAT_BLOCK, + VK_FORMAT_BC6H_SFLOAT_BLOCK, + VK_FORMAT_BC7_UNORM_BLOCK, + VK_FORMAT_BC7_SRGB_BLOCK, + VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, + VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, + VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, + VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, + VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, + VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, + VK_FORMAT_EAC_R11_UNORM_BLOCK, + VK_FORMAT_EAC_R11_SNORM_BLOCK, + VK_FORMAT_EAC_R11G11_UNORM_BLOCK, + VK_FORMAT_EAC_R11G11_SNORM_BLOCK, + VK_FORMAT_ASTC_4x4_UNORM_BLOCK, + VK_FORMAT_ASTC_4x4_SRGB_BLOCK, + VK_FORMAT_ASTC_5x4_UNORM_BLOCK, + VK_FORMAT_ASTC_5x4_SRGB_BLOCK, + VK_FORMAT_ASTC_5x5_UNORM_BLOCK, + VK_FORMAT_ASTC_5x5_SRGB_BLOCK, + VK_FORMAT_ASTC_6x5_UNORM_BLOCK, + VK_FORMAT_ASTC_6x5_SRGB_BLOCK, + VK_FORMAT_ASTC_6x6_UNORM_BLOCK, + VK_FORMAT_ASTC_6x6_SRGB_BLOCK, + VK_FORMAT_ASTC_8x5_UNORM_BLOCK, + VK_FORMAT_ASTC_8x5_SRGB_BLOCK, + VK_FORMAT_ASTC_8x6_UNORM_BLOCK, + VK_FORMAT_ASTC_8x6_SRGB_BLOCK, + VK_FORMAT_ASTC_8x8_UNORM_BLOCK, + VK_FORMAT_ASTC_8x8_SRGB_BLOCK, + VK_FORMAT_ASTC_10x5_UNORM_BLOCK, + VK_FORMAT_ASTC_10x5_SRGB_BLOCK, + VK_FORMAT_ASTC_10x6_UNORM_BLOCK, + VK_FORMAT_ASTC_10x6_SRGB_BLOCK, + VK_FORMAT_ASTC_10x8_UNORM_BLOCK, + VK_FORMAT_ASTC_10x8_SRGB_BLOCK, + VK_FORMAT_ASTC_10x10_UNORM_BLOCK, + VK_FORMAT_ASTC_10x10_SRGB_BLOCK, + VK_FORMAT_ASTC_12x10_UNORM_BLOCK, + VK_FORMAT_ASTC_12x10_SRGB_BLOCK, + VK_FORMAT_ASTC_12x12_UNORM_BLOCK, + VK_FORMAT_ASTC_12x12_SRGB_BLOCK, + VK_FORMAT_G8B8G8R8_422_UNORM, + VK_FORMAT_B8G8R8G8_422_UNORM, + VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, + VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, + VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM, + VK_FORMAT_G8_B8R8_2PLANE_422_UNORM, + VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM, + VK_FORMAT_R10X6_UNORM_PACK16, + VK_FORMAT_R10X6G10X6_UNORM_2PACK16, + VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16, + VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16, + VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, + VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, + VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, + VK_FORMAT_R12X4_UNORM_PACK16, + VK_FORMAT_R12X4G12X4_UNORM_2PACK16, + VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16, + VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, + VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, + VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, + VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, + VK_FORMAT_G16B16G16R16_422_UNORM, + VK_FORMAT_B16G16R16G16_422_UNORM, + VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, + VK_FORMAT_G16_B16R16_2PLANE_420_UNORM, + VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM, + VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, + VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM, + VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG, + VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG, + VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG, + VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG, + VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG, + VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG, + VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG, + VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG, +}; +const size_t VK_FORMAT_COUNT = _countof(VK_FORMAT_NAMES); +static_assert( + _countof(VK_FORMAT_NAMES) == _countof(VK_FORMAT_VALUES), + "VK_FORMAT_NAMES array doesn't match VK_FORMAT_VALUES."); + +const char* VK_SAMPLE_COUNT_NAMES[] = { + "VK_SAMPLE_COUNT_1_BIT", + "VK_SAMPLE_COUNT_2_BIT", + "VK_SAMPLE_COUNT_4_BIT", + "VK_SAMPLE_COUNT_8_BIT", + "VK_SAMPLE_COUNT_16_BIT", + "VK_SAMPLE_COUNT_32_BIT", + "VK_SAMPLE_COUNT_64_BIT", +}; +const uint32_t VK_SAMPLE_COUNT_VALUES[] = { + VK_SAMPLE_COUNT_1_BIT, + VK_SAMPLE_COUNT_2_BIT, + VK_SAMPLE_COUNT_4_BIT, + VK_SAMPLE_COUNT_8_BIT, + VK_SAMPLE_COUNT_16_BIT, + VK_SAMPLE_COUNT_32_BIT, + VK_SAMPLE_COUNT_64_BIT, +}; +const size_t VK_SAMPLE_COUNT_COUNT = _countof(VK_SAMPLE_COUNT_NAMES); +static_assert( + _countof(VK_SAMPLE_COUNT_NAMES) == _countof(VK_SAMPLE_COUNT_VALUES), + "VK_SAMPLE_COUNT_NAMES array doesn't match VK_SAMPLE_COUNT_VALUES."); + +const char* VK_IMAGE_TILING_NAMES[] = { + "VK_IMAGE_TILING_OPTIMAL", + "VK_IMAGE_TILING_LINEAR", +}; +const size_t VK_IMAGE_TILING_COUNT = _countof(VK_IMAGE_TILING_NAMES); + +const char* VK_IMAGE_USAGE_FLAG_NAMES[] = { + "VK_IMAGE_USAGE_TRANSFER_SRC_BIT", + "VK_IMAGE_USAGE_TRANSFER_DST_BIT", + "VK_IMAGE_USAGE_SAMPLED_BIT", + "VK_IMAGE_USAGE_STORAGE_BIT", + "VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT", + "VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT", + "VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT", + "VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT", + //"VK_IMAGE_USAGE_SHADING_RATE_IMAGE_BIT_NV", +}; +const uint32_t VK_IMAGE_USAGE_FLAG_VALUES[] = { + VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_USAGE_STORAGE_BIT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT, + VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT, + //VK_IMAGE_USAGE_SHADING_RATE_IMAGE_BIT_NV, +}; +const size_t VK_IMAGE_USAGE_FLAG_COUNT = _countof(VK_IMAGE_USAGE_FLAG_NAMES); +static_assert( + _countof(VK_IMAGE_USAGE_FLAG_NAMES) == _countof(VK_IMAGE_USAGE_FLAG_VALUES), + "VK_IMAGE_USAGE_FLAG_NAMES array doesn't match VK_IMAGE_USAGE_FLAG_VALUES."); + +const char* VK_IMAGE_LAYOUT_NAMES[] = { + "VK_IMAGE_LAYOUT_UNDEFINED", + "VK_IMAGE_LAYOUT_GENERAL", + "VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL", + "VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL", + "VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL", + "VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL", + "VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL", + "VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL", + "VK_IMAGE_LAYOUT_PREINITIALIZED", + "VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL", + "VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL", + "VK_IMAGE_LAYOUT_PRESENT_SRC_KHR", + "VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR", + //"VK_IMAGE_LAYOUT_SHADING_RATE_OPTIMAL_NV", +}; +const uint32_t VK_IMAGE_LAYOUT_VALUES[] = { + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_PREINITIALIZED, + VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR, + //VK_IMAGE_LAYOUT_SHADING_RATE_OPTIMAL_NV, +}; +const size_t VK_IMAGE_LAYOUT_COUNT = _countof(VK_IMAGE_LAYOUT_NAMES); +static_assert( + _countof(VK_IMAGE_LAYOUT_NAMES) == _countof(VK_IMAGE_LAYOUT_VALUES), + "VK_IMAGE_LAYOUT_NAMES array doesn't match VK_IMAGE_LAYOUT_VALUES."); + +const char* VMA_ALLOCATION_CREATE_FLAG_NAMES[] = { + "VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT", + "VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT", + "VMA_ALLOCATION_CREATE_MAPPED_BIT", + "VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT", + "VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT", + "VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT", + "VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT", + "VMA_ALLOCATION_CREATE_DONT_BIND_BIT", + "VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT", + "VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT", + "VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT", + "VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT", +}; +const uint32_t VMA_ALLOCATION_CREATE_FLAG_VALUES[] = { + VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, + VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT, + VMA_ALLOCATION_CREATE_MAPPED_BIT, + VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT, + VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT, + VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT, + VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT, + VMA_ALLOCATION_CREATE_DONT_BIND_BIT, + VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT, + VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT, + VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT, + VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT, +}; +const size_t VMA_ALLOCATION_CREATE_FLAG_COUNT = _countof(VMA_ALLOCATION_CREATE_FLAG_NAMES); +static_assert( + _countof(VMA_ALLOCATION_CREATE_FLAG_NAMES) == _countof(VMA_ALLOCATION_CREATE_FLAG_VALUES), + "VMA_ALLOCATION_CREATE_FLAG_NAMES array doesn't match VMA_ALLOCATION_CREATE_FLAG_VALUES."); + +const char* VMA_MEMORY_USAGE_NAMES[] = { + "VMA_MEMORY_USAGE_UNKNOWN", + "VMA_MEMORY_USAGE_GPU_ONLY", + "VMA_MEMORY_USAGE_CPU_ONLY", + "VMA_MEMORY_USAGE_CPU_TO_GPU", + "VMA_MEMORY_USAGE_GPU_TO_CPU", + "VMA_MEMORY_USAGE_CPU_COPY", + "VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED", +}; +const size_t VMA_MEMORY_USAGE_COUNT = _countof(VMA_MEMORY_USAGE_NAMES); + +const char* VK_MEMORY_PROPERTY_FLAG_NAMES[] = { + "VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT", + "VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT", + "VK_MEMORY_PROPERTY_HOST_COHERENT_BIT", + "VK_MEMORY_PROPERTY_HOST_CACHED_BIT", + "VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT", + "VK_MEMORY_PROPERTY_PROTECTED_BIT", +}; +const uint32_t VK_MEMORY_PROPERTY_FLAG_VALUES[] = { + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + VK_MEMORY_PROPERTY_HOST_CACHED_BIT, + VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT, + VK_MEMORY_PROPERTY_PROTECTED_BIT, +}; +const size_t VK_MEMORY_PROPERTY_FLAG_COUNT = _countof(VK_MEMORY_PROPERTY_FLAG_NAMES); +static_assert( + _countof(VK_MEMORY_PROPERTY_FLAG_NAMES) == _countof(VK_MEMORY_PROPERTY_FLAG_VALUES), + "VK_MEMORY_PROPERTY_FLAG_NAMES array doesn't match VK_MEMORY_PROPERTY_FLAG_VALUES.");
diff --git a/src/VmaReplay/Constants.h b/src/VmaReplay/Constants.h index 03b76ff..959681f 100644 --- a/src/VmaReplay/Constants.h +++ b/src/VmaReplay/Constants.h
@@ -1,149 +1,149 @@ -// -// Copyright (c) 2017-2021 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. -// - -#pragma once - -extern const int RESULT_EXCEPTION; -extern const int RESULT_ERROR_COMMAND_LINE; -extern const int RESULT_ERROR_SOURCE_FILE; -extern const int RESULT_ERROR_FORMAT; -extern const int RESULT_ERROR_VULKAN; - -enum CMD_LINE_OPT -{ - CMD_LINE_OPT_VERBOSITY, - CMD_LINE_OPT_ITERATIONS, - CMD_LINE_OPT_LINES, - CMD_LINE_OPT_PHYSICAL_DEVICE, - CMD_LINE_OPT_USER_DATA, - CMD_LINE_OPT_VK_KHR_DEDICATED_ALLOCATION, - CMD_LINE_OPT_VK_EXT_MEMORY_BUDGET, - CMD_LINE_OPT_VK_LAYER_KHRONOS_VALIDATION, - CMD_LINE_OPT_MEM_STATS, - CMD_LINE_OPT_DUMP_STATS_AFTER_LINE, - CMD_LINE_OPT_DEFRAGMENT_AFTER_LINE, - CMD_LINE_OPT_DEFRAGMENTATION_FLAGS, - CMD_LINE_OPT_DUMP_DETAILED_STATS_AFTER_LINE, -}; - -enum class VERBOSITY -{ - MINIMUM = 0, - DEFAULT, - MAXIMUM, - COUNT, -}; - -enum class VULKAN_EXTENSION_REQUEST -{ - DISABLED, - ENABLED, - DEFAULT -}; - -enum class OBJECT_TYPE { BUFFER, IMAGE }; - -enum class VMA_FUNCTION -{ - CreatePool, - DestroyPool, - SetAllocationUserData, - CreateBuffer, - DestroyBuffer, - CreateImage, - DestroyImage, - FreeMemory, - FreeMemoryPages, - CreateLostAllocation, - AllocateMemory, - AllocateMemoryPages, - AllocateMemoryForBuffer, - AllocateMemoryForImage, - MapMemory, - UnmapMemory, - FlushAllocation, - InvalidateAllocation, - TouchAllocation, - GetAllocationInfo, - MakePoolAllocationsLost, - ResizeAllocation, - DefragmentationBegin, - DefragmentationEnd, - SetPoolName, - Count -}; -extern const char* VMA_FUNCTION_NAMES[]; - -extern const char* VMA_POOL_CREATE_FLAG_NAMES[]; -extern const uint32_t VMA_POOL_CREATE_FLAG_VALUES[]; -extern const size_t VMA_POOL_CREATE_FLAG_COUNT; - -extern const char* VK_BUFFER_CREATE_FLAG_NAMES[]; -extern const uint32_t VK_BUFFER_CREATE_FLAG_VALUES[]; -extern const size_t VK_BUFFER_CREATE_FLAG_COUNT; - -extern const char* VK_BUFFER_USAGE_FLAG_NAMES[]; -extern const uint32_t VK_BUFFER_USAGE_FLAG_VALUES[]; -extern const size_t VK_BUFFER_USAGE_FLAG_COUNT; - -extern const char* VK_SHARING_MODE_NAMES[]; -extern const size_t VK_SHARING_MODE_COUNT; - -extern const char* VK_IMAGE_CREATE_FLAG_NAMES[]; -extern const uint32_t VK_IMAGE_CREATE_FLAG_VALUES[]; -extern const size_t VK_IMAGE_CREATE_FLAG_COUNT; - -extern const char* VK_IMAGE_TYPE_NAMES[]; -extern const size_t VK_IMAGE_TYPE_COUNT; - -extern const char* VK_FORMAT_NAMES[]; -extern const uint32_t VK_FORMAT_VALUES[]; -extern const size_t VK_FORMAT_COUNT; - -extern const char* VK_SAMPLE_COUNT_NAMES[]; -extern const uint32_t VK_SAMPLE_COUNT_VALUES[]; -extern const size_t VK_SAMPLE_COUNT_COUNT; - -extern const char* VK_IMAGE_TILING_NAMES[]; -extern const size_t VK_IMAGE_TILING_COUNT; - -extern const char* VK_IMAGE_USAGE_FLAG_NAMES[]; -extern const uint32_t VK_IMAGE_USAGE_FLAG_VALUES[]; -extern const size_t VK_IMAGE_USAGE_FLAG_COUNT; - -extern const char* VK_IMAGE_TILING_NAMES[]; -extern const size_t VK_IMAGE_TILING_COUNT; - -extern const char* VK_IMAGE_LAYOUT_NAMES[]; -extern const uint32_t VK_IMAGE_LAYOUT_VALUES[]; -extern const size_t VK_IMAGE_LAYOUT_COUNT; - -extern const char* VMA_ALLOCATION_CREATE_FLAG_NAMES[]; -extern const uint32_t VMA_ALLOCATION_CREATE_FLAG_VALUES[]; -extern const size_t VMA_ALLOCATION_CREATE_FLAG_COUNT; - -extern const char* VMA_MEMORY_USAGE_NAMES[]; -extern const size_t VMA_MEMORY_USAGE_COUNT; - -extern const char* VK_MEMORY_PROPERTY_FLAG_NAMES[]; -extern const uint32_t VK_MEMORY_PROPERTY_FLAG_VALUES[]; -extern const size_t VK_MEMORY_PROPERTY_FLAG_COUNT; +// +// Copyright (c) 2017-2021 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. +// + +#pragma once + +extern const int RESULT_EXCEPTION; +extern const int RESULT_ERROR_COMMAND_LINE; +extern const int RESULT_ERROR_SOURCE_FILE; +extern const int RESULT_ERROR_FORMAT; +extern const int RESULT_ERROR_VULKAN; + +enum CMD_LINE_OPT +{ + CMD_LINE_OPT_VERBOSITY, + CMD_LINE_OPT_ITERATIONS, + CMD_LINE_OPT_LINES, + CMD_LINE_OPT_PHYSICAL_DEVICE, + CMD_LINE_OPT_USER_DATA, + CMD_LINE_OPT_VK_KHR_DEDICATED_ALLOCATION, + CMD_LINE_OPT_VK_EXT_MEMORY_BUDGET, + CMD_LINE_OPT_VK_LAYER_KHRONOS_VALIDATION, + CMD_LINE_OPT_MEM_STATS, + CMD_LINE_OPT_DUMP_STATS_AFTER_LINE, + CMD_LINE_OPT_DEFRAGMENT_AFTER_LINE, + CMD_LINE_OPT_DEFRAGMENTATION_FLAGS, + CMD_LINE_OPT_DUMP_DETAILED_STATS_AFTER_LINE, +}; + +enum class VERBOSITY +{ + MINIMUM = 0, + DEFAULT, + MAXIMUM, + COUNT, +}; + +enum class VULKAN_EXTENSION_REQUEST +{ + DISABLED, + ENABLED, + DEFAULT +}; + +enum class OBJECT_TYPE { BUFFER, IMAGE }; + +enum class VMA_FUNCTION +{ + CreatePool, + DestroyPool, + SetAllocationUserData, + CreateBuffer, + DestroyBuffer, + CreateImage, + DestroyImage, + FreeMemory, + FreeMemoryPages, + CreateLostAllocation, + AllocateMemory, + AllocateMemoryPages, + AllocateMemoryForBuffer, + AllocateMemoryForImage, + MapMemory, + UnmapMemory, + FlushAllocation, + InvalidateAllocation, + TouchAllocation, + GetAllocationInfo, + MakePoolAllocationsLost, + ResizeAllocation, + DefragmentationBegin, + DefragmentationEnd, + SetPoolName, + Count +}; +extern const char* VMA_FUNCTION_NAMES[]; + +extern const char* VMA_POOL_CREATE_FLAG_NAMES[]; +extern const uint32_t VMA_POOL_CREATE_FLAG_VALUES[]; +extern const size_t VMA_POOL_CREATE_FLAG_COUNT; + +extern const char* VK_BUFFER_CREATE_FLAG_NAMES[]; +extern const uint32_t VK_BUFFER_CREATE_FLAG_VALUES[]; +extern const size_t VK_BUFFER_CREATE_FLAG_COUNT; + +extern const char* VK_BUFFER_USAGE_FLAG_NAMES[]; +extern const uint32_t VK_BUFFER_USAGE_FLAG_VALUES[]; +extern const size_t VK_BUFFER_USAGE_FLAG_COUNT; + +extern const char* VK_SHARING_MODE_NAMES[]; +extern const size_t VK_SHARING_MODE_COUNT; + +extern const char* VK_IMAGE_CREATE_FLAG_NAMES[]; +extern const uint32_t VK_IMAGE_CREATE_FLAG_VALUES[]; +extern const size_t VK_IMAGE_CREATE_FLAG_COUNT; + +extern const char* VK_IMAGE_TYPE_NAMES[]; +extern const size_t VK_IMAGE_TYPE_COUNT; + +extern const char* VK_FORMAT_NAMES[]; +extern const uint32_t VK_FORMAT_VALUES[]; +extern const size_t VK_FORMAT_COUNT; + +extern const char* VK_SAMPLE_COUNT_NAMES[]; +extern const uint32_t VK_SAMPLE_COUNT_VALUES[]; +extern const size_t VK_SAMPLE_COUNT_COUNT; + +extern const char* VK_IMAGE_TILING_NAMES[]; +extern const size_t VK_IMAGE_TILING_COUNT; + +extern const char* VK_IMAGE_USAGE_FLAG_NAMES[]; +extern const uint32_t VK_IMAGE_USAGE_FLAG_VALUES[]; +extern const size_t VK_IMAGE_USAGE_FLAG_COUNT; + +extern const char* VK_IMAGE_TILING_NAMES[]; +extern const size_t VK_IMAGE_TILING_COUNT; + +extern const char* VK_IMAGE_LAYOUT_NAMES[]; +extern const uint32_t VK_IMAGE_LAYOUT_VALUES[]; +extern const size_t VK_IMAGE_LAYOUT_COUNT; + +extern const char* VMA_ALLOCATION_CREATE_FLAG_NAMES[]; +extern const uint32_t VMA_ALLOCATION_CREATE_FLAG_VALUES[]; +extern const size_t VMA_ALLOCATION_CREATE_FLAG_COUNT; + +extern const char* VMA_MEMORY_USAGE_NAMES[]; +extern const size_t VMA_MEMORY_USAGE_COUNT; + +extern const char* VK_MEMORY_PROPERTY_FLAG_NAMES[]; +extern const uint32_t VK_MEMORY_PROPERTY_FLAG_VALUES[]; +extern const size_t VK_MEMORY_PROPERTY_FLAG_COUNT;
diff --git a/src/VmaReplay/VmaReplay.cpp b/src/VmaReplay/VmaReplay.cpp index d6b23c0..e989eef 100644 --- a/src/VmaReplay/VmaReplay.cpp +++ b/src/VmaReplay/VmaReplay.cpp
@@ -1,4397 +1,4397 @@ -// -// Copyright (c) 2018-2021 Advanced Micro Devices, Inc. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -#include "VmaUsage.h" -#include "Common.h" -#include "Constants.h" -#include <unordered_map> -#include <map> -#include <algorithm> - -static VERBOSITY g_Verbosity = VERBOSITY::DEFAULT; - -static const uint32_t VULKAN_API_VERSION = VK_API_VERSION_1_1; - -namespace DetailedStats -{ - -struct Flag -{ - uint32_t setCount = 0; - - void PostValue(bool v) - { - if(v) - { - ++setCount; - } - } - - void Print(uint32_t totalCount) const - { - if(setCount) - { - printf(" %u (%.2f%%)\n", setCount, (double)setCount * 100.0 / (double)totalCount); - } - else - { - printf(" 0\n"); - } - } -}; - -struct Enum -{ - Enum(size_t itemCount, const char* const* itemNames, const uint32_t* itemValues = nullptr) : - m_ItemCount(itemCount), - m_ItemNames(itemNames), - m_ItemValues(itemValues) - { - } - - void PostValue(uint32_t v) - { - if(v < _countof(m_BaseCount)) - { - ++m_BaseCount[v]; - } - else - { - auto it = m_ExtendedCount.find(v); - if(it != m_ExtendedCount.end()) - { - ++it->second; - } - else - { - m_ExtendedCount.insert(std::make_pair(v, 1u)); - } - } - } - - void Print(uint32_t totalCount) const - { - if(totalCount && - (!m_ExtendedCount.empty() || std::count_if(m_BaseCount, m_BaseCount + _countof(m_BaseCount), [](uint32_t v) { return v > 0; }))) - { - printf("\n"); - - for(size_t i = 0; i < _countof(m_BaseCount); ++i) - { - const uint32_t currCount = m_BaseCount[i]; - if(currCount) - { - PrintItem((uint32_t)i, currCount, totalCount); - } - } - - for(const auto& it : m_ExtendedCount) - { - PrintItem(it.first, it.second, totalCount); - } - } - else - { - printf(" 0\n"); - } - } - -private: - const size_t m_ItemCount; - const char* const* const m_ItemNames; - const uint32_t* const m_ItemValues; - - uint32_t m_BaseCount[32] = {}; - std::map<uint32_t, uint32_t> m_ExtendedCount; - - void PrintItem(uint32_t value, uint32_t count, uint32_t totalCount) const - { - size_t itemIndex = m_ItemCount; - if(m_ItemValues) - { - for(itemIndex = 0; itemIndex < m_ItemCount; ++itemIndex) - { - if(m_ItemValues[itemIndex] == value) - { - break; - } - } - } - else - { - if(value < m_ItemCount) - { - itemIndex = value; - } - } - - if(itemIndex < m_ItemCount) - { - printf(" %s: ", m_ItemNames[itemIndex]); - } - else - { - printf(" 0x%X: ", value); - } - - printf("%u (%.2f%%)\n", count, (double)count * 100.0 / (double)totalCount); - } -}; - -struct FlagSet -{ - uint32_t count[32] = {}; - - FlagSet(size_t count, const char* const* names, const uint32_t* values = nullptr) : - m_Count(count), - m_Names(names), - m_Values(values) - { - } - - void PostValue(uint32_t v) - { - for(size_t i = 0; i < 32; ++i) - { - if((v & (1u << i)) != 0) - { - ++count[i]; - } - } - } - - void Print(uint32_t totalCount) const - { - if(totalCount && - std::count_if(count, count + _countof(count), [](uint32_t v) { return v > 0; })) - { - printf("\n"); - for(uint32_t bitIndex = 0; bitIndex < 32; ++bitIndex) - { - const uint32_t currCount = count[bitIndex]; - if(currCount) - { - size_t itemIndex = m_Count; - if(m_Values) - { - for(itemIndex = 0; itemIndex < m_Count; ++itemIndex) - { - if(m_Values[itemIndex] == (1u << bitIndex)) - { - break; - } - } - } - else - { - if(bitIndex < m_Count) - { - itemIndex = bitIndex; - } - } - - if(itemIndex < m_Count) - { - printf(" %s: ", m_Names[itemIndex]); - } - else - { - printf(" 0x%X: ", 1u << bitIndex); - } - - printf("%u (%.2f%%)\n", currCount, (double)currCount * 100.0 / (double)totalCount); - } - } - } - else - { - printf(" 0\n"); - } - } - -private: - const size_t m_Count; - const char* const* const m_Names; - const uint32_t* const m_Values; -}; - -// T should be unsigned int -template<typename T> -struct MinMaxAvg -{ - T min = std::numeric_limits<T>::max(); - T max = 0; - T sum = T(); - - void PostValue(T v) - { - this->min = std::min(this->min, v); - this->max = std::max(this->max, v); - sum += v; - } - - void Print(uint32_t totalCount) const - { - if(totalCount && sum > T()) - { - if(this->min == this->max) - { - printf(" %llu\n", (uint64_t)this->max); - } - else - { - printf("\n Min: %llu\n Max: %llu\n Avg: %llu\n", - (uint64_t)this->min, - (uint64_t)this->max, - round_div<uint64_t>(this->sum, totalCount)); - } - } - else - { - printf(" 0\n"); - } - } -}; - -template<typename T> -struct BitMask -{ - uint32_t zeroCount = 0; - uint32_t maxCount = 0; - - void PostValue(T v) - { - if(v) - { - if(v == std::numeric_limits<T>::max()) - { - ++maxCount; - } - } - else - { - ++zeroCount; - } - } - - void Print(uint32_t totalCount) const - { - if(totalCount > 0 && zeroCount < totalCount) - { - const uint32_t otherCount = totalCount - (zeroCount + maxCount); - - printf("\n 0: %u (%.2f%%)\n Max: %u (%.2f%%)\n Other: %u (%.2f%%)\n", - zeroCount, (double)zeroCount * 100.0 / (double)totalCount, - maxCount, (double)maxCount * 100.0 / (double)totalCount, - otherCount, (double)otherCount * 100.0 / (double)totalCount); - } - else - { - printf(" 0\n"); - } - } -}; - -struct CountPerMemType -{ - uint32_t count[VK_MAX_MEMORY_TYPES] = {}; - - void PostValue(uint32_t v) - { - for(uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i) - { - if((v & (1u << i)) != 0) - { - ++count[i]; - } - } - } - - void Print(uint32_t totalCount) const - { - if(totalCount) - { - printf("\n"); - for(uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i) - { - if(count[i]) - { - printf(" %u: %u (%.2f%%)\n", i, count[i], - (double)count[i] * 100.0 / (double)totalCount); - } - } - } - else - { - printf(" 0\n"); - } - } -}; - -struct StructureStats -{ - uint32_t totalCount = 0; -}; - -#define PRINT_FIELD(name) \ - printf(" " #name ":"); \ - (name).Print(totalCount); -#define PRINT_FIELD_NAMED(name, nameStr) \ - printf(" " nameStr ":"); \ - (name).Print(totalCount); - -struct VmaPoolCreateInfoStats : public StructureStats -{ - CountPerMemType memoryTypeIndex; - FlagSet flags; - MinMaxAvg<VkDeviceSize> blockSize; - MinMaxAvg<size_t> minBlockCount; - MinMaxAvg<size_t> maxBlockCount; - Flag minMaxBlockCountEqual; - MinMaxAvg<uint32_t> frameInUseCount; - - VmaPoolCreateInfoStats() : - flags(VMA_POOL_CREATE_FLAG_COUNT, VMA_POOL_CREATE_FLAG_NAMES, VMA_POOL_CREATE_FLAG_VALUES) - { - } - - void PostValue(const VmaPoolCreateInfo& v) - { - ++totalCount; - - memoryTypeIndex.PostValue(v.memoryTypeIndex); - flags.PostValue(v.flags); - blockSize.PostValue(v.blockSize); - minBlockCount.PostValue(v.minBlockCount); - maxBlockCount.PostValue(v.maxBlockCount); - minMaxBlockCountEqual.PostValue(v.minBlockCount == v.maxBlockCount); - frameInUseCount.PostValue(v.frameInUseCount); - } - - void Print() const - { - if(totalCount == 0) - { - return; - } - - printf("VmaPoolCreateInfo (%u):\n", totalCount); - - PRINT_FIELD(memoryTypeIndex); - PRINT_FIELD(flags); - PRINT_FIELD(blockSize); - PRINT_FIELD(minBlockCount); - PRINT_FIELD(maxBlockCount); - PRINT_FIELD_NAMED(minMaxBlockCountEqual, "minBlockCount == maxBlockCount"); - PRINT_FIELD(frameInUseCount); - } -}; - -struct VkBufferCreateInfoStats : public StructureStats -{ - FlagSet flags; - MinMaxAvg<VkDeviceSize> size; - FlagSet usage; - Enum sharingMode; - - VkBufferCreateInfoStats() : - flags(VK_BUFFER_CREATE_FLAG_COUNT, VK_BUFFER_CREATE_FLAG_NAMES, VK_BUFFER_CREATE_FLAG_VALUES), - usage(VK_BUFFER_USAGE_FLAG_COUNT, VK_BUFFER_USAGE_FLAG_NAMES, VK_BUFFER_USAGE_FLAG_VALUES), - sharingMode(VK_SHARING_MODE_COUNT, VK_SHARING_MODE_NAMES) - { - } - - void PostValue(const VkBufferCreateInfo& v) - { - ++totalCount; - - flags.PostValue(v.flags); - size.PostValue(v.size); - usage.PostValue(v.usage); - sharingMode.PostValue(v.sharingMode); - } - - void Print() const - { - if(totalCount == 0) - { - return; - } - - printf("VkBufferCreateInfo (%u):\n", totalCount); - - PRINT_FIELD(flags); - PRINT_FIELD(size); - PRINT_FIELD(usage); - PRINT_FIELD(sharingMode); - } -}; - -struct VkImageCreateInfoStats : public StructureStats -{ - FlagSet flags; - Enum imageType; - Enum format; - MinMaxAvg<uint32_t> width, height, depth, mipLevels, arrayLayers; - Flag depthGreaterThanOne, mipLevelsGreaterThanOne, arrayLayersGreaterThanOne; - Enum samples; - Enum tiling; - FlagSet usage; - Enum sharingMode; - Enum initialLayout; - - VkImageCreateInfoStats() : - flags(VK_IMAGE_CREATE_FLAG_COUNT, VK_IMAGE_CREATE_FLAG_NAMES, VK_IMAGE_CREATE_FLAG_VALUES), - imageType(VK_IMAGE_TYPE_COUNT, VK_IMAGE_TYPE_NAMES), - format(VK_FORMAT_COUNT, VK_FORMAT_NAMES, VK_FORMAT_VALUES), - samples(VK_SAMPLE_COUNT_COUNT, VK_SAMPLE_COUNT_NAMES, VK_SAMPLE_COUNT_VALUES), - tiling(VK_IMAGE_TILING_COUNT, VK_IMAGE_TILING_NAMES), - usage(VK_IMAGE_USAGE_FLAG_COUNT, VK_IMAGE_USAGE_FLAG_NAMES, VK_IMAGE_USAGE_FLAG_VALUES), - sharingMode(VK_SHARING_MODE_COUNT, VK_SHARING_MODE_NAMES), - initialLayout(VK_IMAGE_LAYOUT_COUNT, VK_IMAGE_LAYOUT_NAMES, VK_IMAGE_LAYOUT_VALUES) - { - } - - void PostValue(const VkImageCreateInfo& v) - { - ++totalCount; - - flags.PostValue(v.flags); - imageType.PostValue(v.imageType); - format.PostValue(v.format); - width.PostValue(v.extent.width); - height.PostValue(v.extent.height); - depth.PostValue(v.extent.depth); - mipLevels.PostValue(v.mipLevels); - arrayLayers.PostValue(v.arrayLayers); - depthGreaterThanOne.PostValue(v.extent.depth > 1); - mipLevelsGreaterThanOne.PostValue(v.mipLevels > 1); - arrayLayersGreaterThanOne.PostValue(v.arrayLayers > 1); - samples.PostValue(v.samples); - tiling.PostValue(v.tiling); - usage.PostValue(v.usage); - sharingMode.PostValue(v.sharingMode); - initialLayout.PostValue(v.initialLayout); - } - - void Print() const - { - if(totalCount == 0) - { - return; - } - - printf("VkImageCreateInfo (%u):\n", totalCount); - - PRINT_FIELD(flags); - PRINT_FIELD(imageType); - PRINT_FIELD(format); - PRINT_FIELD(width); - PRINT_FIELD(height); - PRINT_FIELD(depth); - PRINT_FIELD(mipLevels); - PRINT_FIELD(arrayLayers); - PRINT_FIELD_NAMED(depthGreaterThanOne, "depth > 1"); - PRINT_FIELD_NAMED(mipLevelsGreaterThanOne, "mipLevels > 1"); - PRINT_FIELD_NAMED(arrayLayersGreaterThanOne, "arrayLayers > 1"); - PRINT_FIELD(samples); - PRINT_FIELD(tiling); - PRINT_FIELD(usage); - PRINT_FIELD(sharingMode); - PRINT_FIELD(initialLayout); - } -}; - -struct VmaAllocationCreateInfoStats : public StructureStats -{ - FlagSet flags; - Enum usage; - FlagSet requiredFlags, preferredFlags; - Flag requiredFlagsNotZero, preferredFlagsNotZero; - BitMask<uint32_t> memoryTypeBits; - Flag poolNotNull; - Flag userDataNotNull; - - VmaAllocationCreateInfoStats() : - flags(VMA_ALLOCATION_CREATE_FLAG_COUNT, VMA_ALLOCATION_CREATE_FLAG_NAMES, VMA_ALLOCATION_CREATE_FLAG_VALUES), - usage(VMA_MEMORY_USAGE_COUNT, VMA_MEMORY_USAGE_NAMES), - requiredFlags(VK_MEMORY_PROPERTY_FLAG_COUNT, VK_MEMORY_PROPERTY_FLAG_NAMES, VK_MEMORY_PROPERTY_FLAG_VALUES), - preferredFlags(VK_MEMORY_PROPERTY_FLAG_COUNT, VK_MEMORY_PROPERTY_FLAG_NAMES, VK_MEMORY_PROPERTY_FLAG_VALUES) - { - } - - void PostValue(const VmaAllocationCreateInfo& v, size_t count = 1) - { - totalCount += (uint32_t)count; - - for(size_t i = 0; i < count; ++i) - { - flags.PostValue(v.flags); - usage.PostValue(v.usage); - requiredFlags.PostValue(v.requiredFlags); - preferredFlags.PostValue(v.preferredFlags); - requiredFlagsNotZero.PostValue(v.requiredFlags != 0); - preferredFlagsNotZero.PostValue(v.preferredFlags != 0); - memoryTypeBits.PostValue(v.memoryTypeBits); - poolNotNull.PostValue(v.pool != VK_NULL_HANDLE); - userDataNotNull.PostValue(v.pUserData != nullptr); - } - } - - void Print() const - { - if(totalCount == 0) - { - return; - } - - printf("VmaAllocationCreateInfo (%u):\n", totalCount); - - PRINT_FIELD(flags); - PRINT_FIELD(usage); - PRINT_FIELD(requiredFlags); - PRINT_FIELD(preferredFlags); - PRINT_FIELD_NAMED(requiredFlagsNotZero, "requiredFlags != 0"); - PRINT_FIELD_NAMED(preferredFlagsNotZero, "preferredFlags != 0"); - PRINT_FIELD(memoryTypeBits); - PRINT_FIELD_NAMED(poolNotNull, "pool != VK_NULL_HANDLE"); - PRINT_FIELD_NAMED(userDataNotNull, "pUserData != nullptr"); - } -}; - -struct VmaAllocateMemoryPagesStats : public StructureStats -{ - MinMaxAvg<size_t> allocationCount; - - void PostValue(size_t allocationCount) - { - this->allocationCount.PostValue(allocationCount); - } - - void Print() const - { - if(totalCount == 0) - { - return; - } - - printf("vmaAllocateMemoryPages (%u):\n", totalCount); - - PRINT_FIELD(allocationCount); - } -}; - -struct VmaDefragmentationInfo2Stats : public StructureStats -{ - BitMask<VkDeviceSize> maxCpuBytesToMove; - BitMask<uint32_t> maxCpuAllocationsToMove; - BitMask<VkDeviceSize> maxGpuBytesToMove; - BitMask<uint32_t> maxGpuAllocationsToMove; - Flag commandBufferNotNull; - MinMaxAvg<uint32_t> allocationCount; - Flag allocationCountNotZero; - MinMaxAvg<uint32_t> poolCount; - Flag poolCountNotZero; - - void PostValue(const VmaDefragmentationInfo2& info) - { - ++totalCount; - - maxCpuBytesToMove.PostValue(info.maxCpuBytesToMove); - maxCpuAllocationsToMove.PostValue(info.maxCpuAllocationsToMove); - maxGpuBytesToMove.PostValue(info.maxGpuBytesToMove); - maxGpuAllocationsToMove.PostValue(info.maxGpuAllocationsToMove); - commandBufferNotNull.PostValue(info.commandBuffer != VK_NULL_HANDLE); - allocationCount.PostValue(info.allocationCount); - allocationCountNotZero.PostValue(info.allocationCount != 0); - poolCount.PostValue(info.poolCount); - poolCountNotZero.PostValue(info.poolCount != 0); - } - - void Print() const - { - if(totalCount == 0) - { - return; - } - - printf("VmaDefragmentationInfo2 (%u):\n", totalCount); - - PRINT_FIELD(maxCpuBytesToMove); - PRINT_FIELD(maxCpuAllocationsToMove); - PRINT_FIELD(maxGpuBytesToMove); - PRINT_FIELD(maxGpuAllocationsToMove); - PRINT_FIELD_NAMED(commandBufferNotNull, "commandBuffer != VK_NULL_HANDLE"); - PRINT_FIELD(allocationCount); - PRINT_FIELD_NAMED(allocationCountNotZero, "allocationCount > 0"); - PRINT_FIELD(poolCount); - PRINT_FIELD_NAMED(poolCountNotZero, "poolCount > 0"); - } -}; - -#undef PRINT_FIELD_NAMED -#undef PRINT_FIELD - -} // namespace DetailedStats - -// Set this to false to disable deleting leaked VmaAllocation, VmaPool objects -// and let VMA report asserts about them. -static const bool CLEANUP_LEAKED_OBJECTS = true; - -static std::string g_FilePath; -// Most significant 16 bits are major version, least significant 16 bits are minor version. -static uint32_t g_FileVersion; - -inline uint32_t MakeVersion(uint32_t major, uint32_t minor) { return (major << 16) | minor; } -inline uint32_t GetVersionMajor(uint32_t version) { return version >> 16; } -inline uint32_t GetVersionMinor(uint32_t version) { return version & 0xFFFF; } - -static size_t g_IterationCount = 1; -static uint32_t g_PhysicalDeviceIndex = 0; -static RangeSequence<size_t> g_LineRanges; -static bool g_UserDataEnabled = true; -static bool g_MemStatsEnabled = false; -VULKAN_EXTENSION_REQUEST g_VK_LAYER_KHRONOS_validation = VULKAN_EXTENSION_REQUEST::DEFAULT; -VULKAN_EXTENSION_REQUEST g_VK_EXT_memory_budget_request = VULKAN_EXTENSION_REQUEST::DEFAULT; -VULKAN_EXTENSION_REQUEST g_VK_AMD_device_coherent_memory_request = VULKAN_EXTENSION_REQUEST::DEFAULT; - -struct StatsAfterLineEntry -{ - size_t line; - bool detailed; - - bool operator<(const StatsAfterLineEntry& rhs) const { return line < rhs.line; } - bool operator==(const StatsAfterLineEntry& rhs) const { return line == rhs.line; } -}; -static std::vector<StatsAfterLineEntry> g_DumpStatsAfterLine; -static std::vector<size_t> g_DefragmentAfterLine; -static uint32_t g_DefragmentationFlags = 0; -static size_t g_DumpStatsAfterLineNextIndex = 0; -static size_t g_DefragmentAfterLineNextIndex = 0; - -static bool ValidateFileVersion() -{ - if(GetVersionMajor(g_FileVersion) == 1 && - GetVersionMinor(g_FileVersion) <= 8) - { - return true; - } - - return false; -} - -static bool ParseFileVersion(const StrRange& s) -{ - CsvSplit csvSplit; - csvSplit.Set(s, 2); - uint32_t major, minor; - if(csvSplit.GetCount() == 2 && - StrRangeToUint(csvSplit.GetRange(0), major) && - StrRangeToUint(csvSplit.GetRange(1), minor)) - { - g_FileVersion = (major << 16) | minor; - return true; - } - else - { - return false; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// class Statistics - -class Statistics -{ -public: - static uint32_t BufferUsageToClass(uint32_t usage); - static uint32_t ImageUsageToClass(uint32_t usage); - - Statistics(); - ~Statistics(); - void Init(uint32_t memHeapCount, uint32_t memTypeCount); - void PrintDeviceMemStats() const; - void PrintMemStats() const; - void PrintDetailedStats() const; - - const size_t* GetFunctionCallCount() const { return m_FunctionCallCount; } - size_t GetImageCreationCount(uint32_t imgClass) const { return m_ImageCreationCount[imgClass]; } - size_t GetLinearImageCreationCount() const { return m_LinearImageCreationCount; } - size_t GetBufferCreationCount(uint32_t bufClass) const { return m_BufferCreationCount[bufClass]; } - size_t GetAllocationCreationCount() const { return (size_t)m_VmaAllocationCreateInfo.totalCount + m_CreateLostAllocationCount; } - size_t GetPoolCreationCount() const { return m_VmaPoolCreateInfo.totalCount; } - size_t GetBufferCreationCount() const { return (size_t)m_VkBufferCreateInfo.totalCount; } - - void RegisterFunctionCall(VMA_FUNCTION func); - void RegisterCreateImage(const VkImageCreateInfo& info); - void RegisterCreateBuffer(const VkBufferCreateInfo& info); - void RegisterCreatePool(const VmaPoolCreateInfo& info); - void RegisterCreateAllocation(const VmaAllocationCreateInfo& info, size_t allocCount = 1); - void RegisterCreateLostAllocation() { ++m_CreateLostAllocationCount; } - void RegisterAllocateMemoryPages(size_t allocCount) { m_VmaAllocateMemoryPages.PostValue(allocCount); } - void RegisterDefragmentation(const VmaDefragmentationInfo2& info); - - void RegisterDeviceMemoryAllocation(uint32_t memoryType, VkDeviceSize size); - void UpdateMemStats(const VmaStats& currStats); - -private: - uint32_t m_MemHeapCount = 0; - uint32_t m_MemTypeCount = 0; - - size_t m_FunctionCallCount[(size_t)VMA_FUNCTION::Count] = {}; - size_t m_ImageCreationCount[4] = { }; - size_t m_LinearImageCreationCount = 0; - size_t m_BufferCreationCount[4] = { }; - - struct DeviceMemStatInfo - { - size_t allocationCount; - VkDeviceSize allocationTotalSize; - }; - struct DeviceMemStats - { - DeviceMemStatInfo memoryType[VK_MAX_MEMORY_TYPES]; - DeviceMemStatInfo total; - } m_DeviceMemStats; - - // Structure similar to VmaStatInfo, but not the same. - struct MemStatInfo - { - uint32_t blockCount; - uint32_t allocationCount; - uint32_t unusedRangeCount; - VkDeviceSize usedBytes; - VkDeviceSize unusedBytes; - VkDeviceSize totalBytes; - }; - struct MemStats - { - MemStatInfo memoryType[VK_MAX_MEMORY_TYPES]; - MemStatInfo memoryHeap[VK_MAX_MEMORY_HEAPS]; - MemStatInfo total; - } m_PeakMemStats; - - DetailedStats::VmaPoolCreateInfoStats m_VmaPoolCreateInfo; - DetailedStats::VkBufferCreateInfoStats m_VkBufferCreateInfo; - DetailedStats::VkImageCreateInfoStats m_VkImageCreateInfo; - DetailedStats::VmaAllocationCreateInfoStats m_VmaAllocationCreateInfo; - size_t m_CreateLostAllocationCount = 0; - DetailedStats::VmaAllocateMemoryPagesStats m_VmaAllocateMemoryPages; - DetailedStats::VmaDefragmentationInfo2Stats m_VmaDefragmentationInfo2; - - void UpdateMemStatInfo(MemStatInfo& inoutPeakInfo, const VmaStatInfo& currInfo); - static void PrintMemStatInfo(const MemStatInfo& info); -}; - -// Hack for global AllocateDeviceMemoryCallback. -static Statistics* g_Statistics; - -static void VKAPI_CALL AllocateDeviceMemoryCallback( - VmaAllocator allocator, - uint32_t memoryType, - VkDeviceMemory memory, - VkDeviceSize size, - void* pUserData) -{ - g_Statistics->RegisterDeviceMemoryAllocation(memoryType, size); -} - -/// Callback function called before vkFreeMemory. -static void VKAPI_CALL FreeDeviceMemoryCallback( - VmaAllocator allocator, - uint32_t memoryType, - VkDeviceMemory memory, - VkDeviceSize size, - void* pUserData) -{ - // Nothing. -} - -uint32_t Statistics::BufferUsageToClass(uint32_t usage) -{ - // Buffer is used as source of data for fixed-function stage of graphics pipeline. - // It's indirect, vertex, or index buffer. - if ((usage & (VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | - VK_BUFFER_USAGE_INDEX_BUFFER_BIT)) != 0) - { - return 0; - } - // Buffer is accessed by shaders for load/store/atomic. - // Aka "UAV" - else if ((usage & (VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | - VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT)) != 0) - { - return 1; - } - // Buffer is accessed by shaders for reading uniform data. - // Aka "constant buffer" - else if ((usage & (VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | - VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT)) != 0) - { - return 2; - } - // Any other type of buffer. - // Notice that VK_BUFFER_USAGE_TRANSFER_SRC_BIT and VK_BUFFER_USAGE_TRANSFER_DST_BIT - // flags are intentionally ignored. - else - { - return 3; - } -} - -uint32_t Statistics::ImageUsageToClass(uint32_t usage) -{ - // Image is used as depth/stencil "texture/surface". - if ((usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0) - { - return 0; - } - // Image is used as other type of attachment. - // Aka "render target" - else if ((usage & (VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT | - VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) != 0) - { - return 1; - } - // Image is accessed by shaders for sampling. - // Aka "texture" - else if ((usage & VK_IMAGE_USAGE_SAMPLED_BIT) != 0) - { - return 2; - } - // Any other type of image. - // Notice that VK_IMAGE_USAGE_TRANSFER_SRC_BIT and VK_IMAGE_USAGE_TRANSFER_DST_BIT - // flags are intentionally ignored. - else - { - return 3; - } -} - -Statistics::Statistics() -{ - ZeroMemory(&m_DeviceMemStats, sizeof(m_DeviceMemStats)); - ZeroMemory(&m_PeakMemStats, sizeof(m_PeakMemStats)); - - assert(g_Statistics == nullptr); - g_Statistics = this; -} - -Statistics::~Statistics() -{ - assert(g_Statistics == this); - g_Statistics = nullptr; -} - -void Statistics::Init(uint32_t memHeapCount, uint32_t memTypeCount) -{ - m_MemHeapCount = memHeapCount; - m_MemTypeCount = memTypeCount; -} - -void Statistics::PrintDeviceMemStats() const -{ - printf("Successful device memory allocations:\n"); - printf(" Total: count = %zu, total size = %llu\n", - m_DeviceMemStats.total.allocationCount, m_DeviceMemStats.total.allocationTotalSize); - for(uint32_t i = 0; i < m_MemTypeCount; ++i) - { - printf(" Memory type %u: count = %zu, total size = %llu\n", - i, m_DeviceMemStats.memoryType[i].allocationCount, m_DeviceMemStats.memoryType[i].allocationTotalSize); - } -} - -void Statistics::PrintMemStats() const -{ - printf("Memory statistics:\n"); - - printf(" Total:\n"); - PrintMemStatInfo(m_PeakMemStats.total); - - for(uint32_t i = 0; i < m_MemHeapCount; ++i) - { - const MemStatInfo& info = m_PeakMemStats.memoryHeap[i]; - if(info.blockCount > 0 || info.totalBytes > 0) - { - printf(" Heap %u:\n", i); - PrintMemStatInfo(info); - } - } - - for(uint32_t i = 0; i < m_MemTypeCount; ++i) - { - const MemStatInfo& info = m_PeakMemStats.memoryType[i]; - if(info.blockCount > 0 || info.totalBytes > 0) - { - printf(" Type %u:\n", i); - PrintMemStatInfo(info); - } - } -} - -void Statistics::PrintDetailedStats() const -{ - m_VmaPoolCreateInfo.Print(); - m_VmaAllocationCreateInfo.Print(); - m_VmaAllocateMemoryPages.Print(); - m_VkBufferCreateInfo.Print(); - m_VkImageCreateInfo.Print(); - m_VmaDefragmentationInfo2.Print(); -} - -void Statistics::RegisterFunctionCall(VMA_FUNCTION func) -{ - ++m_FunctionCallCount[(size_t)func]; -} - -void Statistics::RegisterCreateImage(const VkImageCreateInfo& info) -{ - if(info.tiling == VK_IMAGE_TILING_LINEAR) - ++m_LinearImageCreationCount; - else - { - const uint32_t imgClass = ImageUsageToClass(info.usage); - ++m_ImageCreationCount[imgClass]; - } - - m_VkImageCreateInfo.PostValue(info); -} - -void Statistics::RegisterCreateBuffer(const VkBufferCreateInfo& info) -{ - const uint32_t bufClass = BufferUsageToClass(info.usage); - ++m_BufferCreationCount[bufClass]; - - m_VkBufferCreateInfo.PostValue(info); -} - -void Statistics::RegisterCreatePool(const VmaPoolCreateInfo& info) -{ - m_VmaPoolCreateInfo.PostValue(info); -} - -void Statistics::RegisterCreateAllocation(const VmaAllocationCreateInfo& info, size_t allocCount) -{ - m_VmaAllocationCreateInfo.PostValue(info, allocCount); -} - -void Statistics::RegisterDefragmentation(const VmaDefragmentationInfo2& info) -{ - m_VmaDefragmentationInfo2.PostValue(info); -} - -void Statistics::UpdateMemStats(const VmaStats& currStats) -{ - UpdateMemStatInfo(m_PeakMemStats.total, currStats.total); - - for(uint32_t i = 0; i < m_MemHeapCount; ++i) - { - UpdateMemStatInfo(m_PeakMemStats.memoryHeap[i], currStats.memoryHeap[i]); - } - - for(uint32_t i = 0; i < m_MemTypeCount; ++i) - { - UpdateMemStatInfo(m_PeakMemStats.memoryType[i], currStats.memoryType[i]); - } -} - -void Statistics::RegisterDeviceMemoryAllocation(uint32_t memoryType, VkDeviceSize size) -{ - ++m_DeviceMemStats.total.allocationCount; - m_DeviceMemStats.total.allocationTotalSize += size; - - ++m_DeviceMemStats.memoryType[memoryType].allocationCount; - m_DeviceMemStats.memoryType[memoryType].allocationTotalSize += size; -} - -void Statistics::UpdateMemStatInfo(MemStatInfo& inoutPeakInfo, const VmaStatInfo& currInfo) -{ -#define SET_PEAK(inoutDst, src) \ - if((src) > (inoutDst)) \ - { \ - (inoutDst) = (src); \ - } - - SET_PEAK(inoutPeakInfo.blockCount, currInfo.blockCount); - SET_PEAK(inoutPeakInfo.allocationCount, currInfo.allocationCount); - SET_PEAK(inoutPeakInfo.unusedRangeCount, currInfo.unusedRangeCount); - SET_PEAK(inoutPeakInfo.usedBytes, currInfo.usedBytes); - SET_PEAK(inoutPeakInfo.unusedBytes, currInfo.unusedBytes); - SET_PEAK(inoutPeakInfo.totalBytes, currInfo.usedBytes + currInfo.unusedBytes); - -#undef SET_PEAK -} - -void Statistics::PrintMemStatInfo(const MemStatInfo& info) -{ - printf(" Peak blocks %u, allocations %u, unused ranges %u\n", - info.blockCount, - info.allocationCount, - info.unusedRangeCount); - printf(" Peak total bytes %llu, used bytes %llu, unused bytes %llu\n", - info.totalBytes, - info.usedBytes, - info.unusedBytes); -} - -//////////////////////////////////////////////////////////////////////////////// -// class ConfigurationParser - -class ConfigurationParser -{ -public: - ConfigurationParser(); - - bool Parse(LineSplit& lineSplit); - - void Compare( - const VkPhysicalDeviceProperties& currDevProps, - const VkPhysicalDeviceMemoryProperties& currMemProps, - uint32_t vulkanApiVersion, - bool currMemoryBudgetEnabled); - -private: - enum class OPTION - { - VulkanApiVersion, - PhysicalDevice_apiVersion, - PhysicalDevice_driverVersion, - PhysicalDevice_vendorID, - PhysicalDevice_deviceID, - PhysicalDevice_deviceType, - PhysicalDevice_deviceName, - PhysicalDeviceLimits_maxMemoryAllocationCount, - PhysicalDeviceLimits_bufferImageGranularity, - PhysicalDeviceLimits_nonCoherentAtomSize, - Extension_VK_KHR_dedicated_allocation, - Extension_VK_KHR_bind_memory2, - Extension_VK_EXT_memory_budget, - Extension_VK_AMD_device_coherent_memory, - Macro_VMA_DEBUG_ALWAYS_DEDICATED_MEMORY, - Macro_VMA_MIN_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_MIN_ALIGNMENT") || StrRangeEq(subOptionName, "VMA_DEBUG_ALIGNMENT")) - SetOption(currLineNumber, OPTION::Macro_VMA_MIN_ALIGNMENT, csvSplit.GetRange(2)); - else if(StrRangeEq(subOptionName, "VMA_DEBUG_MARGIN")) - SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_MARGIN, csvSplit.GetRange(2)); - else if(StrRangeEq(subOptionName, "VMA_DEBUG_INITIALIZE_ALLOCATIONS")) - SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_INITIALIZE_ALLOCATIONS, csvSplit.GetRange(2)); - else if(StrRangeEq(subOptionName, "VMA_DEBUG_DETECT_CORRUPTION")) - SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_DETECT_CORRUPTION, csvSplit.GetRange(2)); - else if(StrRangeEq(subOptionName, "VMA_DEBUG_GLOBAL_MUTEX")) - SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_GLOBAL_MUTEX, csvSplit.GetRange(2)); - else if(StrRangeEq(subOptionName, "VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY")) - SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY, csvSplit.GetRange(2)); - else if(StrRangeEq(subOptionName, "VMA_SMALL_HEAP_MAX_SIZE")) - SetOption(currLineNumber, OPTION::Macro_VMA_SMALL_HEAP_MAX_SIZE, csvSplit.GetRange(2)); - else if(StrRangeEq(subOptionName, "VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE")) - SetOption(currLineNumber, OPTION::Macro_VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE, csvSplit.GetRange(2)); - else - printf("Line %zu: Unrecognized configuration option.\n", currLineNumber); - } - else - printf("Line %zu: Too few columns.\n", currLineNumber); - } - else if(StrRangeEq(optionName, "PhysicalDeviceMemory")) - { - uint32_t value = 0; - if(csvSplit.GetCount() == 3 && StrRangeEq(csvSplit.GetRange(1), "HeapCount") && - StrRangeToUint(csvSplit.GetRange(2), value)) - { - m_MemProps.memoryHeapCount = value; - } - else if(csvSplit.GetCount() == 3 && StrRangeEq(csvSplit.GetRange(1), "TypeCount") && - StrRangeToUint(csvSplit.GetRange(2), value)) - { - m_MemProps.memoryTypeCount = value; - } - else if(csvSplit.GetCount() == 5 && StrRangeEq(csvSplit.GetRange(1), "Heap") && - StrRangeToUint(csvSplit.GetRange(2), value) && - value < m_MemProps.memoryHeapCount) - { - if(StrRangeEq(csvSplit.GetRange(3), "size") && - StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryHeaps[value].size)) - { - // Parsed. - } - else if(StrRangeEq(csvSplit.GetRange(3), "flags") && - StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryHeaps[value].flags)) - { - // Parsed. - } - else - printf("Line %zu: Invalid configuration option.\n", currLineNumber); - } - else if(csvSplit.GetCount() == 5 && StrRangeEq(csvSplit.GetRange(1), "Type") && - StrRangeToUint(csvSplit.GetRange(2), value) && - value < m_MemProps.memoryTypeCount) - { - if(StrRangeEq(csvSplit.GetRange(3), "heapIndex") && - StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryTypes[value].heapIndex)) - { - // Parsed. - } - else if(StrRangeEq(csvSplit.GetRange(3), "propertyFlags") && - StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryTypes[value].propertyFlags)) - { - // Parsed. - } - else - printf("Line %zu: Invalid configuration option.\n", currLineNumber); - } - else - printf("Line %zu: Invalid configuration option.\n", currLineNumber); - } - else - printf("Line %zu: Unrecognized configuration option.\n", currLineNumber); - } - - return true; -} - -void ConfigurationParser::Compare( - const VkPhysicalDeviceProperties& currDevProps, - const VkPhysicalDeviceMemoryProperties& currMemProps, - uint32_t vulkanApiVersion, - bool currMemoryBudgetEnabled) -{ - char vulkanApiVersionStr[32]; - sprintf_s(vulkanApiVersionStr, "%u,%u", VK_VERSION_MAJOR(vulkanApiVersion), VK_VERSION_MINOR(vulkanApiVersion)); - CompareOption(VERBOSITY::DEFAULT, "VulkanApiVersion", - OPTION::VulkanApiVersion, vulkanApiVersionStr); - - CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice apiVersion", - OPTION::PhysicalDevice_apiVersion, currDevProps.apiVersion); - CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice driverVersion", - OPTION::PhysicalDevice_driverVersion, currDevProps.driverVersion); - CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice vendorID", - OPTION::PhysicalDevice_vendorID, currDevProps.vendorID); - CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice deviceID", - OPTION::PhysicalDevice_deviceID, currDevProps.deviceID); - CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice deviceType", - OPTION::PhysicalDevice_deviceType, (uint32_t)currDevProps.deviceType); - CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice deviceName", - OPTION::PhysicalDevice_deviceName, currDevProps.deviceName); - - CompareOption(VERBOSITY::DEFAULT, "PhysicalDeviceLimits maxMemoryAllocationCount", - OPTION::PhysicalDeviceLimits_maxMemoryAllocationCount, currDevProps.limits.maxMemoryAllocationCount); - CompareOption(VERBOSITY::DEFAULT, "PhysicalDeviceLimits bufferImageGranularity", - OPTION::PhysicalDeviceLimits_bufferImageGranularity, currDevProps.limits.bufferImageGranularity); - CompareOption(VERBOSITY::DEFAULT, "PhysicalDeviceLimits nonCoherentAtomSize", - OPTION::PhysicalDeviceLimits_nonCoherentAtomSize, currDevProps.limits.nonCoherentAtomSize); - - CompareMemProps(currMemProps); -} - -void ConfigurationParser::SetOption( - size_t lineNumber, - OPTION option, - const StrRange& str) -{ - if(m_OptionSet[(size_t)option]) - { - printf("Line %zu: Option already specified.\n" ,lineNumber); - } - - m_OptionSet[(size_t)option] = true; - - std::string val; - str.to_str(val); - m_OptionValue[(size_t)option] = std::move(val); -} - -void ConfigurationParser::EnsureWarningHeader() -{ - if(!m_WarningHeaderPrinted) - { - printf("WARNING: Following configuration parameters don't match:\n"); - m_WarningHeaderPrinted = true; - } -} - -void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name, - OPTION option, uint32_t currValue) -{ - if(m_OptionSet[(size_t)option] && - g_Verbosity >= minVerbosity) - { - uint32_t origValue; - if(StrRangeToUint(StrRange(m_OptionValue[(size_t)option]), origValue)) - { - if(origValue != currValue) - { - EnsureWarningHeader(); - printf(" %s: original %u, current %u\n", name, origValue, currValue); - } - } - } -} - -void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name, - OPTION option, uint64_t currValue) -{ - if(m_OptionSet[(size_t)option] && - g_Verbosity >= minVerbosity) - { - uint64_t origValue; - if(StrRangeToUint(StrRange(m_OptionValue[(size_t)option]), origValue)) - { - if(origValue != currValue) - { - EnsureWarningHeader(); - printf(" %s: original %llu, current %llu\n", name, origValue, currValue); - } - } - } -} - -void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name, - OPTION option, bool currValue) -{ - if(m_OptionSet[(size_t)option] && - g_Verbosity >= minVerbosity) - { - bool origValue; - if(StrRangeToBool(StrRange(m_OptionValue[(size_t)option]), origValue)) - { - if(origValue != currValue) - { - EnsureWarningHeader(); - printf(" %s: original %u, current %u\n", name, - origValue ? 1 : 0, - currValue ? 1 : 0); - } - } - } -} - -void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name, - OPTION option, const char* currValue) -{ - if(m_OptionSet[(size_t)option] && - g_Verbosity >= minVerbosity) - { - const std::string& origValue = m_OptionValue[(size_t)option]; - if(origValue != currValue) - { - EnsureWarningHeader(); - printf(" %s: original \"%s\", current \"%s\"\n", name, origValue.c_str(), currValue); - } - } -} - -void ConfigurationParser::CompareMemProps( - const VkPhysicalDeviceMemoryProperties& currMemProps) -{ - if(g_Verbosity < VERBOSITY::DEFAULT) - { - return; - } - - bool memoryMatch = - currMemProps.memoryHeapCount == m_MemProps.memoryHeapCount && - currMemProps.memoryTypeCount == m_MemProps.memoryTypeCount; - - for(uint32_t i = 0; memoryMatch && i < currMemProps.memoryHeapCount; ++i) - { - memoryMatch = - currMemProps.memoryHeaps[i].flags == m_MemProps.memoryHeaps[i].flags; - } - for(uint32_t i = 0; memoryMatch && i < currMemProps.memoryTypeCount; ++i) - { - memoryMatch = - currMemProps.memoryTypes[i].heapIndex == m_MemProps.memoryTypes[i].heapIndex && - currMemProps.memoryTypes[i].propertyFlags == m_MemProps.memoryTypes[i].propertyFlags; - } - - if(memoryMatch && g_Verbosity == VERBOSITY::MAXIMUM) - { - bool memorySizeMatch = true; - for(uint32_t i = 0; memorySizeMatch && i < currMemProps.memoryHeapCount; ++i) - { - memorySizeMatch = - currMemProps.memoryHeaps[i].size == m_MemProps.memoryHeaps[i].size; - } - - if(!memorySizeMatch) - { - printf("WARNING: Sizes of original memory heaps are different from current ones.\n"); - } - } - else - { - printf("WARNING: Layout of original memory heaps and types is different from current one.\n"); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// class Player - -static const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; - -static VkBool32 VKAPI_PTR MyDebugReportCallback( - VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, - VkDebugUtilsMessageTypeFlagsEXT messageTypes, - const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - void* pUserData) -{ - assert(pCallbackData && pCallbackData->pMessageIdName && pCallbackData->pMessage); - printf("%s \xBA %s\n", pCallbackData->pMessageIdName, pCallbackData->pMessage); - return VK_FALSE; -} - -static bool IsLayerSupported(const VkLayerProperties* pProps, size_t propCount, const char* pLayerName) -{ - const VkLayerProperties* propsEnd = pProps + propCount; - return std::find_if( - pProps, - propsEnd, - [pLayerName](const VkLayerProperties& prop) -> bool { - return strcmp(pLayerName, prop.layerName) == 0; - }) != propsEnd; -} - -static const size_t FIRST_PARAM_INDEX = 4; - -static void InitVulkanFeatures( - VkPhysicalDeviceFeatures& outFeatures, - const VkPhysicalDeviceFeatures& supportedFeatures) -{ - ZeroMemory(&outFeatures, sizeof(outFeatures)); - - // Enable something what may interact with memory/buffer/image support. - - outFeatures.fullDrawIndexUint32 = supportedFeatures.fullDrawIndexUint32; - outFeatures.imageCubeArray = supportedFeatures.imageCubeArray; - outFeatures.geometryShader = supportedFeatures.geometryShader; - outFeatures.tessellationShader = supportedFeatures.tessellationShader; - outFeatures.multiDrawIndirect = supportedFeatures.multiDrawIndirect; - outFeatures.textureCompressionETC2 = supportedFeatures.textureCompressionETC2; - outFeatures.textureCompressionASTC_LDR = supportedFeatures.textureCompressionASTC_LDR; - outFeatures.textureCompressionBC = supportedFeatures.textureCompressionBC; -} - -class Player -{ -public: - Player(); - int Init(); - ~Player(); - - void ApplyConfig(ConfigurationParser& configParser); - void ExecuteLine(size_t lineNumber, const StrRange& line); - void DumpStats(const char* fileNameFormat, size_t lineNumber, bool detailed); - void Defragment(); - - void PrintStats(); - -private: - static const size_t MAX_WARNINGS_TO_SHOW = 64; - - size_t m_WarningCount = 0; - bool m_AllocateForBufferImageWarningIssued = false; - - VkInstance m_VulkanInstance = VK_NULL_HANDLE; - VkPhysicalDevice m_PhysicalDevice = VK_NULL_HANDLE; - uint32_t m_GraphicsQueueFamilyIndex = UINT32_MAX; - uint32_t m_TransferQueueFamilyIndex = UINT32_MAX; - VkDevice m_Device = VK_NULL_HANDLE; - VkQueue m_GraphicsQueue = VK_NULL_HANDLE; - VkQueue m_TransferQueue = VK_NULL_HANDLE; - VmaAllocator m_Allocator = VK_NULL_HANDLE; - VkCommandPool m_CommandPool = VK_NULL_HANDLE; - VkCommandBuffer m_CommandBuffer = VK_NULL_HANDLE; - bool m_MemoryBudgetEnabled = false; - const VkPhysicalDeviceProperties* m_DevProps = nullptr; - const VkPhysicalDeviceMemoryProperties* m_MemProps = nullptr; - - PFN_vkCreateDebugUtilsMessengerEXT m_vkCreateDebugUtilsMessengerEXT = nullptr; - PFN_vkDestroyDebugUtilsMessengerEXT m_vkDestroyDebugUtilsMessengerEXT = nullptr; - VkDebugUtilsMessengerEXT m_DebugUtilsMessenger = VK_NULL_HANDLE; - - uint32_t m_VmaFrameIndex = 0; - - // Any of these handles null can mean it was created in original but couldn't be created now. - struct Pool - { - VmaPool pool; - }; - struct Allocation - { - uint32_t allocationFlags = 0; - VmaAllocation allocation = VK_NULL_HANDLE; - VkBuffer buffer = VK_NULL_HANDLE; - VkImage image = VK_NULL_HANDLE; - }; - std::unordered_map<uint64_t, Pool> m_Pools; - std::unordered_map<uint64_t, Allocation> m_Allocations; - std::unordered_map<uint64_t, VmaDefragmentationContext> m_DefragmentationContexts; - - struct Thread - { - uint32_t callCount; - }; - std::unordered_map<uint32_t, Thread> m_Threads; - - // Copy of column [1] from previously parsed line. - std::string m_LastLineTimeStr; - Statistics m_Stats; - - std::vector<char> m_UserDataTmpStr; - - void Destroy(const Allocation& alloc); - - // Finds VmaPool bu original pointer. - // If origPool = null, returns true and outPool = null. - // If failed, prints warning, returns false and outPool = null. - bool FindPool(size_t lineNumber, uint64_t origPool, VmaPool& outPool); - // If allocation with that origPtr already exists, prints warning and replaces it. - void AddAllocation(size_t lineNumber, uint64_t origPtr, VkResult res, const char* functionName, Allocation&& allocDesc); - - // Increments warning counter. Returns true if warning message should be printed. - bool IssueWarning(); - - int InitVulkan(); - void FinalizeVulkan(); - void RegisterDebugCallbacks(); - void UnregisterDebugCallbacks(); - - // If parmeter count doesn't match, issues warning and returns false. - bool ValidateFunctionParameterCount(size_t lineNumber, const CsvSplit& csvSplit, size_t expectedParamCount, bool lastUnbound); - - // If failed, prints warning, returns false, and sets allocCreateInfo.pUserData to null. - bool PrepareUserData(size_t lineNumber, uint32_t allocCreateFlags, const StrRange& userDataColumn, const StrRange& wholeLine, void*& outUserData); - - void UpdateMemStats(); - - void ExecuteCreatePool(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteDestroyPool(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteSetAllocationUserData(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteCreateBuffer(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteDestroyBuffer(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::DestroyBuffer); DestroyAllocation(lineNumber, csvSplit, "vmaDestroyBuffer"); } - void ExecuteCreateImage(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteDestroyImage(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::DestroyImage); DestroyAllocation(lineNumber, csvSplit, "vmaDestroyImage"); } - void ExecuteFreeMemory(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::FreeMemory); DestroyAllocation(lineNumber, csvSplit, "vmaFreeMemory"); } - void ExecuteFreeMemoryPages(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteCreateLostAllocation(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteAllocateMemory(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteAllocateMemoryPages(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteAllocateMemoryForBufferOrImage(size_t lineNumber, const CsvSplit& csvSplit, OBJECT_TYPE objType); - void ExecuteMapMemory(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteUnmapMemory(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteFlushAllocation(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteInvalidateAllocation(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteTouchAllocation(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteGetAllocationInfo(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteMakePoolAllocationsLost(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteResizeAllocation(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteDefragmentationBegin(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteDefragmentationEnd(size_t lineNumber, const CsvSplit& csvSplit); - void ExecuteSetPoolName(size_t lineNumber, const CsvSplit& csvSplit); - - void DestroyAllocation(size_t lineNumber, const CsvSplit& csvSplit, const char* functionName); - - void PrintStats(const VmaStats& stats, const char* suffix); - void PrintStatInfo(const VmaStatInfo& info); -}; - -Player::Player() -{ -} - -int Player::Init() -{ - int result = InitVulkan(); - - if(result == 0) - { - m_Stats.Init(m_MemProps->memoryHeapCount, m_MemProps->memoryTypeCount); - UpdateMemStats(); - } - - return result; -} - -Player::~Player() -{ - FinalizeVulkan(); - - if(g_Verbosity < VERBOSITY::MAXIMUM && m_WarningCount > MAX_WARNINGS_TO_SHOW) - printf("WARNING: %zu more warnings not shown.\n", m_WarningCount - MAX_WARNINGS_TO_SHOW); -} - -void Player::ApplyConfig(ConfigurationParser& configParser) -{ - configParser.Compare(*m_DevProps, *m_MemProps, - VULKAN_API_VERSION, - m_MemoryBudgetEnabled); -} - -void Player::ExecuteLine(size_t lineNumber, const StrRange& line) -{ - CsvSplit csvSplit; - csvSplit.Set(line); - - if(csvSplit.GetCount() >= FIRST_PARAM_INDEX) - { - // Check thread ID. - uint32_t threadId; - if(StrRangeToUint(csvSplit.GetRange(0), threadId)) - { - const auto it = m_Threads.find(threadId); - if(it != m_Threads.end()) - { - ++it->second.callCount; - } - else - { - Thread threadInfo{}; - threadInfo.callCount = 1; - m_Threads[threadId] = threadInfo; - } - } - else - { - if(IssueWarning()) - { - printf("Line %zu: Incorrect thread ID.\n", lineNumber); - } - } - - // Save time. - csvSplit.GetRange(1).to_str(m_LastLineTimeStr); - - // Update VMA current frame index. - StrRange frameIndexStr = csvSplit.GetRange(2); - uint32_t frameIndex; - if(StrRangeToUint(frameIndexStr, frameIndex)) - { - if(frameIndex != m_VmaFrameIndex) - { - vmaSetCurrentFrameIndex(m_Allocator, frameIndex); - m_VmaFrameIndex = frameIndex; - } - } - else - { - if(IssueWarning()) - { - printf("Line %zu: Incorrect frame index.\n", lineNumber); - } - } - - StrRange functionName = csvSplit.GetRange(3); - - if(StrRangeEq(functionName, "vmaCreateAllocator")) - { - if(ValidateFunctionParameterCount(lineNumber, csvSplit, 0, false)) - { - // Nothing. - } - } - else if(StrRangeEq(functionName, "vmaDestroyAllocator")) - { - if(ValidateFunctionParameterCount(lineNumber, csvSplit, 0, false)) - { - // Nothing. - } - } - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreatePool])) - ExecuteCreatePool(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DestroyPool])) - ExecuteDestroyPool(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::SetAllocationUserData])) - ExecuteSetAllocationUserData(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreateBuffer])) - ExecuteCreateBuffer(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DestroyBuffer])) - ExecuteDestroyBuffer(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreateImage])) - ExecuteCreateImage(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DestroyImage])) - ExecuteDestroyImage(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::FreeMemory])) - ExecuteFreeMemory(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::FreeMemoryPages])) - ExecuteFreeMemoryPages(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreateLostAllocation])) - ExecuteCreateLostAllocation(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemory])) - ExecuteAllocateMemory(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryPages])) - ExecuteAllocateMemoryPages(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryForBuffer])) - ExecuteAllocateMemoryForBufferOrImage(lineNumber, csvSplit, OBJECT_TYPE::BUFFER); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryForImage])) - ExecuteAllocateMemoryForBufferOrImage(lineNumber, csvSplit, OBJECT_TYPE::IMAGE); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::MapMemory])) - ExecuteMapMemory(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::UnmapMemory])) - ExecuteUnmapMemory(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::FlushAllocation])) - ExecuteFlushAllocation(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::InvalidateAllocation])) - ExecuteInvalidateAllocation(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::TouchAllocation])) - ExecuteTouchAllocation(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::GetAllocationInfo])) - ExecuteGetAllocationInfo(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::MakePoolAllocationsLost])) - ExecuteMakePoolAllocationsLost(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::ResizeAllocation])) - ExecuteResizeAllocation(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DefragmentationBegin])) - ExecuteDefragmentationBegin(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DefragmentationEnd])) - ExecuteDefragmentationEnd(lineNumber, csvSplit); - else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::SetPoolName])) - ExecuteSetPoolName(lineNumber, csvSplit); - else - { - if(IssueWarning()) - { - printf("Line %zu: Unknown function.\n", lineNumber); - } - } - } - else - { - if(IssueWarning()) - { - printf("Line %zu: Too few columns.\n", lineNumber); - } - } -} - -void Player::DumpStats(const char* fileNameFormat, size_t lineNumber, bool detailed) -{ - char* pStatsString = nullptr; - vmaBuildStatsString(m_Allocator, &pStatsString, detailed ? VK_TRUE : VK_FALSE); - - char fileName[MAX_PATH]; - sprintf_s(fileName, fileNameFormat, lineNumber); - - FILE* file = nullptr; - errno_t err = fopen_s(&file, fileName, "wb"); - if(err == 0) - { - fwrite(pStatsString, 1, strlen(pStatsString), file); - fclose(file); - } - else - { - printf("ERROR: Failed to write file: %s\n", fileName); - } - - vmaFreeStatsString(m_Allocator, pStatsString); -} - -void Player::Destroy(const Allocation& alloc) -{ - if(alloc.buffer) - { - assert(alloc.image == VK_NULL_HANDLE); - vmaDestroyBuffer(m_Allocator, alloc.buffer, alloc.allocation); - } - else if(alloc.image) - { - vmaDestroyImage(m_Allocator, alloc.image, alloc.allocation); - } - else - vmaFreeMemory(m_Allocator, alloc.allocation); -} - -bool Player::FindPool(size_t lineNumber, uint64_t origPool, VmaPool& outPool) -{ - outPool = VK_NULL_HANDLE; - - if(origPool != 0) - { - const auto poolIt = m_Pools.find(origPool); - if(poolIt != m_Pools.end()) - { - outPool = poolIt->second.pool; - return true; - } - else - { - if(IssueWarning()) - { - printf("Line %zu: Pool %llX not found.\n", lineNumber, origPool); - } - } - } - - return true; -} - -void Player::AddAllocation(size_t lineNumber, uint64_t origPtr, VkResult res, const char* functionName, Allocation&& allocDesc) -{ - if(origPtr) - { - if(res == VK_SUCCESS) - { - // Originally succeeded, currently succeeded. - // Just save pointer (done below). - } - else - { - // Originally succeeded, currently failed. - // Print warning. Save null pointer. - if(IssueWarning()) - { - printf("Line %zu: %s failed (%d), while originally succeeded.\n", lineNumber, functionName, res); - } - } - - const auto existingIt = m_Allocations.find(origPtr); - if(existingIt != m_Allocations.end()) - { - if(IssueWarning()) - { - printf("Line %zu: Allocation %llX already exists.\n", lineNumber, origPtr); - } - } - m_Allocations[origPtr] = std::move(allocDesc); - } - else - { - if(res == VK_SUCCESS) - { - // Originally failed, currently succeeded. - // Print warning, destroy the object. - if(IssueWarning()) - { - printf("Line %zu: %s succeeded, originally failed.\n", lineNumber, functionName); - } - - Destroy(allocDesc); - } - else - { - // Originally failed, currently failed. - // Print warning. - if(IssueWarning()) - { - printf("Line %zu: %s failed (%d), originally also failed.\n", lineNumber, functionName, res); - } - } - } -} - -bool Player::IssueWarning() -{ - if(g_Verbosity < VERBOSITY::MAXIMUM) - { - return m_WarningCount++ < MAX_WARNINGS_TO_SHOW; - } - else - { - ++m_WarningCount; - return true; - } -} - -int Player::InitVulkan() -{ - if(g_Verbosity == VERBOSITY::MAXIMUM) - { - printf("Initializing Vulkan...\n"); - } - - uint32_t instanceLayerPropCount = 0; - VkResult res = vkEnumerateInstanceLayerProperties(&instanceLayerPropCount, nullptr); - assert(res == VK_SUCCESS); - - std::vector<VkLayerProperties> instanceLayerProps(instanceLayerPropCount); - if(instanceLayerPropCount > 0) - { - res = vkEnumerateInstanceLayerProperties(&instanceLayerPropCount, instanceLayerProps.data()); - assert(res == VK_SUCCESS); - } - - const bool validationLayersAvailable = - IsLayerSupported(instanceLayerProps.data(), instanceLayerProps.size(), VALIDATION_LAYER_NAME); - - bool validationLayersEnabled = false; - switch(g_VK_LAYER_KHRONOS_validation) - { - case VULKAN_EXTENSION_REQUEST::DISABLED: - break; - case VULKAN_EXTENSION_REQUEST::DEFAULT: - validationLayersEnabled = validationLayersAvailable; - break; - case VULKAN_EXTENSION_REQUEST::ENABLED: - validationLayersEnabled = validationLayersAvailable; - if(!validationLayersAvailable) - { - printf("WARNING: %s layer cannot be enabled.\n", VALIDATION_LAYER_NAME); - } - break; - default: assert(0); - } - - uint32_t availableInstanceExtensionCount = 0; - res = vkEnumerateInstanceExtensionProperties(nullptr, &availableInstanceExtensionCount, nullptr); - assert(res == VK_SUCCESS); - std::vector<VkExtensionProperties> availableInstanceExtensions(availableInstanceExtensionCount); - if(availableInstanceExtensionCount > 0) - { - res = vkEnumerateInstanceExtensionProperties(nullptr, &availableInstanceExtensionCount, availableInstanceExtensions.data()); - assert(res == VK_SUCCESS); - } - - std::vector<const char*> enabledInstanceExtensions; - //enabledInstanceExtensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); - //enabledInstanceExtensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); - - std::vector<const char*> instanceLayers; - if(validationLayersEnabled) - { - instanceLayers.push_back(VALIDATION_LAYER_NAME); - } - - bool VK_KHR_get_physical_device_properties2_enabled = false; - bool VK_EXT_debug_utils_enabled = false; - for(const auto& extensionProperties : availableInstanceExtensions) - { - if(strcmp(extensionProperties.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0) - { - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - VK_KHR_get_physical_device_properties2_enabled = true; - } - else if(strcmp(extensionProperties.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) - { - if(validationLayersEnabled) - { - enabledInstanceExtensions.push_back("VK_EXT_debug_utils"); - VK_EXT_debug_utils_enabled = true; - } - } - } - - VkApplicationInfo appInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO }; - appInfo.pApplicationName = "VmaReplay"; - appInfo.applicationVersion = VK_MAKE_VERSION(2, 3, 0); - appInfo.pEngineName = "Vulkan Memory Allocator"; - appInfo.engineVersion = VK_MAKE_VERSION(2, 3, 0); - appInfo.apiVersion = VULKAN_API_VERSION; - - VkInstanceCreateInfo instInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; - instInfo.pApplicationInfo = &appInfo; - instInfo.enabledExtensionCount = (uint32_t)enabledInstanceExtensions.size(); - instInfo.ppEnabledExtensionNames = enabledInstanceExtensions.data(); - instInfo.enabledLayerCount = (uint32_t)instanceLayers.size(); - instInfo.ppEnabledLayerNames = instanceLayers.data(); - - res = vkCreateInstance(&instInfo, NULL, &m_VulkanInstance); - if(res != VK_SUCCESS) - { - printf("ERROR: vkCreateInstance failed (%d)\n", res); - return RESULT_ERROR_VULKAN; - } - - if(VK_EXT_debug_utils_enabled) - { - RegisterDebugCallbacks(); - } - - // Find physical device - - uint32_t physicalDeviceCount = 0; - res = vkEnumeratePhysicalDevices(m_VulkanInstance, &physicalDeviceCount, nullptr); - assert(res == VK_SUCCESS); - if(physicalDeviceCount == 0) - { - printf("ERROR: No Vulkan physical devices found.\n"); - return RESULT_ERROR_VULKAN; - } - - std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount); - res = vkEnumeratePhysicalDevices(m_VulkanInstance, &physicalDeviceCount, physicalDevices.data()); - assert(res == VK_SUCCESS); - - if(g_PhysicalDeviceIndex >= physicalDeviceCount) - { - printf("ERROR: Incorrect Vulkan physical device index %u. System has %u physical devices.\n", - g_PhysicalDeviceIndex, - physicalDeviceCount); - return RESULT_ERROR_VULKAN; - } - - m_PhysicalDevice = physicalDevices[0]; - - // Find queue family index - - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(m_PhysicalDevice, &queueFamilyCount, nullptr); - if(queueFamilyCount) - { - std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(m_PhysicalDevice, &queueFamilyCount, queueFamilies.data()); - for(uint32_t i = 0; i < queueFamilyCount; ++i) - { - if(queueFamilies[i].queueCount > 0) - { - if(m_GraphicsQueueFamilyIndex == UINT32_MAX && - (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) - { - m_GraphicsQueueFamilyIndex = i; - } - if(m_TransferQueueFamilyIndex == UINT32_MAX && - (queueFamilies[i].queueFlags & VK_QUEUE_TRANSFER_BIT) != 0) - { - m_TransferQueueFamilyIndex = i; - } - } - } - } - if(m_GraphicsQueueFamilyIndex == UINT_MAX) - { - printf("ERROR: Couldn't find graphics queue.\n"); - return RESULT_ERROR_VULKAN; - } - if(m_TransferQueueFamilyIndex == UINT_MAX) - { - printf("ERROR: Couldn't find transfer queue.\n"); - return RESULT_ERROR_VULKAN; - } - - VkPhysicalDeviceFeatures supportedFeatures; - vkGetPhysicalDeviceFeatures(m_PhysicalDevice, &supportedFeatures); - - // Create logical device - - const float queuePriority = 1.f; - - VkDeviceQueueCreateInfo deviceQueueCreateInfo[2] = {}; - deviceQueueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - deviceQueueCreateInfo[0].queueFamilyIndex = m_GraphicsQueueFamilyIndex; - deviceQueueCreateInfo[0].queueCount = 1; - deviceQueueCreateInfo[0].pQueuePriorities = &queuePriority; - - if(m_TransferQueueFamilyIndex != m_GraphicsQueueFamilyIndex) - { - deviceQueueCreateInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - deviceQueueCreateInfo[1].queueFamilyIndex = m_TransferQueueFamilyIndex; - deviceQueueCreateInfo[1].queueCount = 1; - deviceQueueCreateInfo[1].pQueuePriorities = &queuePriority; - } - - // Enable something what may interact with memory/buffer/image support. - VkPhysicalDeviceFeatures enabledFeatures; - InitVulkanFeatures(enabledFeatures, supportedFeatures); - - bool VK_KHR_get_memory_requirements2_available = false; - - // Determine list of device extensions to enable. - std::vector<const char*> enabledDeviceExtensions; - //enabledDeviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); - bool memoryBudgetAvailable = false; - { - uint32_t propertyCount = 0; - res = vkEnumerateDeviceExtensionProperties(m_PhysicalDevice, nullptr, &propertyCount, nullptr); - assert(res == VK_SUCCESS); - - if(propertyCount) - { - std::vector<VkExtensionProperties> properties{propertyCount}; - res = vkEnumerateDeviceExtensionProperties(m_PhysicalDevice, nullptr, &propertyCount, properties.data()); - assert(res == VK_SUCCESS); - - for(uint32_t i = 0; i < propertyCount; ++i) - { - if(strcmp(properties[i].extensionName, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME) == 0) - { - VK_KHR_get_memory_requirements2_available = true; - } - else if(strcmp(properties[i].extensionName, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) == 0) - { - if(VK_KHR_get_physical_device_properties2_enabled) - { - memoryBudgetAvailable = true; - } - } - } - } - } - - switch(g_VK_EXT_memory_budget_request) - { - case VULKAN_EXTENSION_REQUEST::DISABLED: - break; - case VULKAN_EXTENSION_REQUEST::DEFAULT: - m_MemoryBudgetEnabled = memoryBudgetAvailable; - break; - case VULKAN_EXTENSION_REQUEST::ENABLED: - m_MemoryBudgetEnabled = memoryBudgetAvailable; - if(!memoryBudgetAvailable) - { - printf("WARNING: VK_EXT_memory_budget extension cannot be enabled.\n"); - } - break; - default: assert(0); - } - - if(g_VK_AMD_device_coherent_memory_request == VULKAN_EXTENSION_REQUEST::ENABLED) - { - printf("WARNING: AMD_device_coherent_memory requested but not currently supported by the player.\n"); - } - - if(m_MemoryBudgetEnabled) - { - enabledDeviceExtensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); - } - - VkDeviceCreateInfo deviceCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; - deviceCreateInfo.enabledExtensionCount = (uint32_t)enabledDeviceExtensions.size(); - deviceCreateInfo.ppEnabledExtensionNames = !enabledDeviceExtensions.empty() ? enabledDeviceExtensions.data() : nullptr; - deviceCreateInfo.queueCreateInfoCount = m_TransferQueueFamilyIndex != m_GraphicsQueueFamilyIndex ? 2 : 1; - deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfo; - deviceCreateInfo.pEnabledFeatures = &enabledFeatures; - - res = vkCreateDevice(m_PhysicalDevice, &deviceCreateInfo, nullptr, &m_Device); - if(res != VK_SUCCESS) - { - printf("ERROR: vkCreateDevice failed (%d)\n", res); - return RESULT_ERROR_VULKAN; - } - - // Fetch queues - vkGetDeviceQueue(m_Device, m_GraphicsQueueFamilyIndex, 0, &m_GraphicsQueue); - vkGetDeviceQueue(m_Device, m_TransferQueueFamilyIndex, 0, &m_TransferQueue); - - // Create memory allocator - - VmaDeviceMemoryCallbacks deviceMemoryCallbacks = {}; - deviceMemoryCallbacks.pfnAllocate = AllocateDeviceMemoryCallback; - deviceMemoryCallbacks.pfnFree = FreeDeviceMemoryCallback; - - VmaAllocatorCreateInfo allocatorInfo = {}; - allocatorInfo.instance = m_VulkanInstance; - allocatorInfo.physicalDevice = m_PhysicalDevice; - allocatorInfo.device = m_Device; - allocatorInfo.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; - allocatorInfo.pDeviceMemoryCallbacks = &deviceMemoryCallbacks; - allocatorInfo.vulkanApiVersion = VULKAN_API_VERSION; - - if(m_MemoryBudgetEnabled) - { - allocatorInfo.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; - } - - res = vmaCreateAllocator(&allocatorInfo, &m_Allocator); - if(res != VK_SUCCESS) - { - printf("ERROR: vmaCreateAllocator failed (%d)\n", res); - return RESULT_ERROR_VULKAN; - } - - vmaGetPhysicalDeviceProperties(m_Allocator, &m_DevProps); - vmaGetMemoryProperties(m_Allocator, &m_MemProps); - - // Create command pool - - VkCommandPoolCreateInfo cmdPoolCreateInfo = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; - cmdPoolCreateInfo.queueFamilyIndex = m_TransferQueueFamilyIndex; - cmdPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; - - res = vkCreateCommandPool(m_Device, &cmdPoolCreateInfo, nullptr, &m_CommandPool); - if(res != VK_SUCCESS) - { - printf("ERROR: vkCreateCommandPool failed (%d)\n", res); - return RESULT_ERROR_VULKAN; - } - - // Create command buffer - - VkCommandBufferAllocateInfo cmdBufAllocInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; - cmdBufAllocInfo.commandBufferCount = 1; - cmdBufAllocInfo.commandPool = m_CommandPool; - cmdBufAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - res = vkAllocateCommandBuffers(m_Device, &cmdBufAllocInfo, &m_CommandBuffer); - if(res != VK_SUCCESS) - { - printf("ERROR: vkAllocateCommandBuffers failed (%d)\n", res); - return RESULT_ERROR_VULKAN; - } - - return 0; -} - -void Player::FinalizeVulkan() -{ - if(!m_DefragmentationContexts.empty()) - { - printf("WARNING: Defragmentation contexts not destroyed: %zu.\n", m_DefragmentationContexts.size()); - - if(CLEANUP_LEAKED_OBJECTS) - { - for(const auto& it : m_DefragmentationContexts) - { - vmaDefragmentationEnd(m_Allocator, it.second); - } - } - - m_DefragmentationContexts.clear(); - } - - if(!m_Allocations.empty()) - { - printf("WARNING: Allocations not destroyed: %zu.\n", m_Allocations.size()); - - if(CLEANUP_LEAKED_OBJECTS) - { - for(const auto it : m_Allocations) - { - Destroy(it.second); - } - } - - m_Allocations.clear(); - } - - if(!m_Pools.empty()) - { - printf("WARNING: Custom pools not destroyed: %zu.\n", m_Pools.size()); - - if(CLEANUP_LEAKED_OBJECTS) - { - for(const auto it : m_Pools) - { - vmaDestroyPool(m_Allocator, it.second.pool); - } - } - - m_Pools.clear(); - } - - vkDeviceWaitIdle(m_Device); - - if(m_CommandBuffer != VK_NULL_HANDLE) - { - vkFreeCommandBuffers(m_Device, m_CommandPool, 1, &m_CommandBuffer); - m_CommandBuffer = VK_NULL_HANDLE; - } - - if(m_CommandPool != VK_NULL_HANDLE) - { - vkDestroyCommandPool(m_Device, m_CommandPool, nullptr); - m_CommandPool = VK_NULL_HANDLE; - } - - if(m_Allocator != VK_NULL_HANDLE) - { - vmaDestroyAllocator(m_Allocator); - m_Allocator = nullptr; - } - - if(m_Device != VK_NULL_HANDLE) - { - vkDestroyDevice(m_Device, nullptr); - m_Device = nullptr; - } - - UnregisterDebugCallbacks(); - - if(m_VulkanInstance != VK_NULL_HANDLE) - { - vkDestroyInstance(m_VulkanInstance, NULL); - m_VulkanInstance = VK_NULL_HANDLE; - } -} - -void Player::RegisterDebugCallbacks() -{ - static const VkDebugUtilsMessageSeverityFlagsEXT DEBUG_UTILS_MESSENGER_MESSAGE_SEVERITY = - //VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | - //VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - static const VkDebugUtilsMessageTypeFlagsEXT DEBUG_UTILS_MESSENGER_MESSAGE_TYPE = - VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - - m_vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( - m_VulkanInstance, "vkCreateDebugUtilsMessengerEXT"); - m_vkDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( - m_VulkanInstance, "vkDestroyDebugUtilsMessengerEXT"); - assert(m_vkCreateDebugUtilsMessengerEXT); - assert(m_vkDestroyDebugUtilsMessengerEXT); - - VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT }; - messengerCreateInfo.messageSeverity = DEBUG_UTILS_MESSENGER_MESSAGE_SEVERITY; - messengerCreateInfo.messageType = DEBUG_UTILS_MESSENGER_MESSAGE_TYPE; - messengerCreateInfo.pfnUserCallback = MyDebugReportCallback; - VkResult res = m_vkCreateDebugUtilsMessengerEXT(m_VulkanInstance, &messengerCreateInfo, nullptr, &m_DebugUtilsMessenger); - if(res != VK_SUCCESS) - { - printf("ERROR: vkCreateDebugUtilsMessengerEXT failed (%d)\n", res); - m_DebugUtilsMessenger = VK_NULL_HANDLE; - } -} - -void Player::UnregisterDebugCallbacks() -{ - if(m_DebugUtilsMessenger) - { - m_vkDestroyDebugUtilsMessengerEXT(m_VulkanInstance, m_DebugUtilsMessenger, nullptr); - } -} - -void Player::Defragment() -{ - VmaStats stats; - vmaCalculateStats(m_Allocator, &stats); - PrintStats(stats, "before defragmentation"); - - const size_t allocCount = m_Allocations.size(); - std::vector<VmaAllocation> allocations(allocCount); - size_t notNullAllocCount = 0; - for(const auto& it : m_Allocations) - { - if(it.second.allocation != VK_NULL_HANDLE) - { - allocations[notNullAllocCount] = it.second.allocation; - ++notNullAllocCount; - } - } - if(notNullAllocCount == 0) - { - printf(" Nothing to defragment.\n"); - return; - } - - allocations.resize(notNullAllocCount); - std::vector<VkBool32> allocationsChanged(notNullAllocCount); - - VmaDefragmentationStats defragStats = {}; - - VkCommandBufferBeginInfo cmdBufBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - cmdBufBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - VkResult res = vkBeginCommandBuffer(m_CommandBuffer, &cmdBufBeginInfo); - if(res != VK_SUCCESS) - { - printf("ERROR: vkBeginCommandBuffer failed (%d)\n", res); - return; - } - - const time_point timeBeg = std::chrono::high_resolution_clock::now(); - - VmaDefragmentationInfo2 defragInfo = {}; - defragInfo.allocationCount = (uint32_t)notNullAllocCount; - defragInfo.pAllocations = allocations.data(); - defragInfo.pAllocationsChanged = allocationsChanged.data(); - defragInfo.maxCpuAllocationsToMove = UINT32_MAX; - defragInfo.maxCpuBytesToMove = VK_WHOLE_SIZE; - defragInfo.maxGpuAllocationsToMove = UINT32_MAX; - defragInfo.maxGpuBytesToMove = VK_WHOLE_SIZE; - defragInfo.flags = g_DefragmentationFlags; - defragInfo.commandBuffer = m_CommandBuffer; - - VmaDefragmentationContext defragCtx = VK_NULL_HANDLE; - res = vmaDefragmentationBegin(m_Allocator, &defragInfo, &defragStats, &defragCtx); - - const time_point timeAfterDefragBegin = std::chrono::high_resolution_clock::now(); - - vkEndCommandBuffer(m_CommandBuffer); - - if(res >= VK_SUCCESS) - { - VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &m_CommandBuffer; - vkQueueSubmit(m_TransferQueue, 1, &submitInfo, VK_NULL_HANDLE); - vkQueueWaitIdle(m_TransferQueue); - - const time_point timeAfterGpu = std::chrono::high_resolution_clock::now(); - - vmaDefragmentationEnd(m_Allocator, defragCtx); - - const time_point timeAfterDefragEnd = std::chrono::high_resolution_clock::now(); - - const duration defragDurationBegin = timeAfterDefragBegin - timeBeg; - const duration defragDurationGpu = timeAfterGpu - timeAfterDefragBegin; - const duration defragDurationEnd = timeAfterDefragEnd - timeAfterGpu; - - // If anything changed. - if(defragStats.allocationsMoved > 0) - { - // Go over allocation that changed and destroy their buffers and images. - size_t i = 0; - 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()) - { - // Do nothing - the function was deprecated and has been removed. - //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_KHRONOS_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_KHRONOS_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_KHRONOS_VALIDATION: - { - bool newValue; - if(StrRangeToBool(StrRange(cmdLineParser.GetParameter()), newValue)) - { - g_VK_LAYER_KHRONOS_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; - } -} +// +// Copyright (c) 2018-2021 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include "VmaUsage.h" +#include "Common.h" +#include "Constants.h" +#include <unordered_map> +#include <map> +#include <algorithm> + +static VERBOSITY g_Verbosity = VERBOSITY::DEFAULT; + +static const uint32_t VULKAN_API_VERSION = VK_API_VERSION_1_1; + +namespace DetailedStats +{ + +struct Flag +{ + uint32_t setCount = 0; + + void PostValue(bool v) + { + if(v) + { + ++setCount; + } + } + + void Print(uint32_t totalCount) const + { + if(setCount) + { + printf(" %u (%.2f%%)\n", setCount, (double)setCount * 100.0 / (double)totalCount); + } + else + { + printf(" 0\n"); + } + } +}; + +struct Enum +{ + Enum(size_t itemCount, const char* const* itemNames, const uint32_t* itemValues = nullptr) : + m_ItemCount(itemCount), + m_ItemNames(itemNames), + m_ItemValues(itemValues) + { + } + + void PostValue(uint32_t v) + { + if(v < _countof(m_BaseCount)) + { + ++m_BaseCount[v]; + } + else + { + auto it = m_ExtendedCount.find(v); + if(it != m_ExtendedCount.end()) + { + ++it->second; + } + else + { + m_ExtendedCount.insert(std::make_pair(v, 1u)); + } + } + } + + void Print(uint32_t totalCount) const + { + if(totalCount && + (!m_ExtendedCount.empty() || std::count_if(m_BaseCount, m_BaseCount + _countof(m_BaseCount), [](uint32_t v) { return v > 0; }))) + { + printf("\n"); + + for(size_t i = 0; i < _countof(m_BaseCount); ++i) + { + const uint32_t currCount = m_BaseCount[i]; + if(currCount) + { + PrintItem((uint32_t)i, currCount, totalCount); + } + } + + for(const auto& it : m_ExtendedCount) + { + PrintItem(it.first, it.second, totalCount); + } + } + else + { + printf(" 0\n"); + } + } + +private: + const size_t m_ItemCount; + const char* const* const m_ItemNames; + const uint32_t* const m_ItemValues; + + uint32_t m_BaseCount[32] = {}; + std::map<uint32_t, uint32_t> m_ExtendedCount; + + void PrintItem(uint32_t value, uint32_t count, uint32_t totalCount) const + { + size_t itemIndex = m_ItemCount; + if(m_ItemValues) + { + for(itemIndex = 0; itemIndex < m_ItemCount; ++itemIndex) + { + if(m_ItemValues[itemIndex] == value) + { + break; + } + } + } + else + { + if(value < m_ItemCount) + { + itemIndex = value; + } + } + + if(itemIndex < m_ItemCount) + { + printf(" %s: ", m_ItemNames[itemIndex]); + } + else + { + printf(" 0x%X: ", value); + } + + printf("%u (%.2f%%)\n", count, (double)count * 100.0 / (double)totalCount); + } +}; + +struct FlagSet +{ + uint32_t count[32] = {}; + + FlagSet(size_t count, const char* const* names, const uint32_t* values = nullptr) : + m_Count(count), + m_Names(names), + m_Values(values) + { + } + + void PostValue(uint32_t v) + { + for(size_t i = 0; i < 32; ++i) + { + if((v & (1u << i)) != 0) + { + ++count[i]; + } + } + } + + void Print(uint32_t totalCount) const + { + if(totalCount && + std::count_if(count, count + _countof(count), [](uint32_t v) { return v > 0; })) + { + printf("\n"); + for(uint32_t bitIndex = 0; bitIndex < 32; ++bitIndex) + { + const uint32_t currCount = count[bitIndex]; + if(currCount) + { + size_t itemIndex = m_Count; + if(m_Values) + { + for(itemIndex = 0; itemIndex < m_Count; ++itemIndex) + { + if(m_Values[itemIndex] == (1u << bitIndex)) + { + break; + } + } + } + else + { + if(bitIndex < m_Count) + { + itemIndex = bitIndex; + } + } + + if(itemIndex < m_Count) + { + printf(" %s: ", m_Names[itemIndex]); + } + else + { + printf(" 0x%X: ", 1u << bitIndex); + } + + printf("%u (%.2f%%)\n", currCount, (double)currCount * 100.0 / (double)totalCount); + } + } + } + else + { + printf(" 0\n"); + } + } + +private: + const size_t m_Count; + const char* const* const m_Names; + const uint32_t* const m_Values; +}; + +// T should be unsigned int +template<typename T> +struct MinMaxAvg +{ + T min = std::numeric_limits<T>::max(); + T max = 0; + T sum = T(); + + void PostValue(T v) + { + this->min = std::min(this->min, v); + this->max = std::max(this->max, v); + sum += v; + } + + void Print(uint32_t totalCount) const + { + if(totalCount && sum > T()) + { + if(this->min == this->max) + { + printf(" %llu\n", (uint64_t)this->max); + } + else + { + printf("\n Min: %llu\n Max: %llu\n Avg: %llu\n", + (uint64_t)this->min, + (uint64_t)this->max, + round_div<uint64_t>(this->sum, totalCount)); + } + } + else + { + printf(" 0\n"); + } + } +}; + +template<typename T> +struct BitMask +{ + uint32_t zeroCount = 0; + uint32_t maxCount = 0; + + void PostValue(T v) + { + if(v) + { + if(v == std::numeric_limits<T>::max()) + { + ++maxCount; + } + } + else + { + ++zeroCount; + } + } + + void Print(uint32_t totalCount) const + { + if(totalCount > 0 && zeroCount < totalCount) + { + const uint32_t otherCount = totalCount - (zeroCount + maxCount); + + printf("\n 0: %u (%.2f%%)\n Max: %u (%.2f%%)\n Other: %u (%.2f%%)\n", + zeroCount, (double)zeroCount * 100.0 / (double)totalCount, + maxCount, (double)maxCount * 100.0 / (double)totalCount, + otherCount, (double)otherCount * 100.0 / (double)totalCount); + } + else + { + printf(" 0\n"); + } + } +}; + +struct CountPerMemType +{ + uint32_t count[VK_MAX_MEMORY_TYPES] = {}; + + void PostValue(uint32_t v) + { + for(uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i) + { + if((v & (1u << i)) != 0) + { + ++count[i]; + } + } + } + + void Print(uint32_t totalCount) const + { + if(totalCount) + { + printf("\n"); + for(uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i) + { + if(count[i]) + { + printf(" %u: %u (%.2f%%)\n", i, count[i], + (double)count[i] * 100.0 / (double)totalCount); + } + } + } + else + { + printf(" 0\n"); + } + } +}; + +struct StructureStats +{ + uint32_t totalCount = 0; +}; + +#define PRINT_FIELD(name) \ + printf(" " #name ":"); \ + (name).Print(totalCount); +#define PRINT_FIELD_NAMED(name, nameStr) \ + printf(" " nameStr ":"); \ + (name).Print(totalCount); + +struct VmaPoolCreateInfoStats : public StructureStats +{ + CountPerMemType memoryTypeIndex; + FlagSet flags; + MinMaxAvg<VkDeviceSize> blockSize; + MinMaxAvg<size_t> minBlockCount; + MinMaxAvg<size_t> maxBlockCount; + Flag minMaxBlockCountEqual; + MinMaxAvg<uint32_t> frameInUseCount; + + VmaPoolCreateInfoStats() : + flags(VMA_POOL_CREATE_FLAG_COUNT, VMA_POOL_CREATE_FLAG_NAMES, VMA_POOL_CREATE_FLAG_VALUES) + { + } + + void PostValue(const VmaPoolCreateInfo& v) + { + ++totalCount; + + memoryTypeIndex.PostValue(v.memoryTypeIndex); + flags.PostValue(v.flags); + blockSize.PostValue(v.blockSize); + minBlockCount.PostValue(v.minBlockCount); + maxBlockCount.PostValue(v.maxBlockCount); + minMaxBlockCountEqual.PostValue(v.minBlockCount == v.maxBlockCount); + frameInUseCount.PostValue(v.frameInUseCount); + } + + void Print() const + { + if(totalCount == 0) + { + return; + } + + printf("VmaPoolCreateInfo (%u):\n", totalCount); + + PRINT_FIELD(memoryTypeIndex); + PRINT_FIELD(flags); + PRINT_FIELD(blockSize); + PRINT_FIELD(minBlockCount); + PRINT_FIELD(maxBlockCount); + PRINT_FIELD_NAMED(minMaxBlockCountEqual, "minBlockCount == maxBlockCount"); + PRINT_FIELD(frameInUseCount); + } +}; + +struct VkBufferCreateInfoStats : public StructureStats +{ + FlagSet flags; + MinMaxAvg<VkDeviceSize> size; + FlagSet usage; + Enum sharingMode; + + VkBufferCreateInfoStats() : + flags(VK_BUFFER_CREATE_FLAG_COUNT, VK_BUFFER_CREATE_FLAG_NAMES, VK_BUFFER_CREATE_FLAG_VALUES), + usage(VK_BUFFER_USAGE_FLAG_COUNT, VK_BUFFER_USAGE_FLAG_NAMES, VK_BUFFER_USAGE_FLAG_VALUES), + sharingMode(VK_SHARING_MODE_COUNT, VK_SHARING_MODE_NAMES) + { + } + + void PostValue(const VkBufferCreateInfo& v) + { + ++totalCount; + + flags.PostValue(v.flags); + size.PostValue(v.size); + usage.PostValue(v.usage); + sharingMode.PostValue(v.sharingMode); + } + + void Print() const + { + if(totalCount == 0) + { + return; + } + + printf("VkBufferCreateInfo (%u):\n", totalCount); + + PRINT_FIELD(flags); + PRINT_FIELD(size); + PRINT_FIELD(usage); + PRINT_FIELD(sharingMode); + } +}; + +struct VkImageCreateInfoStats : public StructureStats +{ + FlagSet flags; + Enum imageType; + Enum format; + MinMaxAvg<uint32_t> width, height, depth, mipLevels, arrayLayers; + Flag depthGreaterThanOne, mipLevelsGreaterThanOne, arrayLayersGreaterThanOne; + Enum samples; + Enum tiling; + FlagSet usage; + Enum sharingMode; + Enum initialLayout; + + VkImageCreateInfoStats() : + flags(VK_IMAGE_CREATE_FLAG_COUNT, VK_IMAGE_CREATE_FLAG_NAMES, VK_IMAGE_CREATE_FLAG_VALUES), + imageType(VK_IMAGE_TYPE_COUNT, VK_IMAGE_TYPE_NAMES), + format(VK_FORMAT_COUNT, VK_FORMAT_NAMES, VK_FORMAT_VALUES), + samples(VK_SAMPLE_COUNT_COUNT, VK_SAMPLE_COUNT_NAMES, VK_SAMPLE_COUNT_VALUES), + tiling(VK_IMAGE_TILING_COUNT, VK_IMAGE_TILING_NAMES), + usage(VK_IMAGE_USAGE_FLAG_COUNT, VK_IMAGE_USAGE_FLAG_NAMES, VK_IMAGE_USAGE_FLAG_VALUES), + sharingMode(VK_SHARING_MODE_COUNT, VK_SHARING_MODE_NAMES), + initialLayout(VK_IMAGE_LAYOUT_COUNT, VK_IMAGE_LAYOUT_NAMES, VK_IMAGE_LAYOUT_VALUES) + { + } + + void PostValue(const VkImageCreateInfo& v) + { + ++totalCount; + + flags.PostValue(v.flags); + imageType.PostValue(v.imageType); + format.PostValue(v.format); + width.PostValue(v.extent.width); + height.PostValue(v.extent.height); + depth.PostValue(v.extent.depth); + mipLevels.PostValue(v.mipLevels); + arrayLayers.PostValue(v.arrayLayers); + depthGreaterThanOne.PostValue(v.extent.depth > 1); + mipLevelsGreaterThanOne.PostValue(v.mipLevels > 1); + arrayLayersGreaterThanOne.PostValue(v.arrayLayers > 1); + samples.PostValue(v.samples); + tiling.PostValue(v.tiling); + usage.PostValue(v.usage); + sharingMode.PostValue(v.sharingMode); + initialLayout.PostValue(v.initialLayout); + } + + void Print() const + { + if(totalCount == 0) + { + return; + } + + printf("VkImageCreateInfo (%u):\n", totalCount); + + PRINT_FIELD(flags); + PRINT_FIELD(imageType); + PRINT_FIELD(format); + PRINT_FIELD(width); + PRINT_FIELD(height); + PRINT_FIELD(depth); + PRINT_FIELD(mipLevels); + PRINT_FIELD(arrayLayers); + PRINT_FIELD_NAMED(depthGreaterThanOne, "depth > 1"); + PRINT_FIELD_NAMED(mipLevelsGreaterThanOne, "mipLevels > 1"); + PRINT_FIELD_NAMED(arrayLayersGreaterThanOne, "arrayLayers > 1"); + PRINT_FIELD(samples); + PRINT_FIELD(tiling); + PRINT_FIELD(usage); + PRINT_FIELD(sharingMode); + PRINT_FIELD(initialLayout); + } +}; + +struct VmaAllocationCreateInfoStats : public StructureStats +{ + FlagSet flags; + Enum usage; + FlagSet requiredFlags, preferredFlags; + Flag requiredFlagsNotZero, preferredFlagsNotZero; + BitMask<uint32_t> memoryTypeBits; + Flag poolNotNull; + Flag userDataNotNull; + + VmaAllocationCreateInfoStats() : + flags(VMA_ALLOCATION_CREATE_FLAG_COUNT, VMA_ALLOCATION_CREATE_FLAG_NAMES, VMA_ALLOCATION_CREATE_FLAG_VALUES), + usage(VMA_MEMORY_USAGE_COUNT, VMA_MEMORY_USAGE_NAMES), + requiredFlags(VK_MEMORY_PROPERTY_FLAG_COUNT, VK_MEMORY_PROPERTY_FLAG_NAMES, VK_MEMORY_PROPERTY_FLAG_VALUES), + preferredFlags(VK_MEMORY_PROPERTY_FLAG_COUNT, VK_MEMORY_PROPERTY_FLAG_NAMES, VK_MEMORY_PROPERTY_FLAG_VALUES) + { + } + + void PostValue(const VmaAllocationCreateInfo& v, size_t count = 1) + { + totalCount += (uint32_t)count; + + for(size_t i = 0; i < count; ++i) + { + flags.PostValue(v.flags); + usage.PostValue(v.usage); + requiredFlags.PostValue(v.requiredFlags); + preferredFlags.PostValue(v.preferredFlags); + requiredFlagsNotZero.PostValue(v.requiredFlags != 0); + preferredFlagsNotZero.PostValue(v.preferredFlags != 0); + memoryTypeBits.PostValue(v.memoryTypeBits); + poolNotNull.PostValue(v.pool != VK_NULL_HANDLE); + userDataNotNull.PostValue(v.pUserData != nullptr); + } + } + + void Print() const + { + if(totalCount == 0) + { + return; + } + + printf("VmaAllocationCreateInfo (%u):\n", totalCount); + + PRINT_FIELD(flags); + PRINT_FIELD(usage); + PRINT_FIELD(requiredFlags); + PRINT_FIELD(preferredFlags); + PRINT_FIELD_NAMED(requiredFlagsNotZero, "requiredFlags != 0"); + PRINT_FIELD_NAMED(preferredFlagsNotZero, "preferredFlags != 0"); + PRINT_FIELD(memoryTypeBits); + PRINT_FIELD_NAMED(poolNotNull, "pool != VK_NULL_HANDLE"); + PRINT_FIELD_NAMED(userDataNotNull, "pUserData != nullptr"); + } +}; + +struct VmaAllocateMemoryPagesStats : public StructureStats +{ + MinMaxAvg<size_t> allocationCount; + + void PostValue(size_t allocationCount) + { + this->allocationCount.PostValue(allocationCount); + } + + void Print() const + { + if(totalCount == 0) + { + return; + } + + printf("vmaAllocateMemoryPages (%u):\n", totalCount); + + PRINT_FIELD(allocationCount); + } +}; + +struct VmaDefragmentationInfo2Stats : public StructureStats +{ + BitMask<VkDeviceSize> maxCpuBytesToMove; + BitMask<uint32_t> maxCpuAllocationsToMove; + BitMask<VkDeviceSize> maxGpuBytesToMove; + BitMask<uint32_t> maxGpuAllocationsToMove; + Flag commandBufferNotNull; + MinMaxAvg<uint32_t> allocationCount; + Flag allocationCountNotZero; + MinMaxAvg<uint32_t> poolCount; + Flag poolCountNotZero; + + void PostValue(const VmaDefragmentationInfo2& info) + { + ++totalCount; + + maxCpuBytesToMove.PostValue(info.maxCpuBytesToMove); + maxCpuAllocationsToMove.PostValue(info.maxCpuAllocationsToMove); + maxGpuBytesToMove.PostValue(info.maxGpuBytesToMove); + maxGpuAllocationsToMove.PostValue(info.maxGpuAllocationsToMove); + commandBufferNotNull.PostValue(info.commandBuffer != VK_NULL_HANDLE); + allocationCount.PostValue(info.allocationCount); + allocationCountNotZero.PostValue(info.allocationCount != 0); + poolCount.PostValue(info.poolCount); + poolCountNotZero.PostValue(info.poolCount != 0); + } + + void Print() const + { + if(totalCount == 0) + { + return; + } + + printf("VmaDefragmentationInfo2 (%u):\n", totalCount); + + PRINT_FIELD(maxCpuBytesToMove); + PRINT_FIELD(maxCpuAllocationsToMove); + PRINT_FIELD(maxGpuBytesToMove); + PRINT_FIELD(maxGpuAllocationsToMove); + PRINT_FIELD_NAMED(commandBufferNotNull, "commandBuffer != VK_NULL_HANDLE"); + PRINT_FIELD(allocationCount); + PRINT_FIELD_NAMED(allocationCountNotZero, "allocationCount > 0"); + PRINT_FIELD(poolCount); + PRINT_FIELD_NAMED(poolCountNotZero, "poolCount > 0"); + } +}; + +#undef PRINT_FIELD_NAMED +#undef PRINT_FIELD + +} // namespace DetailedStats + +// Set this to false to disable deleting leaked VmaAllocation, VmaPool objects +// and let VMA report asserts about them. +static const bool CLEANUP_LEAKED_OBJECTS = true; + +static std::string g_FilePath; +// Most significant 16 bits are major version, least significant 16 bits are minor version. +static uint32_t g_FileVersion; + +inline uint32_t MakeVersion(uint32_t major, uint32_t minor) { return (major << 16) | minor; } +inline uint32_t GetVersionMajor(uint32_t version) { return version >> 16; } +inline uint32_t GetVersionMinor(uint32_t version) { return version & 0xFFFF; } + +static size_t g_IterationCount = 1; +static uint32_t g_PhysicalDeviceIndex = 0; +static RangeSequence<size_t> g_LineRanges; +static bool g_UserDataEnabled = true; +static bool g_MemStatsEnabled = false; +VULKAN_EXTENSION_REQUEST g_VK_LAYER_KHRONOS_validation = VULKAN_EXTENSION_REQUEST::DEFAULT; +VULKAN_EXTENSION_REQUEST g_VK_EXT_memory_budget_request = VULKAN_EXTENSION_REQUEST::DEFAULT; +VULKAN_EXTENSION_REQUEST g_VK_AMD_device_coherent_memory_request = VULKAN_EXTENSION_REQUEST::DEFAULT; + +struct StatsAfterLineEntry +{ + size_t line; + bool detailed; + + bool operator<(const StatsAfterLineEntry& rhs) const { return line < rhs.line; } + bool operator==(const StatsAfterLineEntry& rhs) const { return line == rhs.line; } +}; +static std::vector<StatsAfterLineEntry> g_DumpStatsAfterLine; +static std::vector<size_t> g_DefragmentAfterLine; +static uint32_t g_DefragmentationFlags = 0; +static size_t g_DumpStatsAfterLineNextIndex = 0; +static size_t g_DefragmentAfterLineNextIndex = 0; + +static bool ValidateFileVersion() +{ + if(GetVersionMajor(g_FileVersion) == 1 && + GetVersionMinor(g_FileVersion) <= 8) + { + return true; + } + + return false; +} + +static bool ParseFileVersion(const StrRange& s) +{ + CsvSplit csvSplit; + csvSplit.Set(s, 2); + uint32_t major, minor; + if(csvSplit.GetCount() == 2 && + StrRangeToUint(csvSplit.GetRange(0), major) && + StrRangeToUint(csvSplit.GetRange(1), minor)) + { + g_FileVersion = (major << 16) | minor; + return true; + } + else + { + return false; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// class Statistics + +class Statistics +{ +public: + static uint32_t BufferUsageToClass(uint32_t usage); + static uint32_t ImageUsageToClass(uint32_t usage); + + Statistics(); + ~Statistics(); + void Init(uint32_t memHeapCount, uint32_t memTypeCount); + void PrintDeviceMemStats() const; + void PrintMemStats() const; + void PrintDetailedStats() const; + + const size_t* GetFunctionCallCount() const { return m_FunctionCallCount; } + size_t GetImageCreationCount(uint32_t imgClass) const { return m_ImageCreationCount[imgClass]; } + size_t GetLinearImageCreationCount() const { return m_LinearImageCreationCount; } + size_t GetBufferCreationCount(uint32_t bufClass) const { return m_BufferCreationCount[bufClass]; } + size_t GetAllocationCreationCount() const { return (size_t)m_VmaAllocationCreateInfo.totalCount + m_CreateLostAllocationCount; } + size_t GetPoolCreationCount() const { return m_VmaPoolCreateInfo.totalCount; } + size_t GetBufferCreationCount() const { return (size_t)m_VkBufferCreateInfo.totalCount; } + + void RegisterFunctionCall(VMA_FUNCTION func); + void RegisterCreateImage(const VkImageCreateInfo& info); + void RegisterCreateBuffer(const VkBufferCreateInfo& info); + void RegisterCreatePool(const VmaPoolCreateInfo& info); + void RegisterCreateAllocation(const VmaAllocationCreateInfo& info, size_t allocCount = 1); + void RegisterCreateLostAllocation() { ++m_CreateLostAllocationCount; } + void RegisterAllocateMemoryPages(size_t allocCount) { m_VmaAllocateMemoryPages.PostValue(allocCount); } + void RegisterDefragmentation(const VmaDefragmentationInfo2& info); + + void RegisterDeviceMemoryAllocation(uint32_t memoryType, VkDeviceSize size); + void UpdateMemStats(const VmaStats& currStats); + +private: + uint32_t m_MemHeapCount = 0; + uint32_t m_MemTypeCount = 0; + + size_t m_FunctionCallCount[(size_t)VMA_FUNCTION::Count] = {}; + size_t m_ImageCreationCount[4] = { }; + size_t m_LinearImageCreationCount = 0; + size_t m_BufferCreationCount[4] = { }; + + struct DeviceMemStatInfo + { + size_t allocationCount; + VkDeviceSize allocationTotalSize; + }; + struct DeviceMemStats + { + DeviceMemStatInfo memoryType[VK_MAX_MEMORY_TYPES]; + DeviceMemStatInfo total; + } m_DeviceMemStats; + + // Structure similar to VmaStatInfo, but not the same. + struct MemStatInfo + { + uint32_t blockCount; + uint32_t allocationCount; + uint32_t unusedRangeCount; + VkDeviceSize usedBytes; + VkDeviceSize unusedBytes; + VkDeviceSize totalBytes; + }; + struct MemStats + { + MemStatInfo memoryType[VK_MAX_MEMORY_TYPES]; + MemStatInfo memoryHeap[VK_MAX_MEMORY_HEAPS]; + MemStatInfo total; + } m_PeakMemStats; + + DetailedStats::VmaPoolCreateInfoStats m_VmaPoolCreateInfo; + DetailedStats::VkBufferCreateInfoStats m_VkBufferCreateInfo; + DetailedStats::VkImageCreateInfoStats m_VkImageCreateInfo; + DetailedStats::VmaAllocationCreateInfoStats m_VmaAllocationCreateInfo; + size_t m_CreateLostAllocationCount = 0; + DetailedStats::VmaAllocateMemoryPagesStats m_VmaAllocateMemoryPages; + DetailedStats::VmaDefragmentationInfo2Stats m_VmaDefragmentationInfo2; + + void UpdateMemStatInfo(MemStatInfo& inoutPeakInfo, const VmaStatInfo& currInfo); + static void PrintMemStatInfo(const MemStatInfo& info); +}; + +// Hack for global AllocateDeviceMemoryCallback. +static Statistics* g_Statistics; + +static void VKAPI_CALL AllocateDeviceMemoryCallback( + VmaAllocator allocator, + uint32_t memoryType, + VkDeviceMemory memory, + VkDeviceSize size, + void* pUserData) +{ + g_Statistics->RegisterDeviceMemoryAllocation(memoryType, size); +} + +/// Callback function called before vkFreeMemory. +static void VKAPI_CALL FreeDeviceMemoryCallback( + VmaAllocator allocator, + uint32_t memoryType, + VkDeviceMemory memory, + VkDeviceSize size, + void* pUserData) +{ + // Nothing. +} + +uint32_t Statistics::BufferUsageToClass(uint32_t usage) +{ + // Buffer is used as source of data for fixed-function stage of graphics pipeline. + // It's indirect, vertex, or index buffer. + if ((usage & (VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_INDEX_BUFFER_BIT)) != 0) + { + return 0; + } + // Buffer is accessed by shaders for load/store/atomic. + // Aka "UAV" + else if ((usage & (VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT)) != 0) + { + return 1; + } + // Buffer is accessed by shaders for reading uniform data. + // Aka "constant buffer" + else if ((usage & (VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT)) != 0) + { + return 2; + } + // Any other type of buffer. + // Notice that VK_BUFFER_USAGE_TRANSFER_SRC_BIT and VK_BUFFER_USAGE_TRANSFER_DST_BIT + // flags are intentionally ignored. + else + { + return 3; + } +} + +uint32_t Statistics::ImageUsageToClass(uint32_t usage) +{ + // Image is used as depth/stencil "texture/surface". + if ((usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0) + { + return 0; + } + // Image is used as other type of attachment. + // Aka "render target" + else if ((usage & (VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) != 0) + { + return 1; + } + // Image is accessed by shaders for sampling. + // Aka "texture" + else if ((usage & VK_IMAGE_USAGE_SAMPLED_BIT) != 0) + { + return 2; + } + // Any other type of image. + // Notice that VK_IMAGE_USAGE_TRANSFER_SRC_BIT and VK_IMAGE_USAGE_TRANSFER_DST_BIT + // flags are intentionally ignored. + else + { + return 3; + } +} + +Statistics::Statistics() +{ + ZeroMemory(&m_DeviceMemStats, sizeof(m_DeviceMemStats)); + ZeroMemory(&m_PeakMemStats, sizeof(m_PeakMemStats)); + + assert(g_Statistics == nullptr); + g_Statistics = this; +} + +Statistics::~Statistics() +{ + assert(g_Statistics == this); + g_Statistics = nullptr; +} + +void Statistics::Init(uint32_t memHeapCount, uint32_t memTypeCount) +{ + m_MemHeapCount = memHeapCount; + m_MemTypeCount = memTypeCount; +} + +void Statistics::PrintDeviceMemStats() const +{ + printf("Successful device memory allocations:\n"); + printf(" Total: count = %zu, total size = %llu\n", + m_DeviceMemStats.total.allocationCount, m_DeviceMemStats.total.allocationTotalSize); + for(uint32_t i = 0; i < m_MemTypeCount; ++i) + { + printf(" Memory type %u: count = %zu, total size = %llu\n", + i, m_DeviceMemStats.memoryType[i].allocationCount, m_DeviceMemStats.memoryType[i].allocationTotalSize); + } +} + +void Statistics::PrintMemStats() const +{ + printf("Memory statistics:\n"); + + printf(" Total:\n"); + PrintMemStatInfo(m_PeakMemStats.total); + + for(uint32_t i = 0; i < m_MemHeapCount; ++i) + { + const MemStatInfo& info = m_PeakMemStats.memoryHeap[i]; + if(info.blockCount > 0 || info.totalBytes > 0) + { + printf(" Heap %u:\n", i); + PrintMemStatInfo(info); + } + } + + for(uint32_t i = 0; i < m_MemTypeCount; ++i) + { + const MemStatInfo& info = m_PeakMemStats.memoryType[i]; + if(info.blockCount > 0 || info.totalBytes > 0) + { + printf(" Type %u:\n", i); + PrintMemStatInfo(info); + } + } +} + +void Statistics::PrintDetailedStats() const +{ + m_VmaPoolCreateInfo.Print(); + m_VmaAllocationCreateInfo.Print(); + m_VmaAllocateMemoryPages.Print(); + m_VkBufferCreateInfo.Print(); + m_VkImageCreateInfo.Print(); + m_VmaDefragmentationInfo2.Print(); +} + +void Statistics::RegisterFunctionCall(VMA_FUNCTION func) +{ + ++m_FunctionCallCount[(size_t)func]; +} + +void Statistics::RegisterCreateImage(const VkImageCreateInfo& info) +{ + if(info.tiling == VK_IMAGE_TILING_LINEAR) + ++m_LinearImageCreationCount; + else + { + const uint32_t imgClass = ImageUsageToClass(info.usage); + ++m_ImageCreationCount[imgClass]; + } + + m_VkImageCreateInfo.PostValue(info); +} + +void Statistics::RegisterCreateBuffer(const VkBufferCreateInfo& info) +{ + const uint32_t bufClass = BufferUsageToClass(info.usage); + ++m_BufferCreationCount[bufClass]; + + m_VkBufferCreateInfo.PostValue(info); +} + +void Statistics::RegisterCreatePool(const VmaPoolCreateInfo& info) +{ + m_VmaPoolCreateInfo.PostValue(info); +} + +void Statistics::RegisterCreateAllocation(const VmaAllocationCreateInfo& info, size_t allocCount) +{ + m_VmaAllocationCreateInfo.PostValue(info, allocCount); +} + +void Statistics::RegisterDefragmentation(const VmaDefragmentationInfo2& info) +{ + m_VmaDefragmentationInfo2.PostValue(info); +} + +void Statistics::UpdateMemStats(const VmaStats& currStats) +{ + UpdateMemStatInfo(m_PeakMemStats.total, currStats.total); + + for(uint32_t i = 0; i < m_MemHeapCount; ++i) + { + UpdateMemStatInfo(m_PeakMemStats.memoryHeap[i], currStats.memoryHeap[i]); + } + + for(uint32_t i = 0; i < m_MemTypeCount; ++i) + { + UpdateMemStatInfo(m_PeakMemStats.memoryType[i], currStats.memoryType[i]); + } +} + +void Statistics::RegisterDeviceMemoryAllocation(uint32_t memoryType, VkDeviceSize size) +{ + ++m_DeviceMemStats.total.allocationCount; + m_DeviceMemStats.total.allocationTotalSize += size; + + ++m_DeviceMemStats.memoryType[memoryType].allocationCount; + m_DeviceMemStats.memoryType[memoryType].allocationTotalSize += size; +} + +void Statistics::UpdateMemStatInfo(MemStatInfo& inoutPeakInfo, const VmaStatInfo& currInfo) +{ +#define SET_PEAK(inoutDst, src) \ + if((src) > (inoutDst)) \ + { \ + (inoutDst) = (src); \ + } + + SET_PEAK(inoutPeakInfo.blockCount, currInfo.blockCount); + SET_PEAK(inoutPeakInfo.allocationCount, currInfo.allocationCount); + SET_PEAK(inoutPeakInfo.unusedRangeCount, currInfo.unusedRangeCount); + SET_PEAK(inoutPeakInfo.usedBytes, currInfo.usedBytes); + SET_PEAK(inoutPeakInfo.unusedBytes, currInfo.unusedBytes); + SET_PEAK(inoutPeakInfo.totalBytes, currInfo.usedBytes + currInfo.unusedBytes); + +#undef SET_PEAK +} + +void Statistics::PrintMemStatInfo(const MemStatInfo& info) +{ + printf(" Peak blocks %u, allocations %u, unused ranges %u\n", + info.blockCount, + info.allocationCount, + info.unusedRangeCount); + printf(" Peak total bytes %llu, used bytes %llu, unused bytes %llu\n", + info.totalBytes, + info.usedBytes, + info.unusedBytes); +} + +//////////////////////////////////////////////////////////////////////////////// +// class ConfigurationParser + +class ConfigurationParser +{ +public: + ConfigurationParser(); + + bool Parse(LineSplit& lineSplit); + + void Compare( + const VkPhysicalDeviceProperties& currDevProps, + const VkPhysicalDeviceMemoryProperties& currMemProps, + uint32_t vulkanApiVersion, + bool currMemoryBudgetEnabled); + +private: + enum class OPTION + { + VulkanApiVersion, + PhysicalDevice_apiVersion, + PhysicalDevice_driverVersion, + PhysicalDevice_vendorID, + PhysicalDevice_deviceID, + PhysicalDevice_deviceType, + PhysicalDevice_deviceName, + PhysicalDeviceLimits_maxMemoryAllocationCount, + PhysicalDeviceLimits_bufferImageGranularity, + PhysicalDeviceLimits_nonCoherentAtomSize, + Extension_VK_KHR_dedicated_allocation, + Extension_VK_KHR_bind_memory2, + Extension_VK_EXT_memory_budget, + Extension_VK_AMD_device_coherent_memory, + Macro_VMA_DEBUG_ALWAYS_DEDICATED_MEMORY, + Macro_VMA_MIN_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_MIN_ALIGNMENT") || StrRangeEq(subOptionName, "VMA_DEBUG_ALIGNMENT")) + SetOption(currLineNumber, OPTION::Macro_VMA_MIN_ALIGNMENT, csvSplit.GetRange(2)); + else if(StrRangeEq(subOptionName, "VMA_DEBUG_MARGIN")) + SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_MARGIN, csvSplit.GetRange(2)); + else if(StrRangeEq(subOptionName, "VMA_DEBUG_INITIALIZE_ALLOCATIONS")) + SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_INITIALIZE_ALLOCATIONS, csvSplit.GetRange(2)); + else if(StrRangeEq(subOptionName, "VMA_DEBUG_DETECT_CORRUPTION")) + SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_DETECT_CORRUPTION, csvSplit.GetRange(2)); + else if(StrRangeEq(subOptionName, "VMA_DEBUG_GLOBAL_MUTEX")) + SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_GLOBAL_MUTEX, csvSplit.GetRange(2)); + else if(StrRangeEq(subOptionName, "VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY")) + SetOption(currLineNumber, OPTION::Macro_VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY, csvSplit.GetRange(2)); + else if(StrRangeEq(subOptionName, "VMA_SMALL_HEAP_MAX_SIZE")) + SetOption(currLineNumber, OPTION::Macro_VMA_SMALL_HEAP_MAX_SIZE, csvSplit.GetRange(2)); + else if(StrRangeEq(subOptionName, "VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE")) + SetOption(currLineNumber, OPTION::Macro_VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE, csvSplit.GetRange(2)); + else + printf("Line %zu: Unrecognized configuration option.\n", currLineNumber); + } + else + printf("Line %zu: Too few columns.\n", currLineNumber); + } + else if(StrRangeEq(optionName, "PhysicalDeviceMemory")) + { + uint32_t value = 0; + if(csvSplit.GetCount() == 3 && StrRangeEq(csvSplit.GetRange(1), "HeapCount") && + StrRangeToUint(csvSplit.GetRange(2), value)) + { + m_MemProps.memoryHeapCount = value; + } + else if(csvSplit.GetCount() == 3 && StrRangeEq(csvSplit.GetRange(1), "TypeCount") && + StrRangeToUint(csvSplit.GetRange(2), value)) + { + m_MemProps.memoryTypeCount = value; + } + else if(csvSplit.GetCount() == 5 && StrRangeEq(csvSplit.GetRange(1), "Heap") && + StrRangeToUint(csvSplit.GetRange(2), value) && + value < m_MemProps.memoryHeapCount) + { + if(StrRangeEq(csvSplit.GetRange(3), "size") && + StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryHeaps[value].size)) + { + // Parsed. + } + else if(StrRangeEq(csvSplit.GetRange(3), "flags") && + StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryHeaps[value].flags)) + { + // Parsed. + } + else + printf("Line %zu: Invalid configuration option.\n", currLineNumber); + } + else if(csvSplit.GetCount() == 5 && StrRangeEq(csvSplit.GetRange(1), "Type") && + StrRangeToUint(csvSplit.GetRange(2), value) && + value < m_MemProps.memoryTypeCount) + { + if(StrRangeEq(csvSplit.GetRange(3), "heapIndex") && + StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryTypes[value].heapIndex)) + { + // Parsed. + } + else if(StrRangeEq(csvSplit.GetRange(3), "propertyFlags") && + StrRangeToUint(csvSplit.GetRange(4), m_MemProps.memoryTypes[value].propertyFlags)) + { + // Parsed. + } + else + printf("Line %zu: Invalid configuration option.\n", currLineNumber); + } + else + printf("Line %zu: Invalid configuration option.\n", currLineNumber); + } + else + printf("Line %zu: Unrecognized configuration option.\n", currLineNumber); + } + + return true; +} + +void ConfigurationParser::Compare( + const VkPhysicalDeviceProperties& currDevProps, + const VkPhysicalDeviceMemoryProperties& currMemProps, + uint32_t vulkanApiVersion, + bool currMemoryBudgetEnabled) +{ + char vulkanApiVersionStr[32]; + sprintf_s(vulkanApiVersionStr, "%u,%u", VK_VERSION_MAJOR(vulkanApiVersion), VK_VERSION_MINOR(vulkanApiVersion)); + CompareOption(VERBOSITY::DEFAULT, "VulkanApiVersion", + OPTION::VulkanApiVersion, vulkanApiVersionStr); + + CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice apiVersion", + OPTION::PhysicalDevice_apiVersion, currDevProps.apiVersion); + CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice driverVersion", + OPTION::PhysicalDevice_driverVersion, currDevProps.driverVersion); + CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice vendorID", + OPTION::PhysicalDevice_vendorID, currDevProps.vendorID); + CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice deviceID", + OPTION::PhysicalDevice_deviceID, currDevProps.deviceID); + CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice deviceType", + OPTION::PhysicalDevice_deviceType, (uint32_t)currDevProps.deviceType); + CompareOption(VERBOSITY::MAXIMUM, "PhysicalDevice deviceName", + OPTION::PhysicalDevice_deviceName, currDevProps.deviceName); + + CompareOption(VERBOSITY::DEFAULT, "PhysicalDeviceLimits maxMemoryAllocationCount", + OPTION::PhysicalDeviceLimits_maxMemoryAllocationCount, currDevProps.limits.maxMemoryAllocationCount); + CompareOption(VERBOSITY::DEFAULT, "PhysicalDeviceLimits bufferImageGranularity", + OPTION::PhysicalDeviceLimits_bufferImageGranularity, currDevProps.limits.bufferImageGranularity); + CompareOption(VERBOSITY::DEFAULT, "PhysicalDeviceLimits nonCoherentAtomSize", + OPTION::PhysicalDeviceLimits_nonCoherentAtomSize, currDevProps.limits.nonCoherentAtomSize); + + CompareMemProps(currMemProps); +} + +void ConfigurationParser::SetOption( + size_t lineNumber, + OPTION option, + const StrRange& str) +{ + if(m_OptionSet[(size_t)option]) + { + printf("Line %zu: Option already specified.\n" ,lineNumber); + } + + m_OptionSet[(size_t)option] = true; + + std::string val; + str.to_str(val); + m_OptionValue[(size_t)option] = std::move(val); +} + +void ConfigurationParser::EnsureWarningHeader() +{ + if(!m_WarningHeaderPrinted) + { + printf("WARNING: Following configuration parameters don't match:\n"); + m_WarningHeaderPrinted = true; + } +} + +void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name, + OPTION option, uint32_t currValue) +{ + if(m_OptionSet[(size_t)option] && + g_Verbosity >= minVerbosity) + { + uint32_t origValue; + if(StrRangeToUint(StrRange(m_OptionValue[(size_t)option]), origValue)) + { + if(origValue != currValue) + { + EnsureWarningHeader(); + printf(" %s: original %u, current %u\n", name, origValue, currValue); + } + } + } +} + +void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name, + OPTION option, uint64_t currValue) +{ + if(m_OptionSet[(size_t)option] && + g_Verbosity >= minVerbosity) + { + uint64_t origValue; + if(StrRangeToUint(StrRange(m_OptionValue[(size_t)option]), origValue)) + { + if(origValue != currValue) + { + EnsureWarningHeader(); + printf(" %s: original %llu, current %llu\n", name, origValue, currValue); + } + } + } +} + +void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name, + OPTION option, bool currValue) +{ + if(m_OptionSet[(size_t)option] && + g_Verbosity >= minVerbosity) + { + bool origValue; + if(StrRangeToBool(StrRange(m_OptionValue[(size_t)option]), origValue)) + { + if(origValue != currValue) + { + EnsureWarningHeader(); + printf(" %s: original %u, current %u\n", name, + origValue ? 1 : 0, + currValue ? 1 : 0); + } + } + } +} + +void ConfigurationParser::CompareOption(VERBOSITY minVerbosity, const char* name, + OPTION option, const char* currValue) +{ + if(m_OptionSet[(size_t)option] && + g_Verbosity >= minVerbosity) + { + const std::string& origValue = m_OptionValue[(size_t)option]; + if(origValue != currValue) + { + EnsureWarningHeader(); + printf(" %s: original \"%s\", current \"%s\"\n", name, origValue.c_str(), currValue); + } + } +} + +void ConfigurationParser::CompareMemProps( + const VkPhysicalDeviceMemoryProperties& currMemProps) +{ + if(g_Verbosity < VERBOSITY::DEFAULT) + { + return; + } + + bool memoryMatch = + currMemProps.memoryHeapCount == m_MemProps.memoryHeapCount && + currMemProps.memoryTypeCount == m_MemProps.memoryTypeCount; + + for(uint32_t i = 0; memoryMatch && i < currMemProps.memoryHeapCount; ++i) + { + memoryMatch = + currMemProps.memoryHeaps[i].flags == m_MemProps.memoryHeaps[i].flags; + } + for(uint32_t i = 0; memoryMatch && i < currMemProps.memoryTypeCount; ++i) + { + memoryMatch = + currMemProps.memoryTypes[i].heapIndex == m_MemProps.memoryTypes[i].heapIndex && + currMemProps.memoryTypes[i].propertyFlags == m_MemProps.memoryTypes[i].propertyFlags; + } + + if(memoryMatch && g_Verbosity == VERBOSITY::MAXIMUM) + { + bool memorySizeMatch = true; + for(uint32_t i = 0; memorySizeMatch && i < currMemProps.memoryHeapCount; ++i) + { + memorySizeMatch = + currMemProps.memoryHeaps[i].size == m_MemProps.memoryHeaps[i].size; + } + + if(!memorySizeMatch) + { + printf("WARNING: Sizes of original memory heaps are different from current ones.\n"); + } + } + else + { + printf("WARNING: Layout of original memory heaps and types is different from current one.\n"); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// class Player + +static const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; + +static VkBool32 VKAPI_PTR MyDebugReportCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) +{ + assert(pCallbackData && pCallbackData->pMessageIdName && pCallbackData->pMessage); + printf("%s \xBA %s\n", pCallbackData->pMessageIdName, pCallbackData->pMessage); + return VK_FALSE; +} + +static bool IsLayerSupported(const VkLayerProperties* pProps, size_t propCount, const char* pLayerName) +{ + const VkLayerProperties* propsEnd = pProps + propCount; + return std::find_if( + pProps, + propsEnd, + [pLayerName](const VkLayerProperties& prop) -> bool { + return strcmp(pLayerName, prop.layerName) == 0; + }) != propsEnd; +} + +static const size_t FIRST_PARAM_INDEX = 4; + +static void InitVulkanFeatures( + VkPhysicalDeviceFeatures& outFeatures, + const VkPhysicalDeviceFeatures& supportedFeatures) +{ + ZeroMemory(&outFeatures, sizeof(outFeatures)); + + // Enable something what may interact with memory/buffer/image support. + + outFeatures.fullDrawIndexUint32 = supportedFeatures.fullDrawIndexUint32; + outFeatures.imageCubeArray = supportedFeatures.imageCubeArray; + outFeatures.geometryShader = supportedFeatures.geometryShader; + outFeatures.tessellationShader = supportedFeatures.tessellationShader; + outFeatures.multiDrawIndirect = supportedFeatures.multiDrawIndirect; + outFeatures.textureCompressionETC2 = supportedFeatures.textureCompressionETC2; + outFeatures.textureCompressionASTC_LDR = supportedFeatures.textureCompressionASTC_LDR; + outFeatures.textureCompressionBC = supportedFeatures.textureCompressionBC; +} + +class Player +{ +public: + Player(); + int Init(); + ~Player(); + + void ApplyConfig(ConfigurationParser& configParser); + void ExecuteLine(size_t lineNumber, const StrRange& line); + void DumpStats(const char* fileNameFormat, size_t lineNumber, bool detailed); + void Defragment(); + + void PrintStats(); + +private: + static const size_t MAX_WARNINGS_TO_SHOW = 64; + + size_t m_WarningCount = 0; + bool m_AllocateForBufferImageWarningIssued = false; + + VkInstance m_VulkanInstance = VK_NULL_HANDLE; + VkPhysicalDevice m_PhysicalDevice = VK_NULL_HANDLE; + uint32_t m_GraphicsQueueFamilyIndex = UINT32_MAX; + uint32_t m_TransferQueueFamilyIndex = UINT32_MAX; + VkDevice m_Device = VK_NULL_HANDLE; + VkQueue m_GraphicsQueue = VK_NULL_HANDLE; + VkQueue m_TransferQueue = VK_NULL_HANDLE; + VmaAllocator m_Allocator = VK_NULL_HANDLE; + VkCommandPool m_CommandPool = VK_NULL_HANDLE; + VkCommandBuffer m_CommandBuffer = VK_NULL_HANDLE; + bool m_MemoryBudgetEnabled = false; + const VkPhysicalDeviceProperties* m_DevProps = nullptr; + const VkPhysicalDeviceMemoryProperties* m_MemProps = nullptr; + + PFN_vkCreateDebugUtilsMessengerEXT m_vkCreateDebugUtilsMessengerEXT = nullptr; + PFN_vkDestroyDebugUtilsMessengerEXT m_vkDestroyDebugUtilsMessengerEXT = nullptr; + VkDebugUtilsMessengerEXT m_DebugUtilsMessenger = VK_NULL_HANDLE; + + uint32_t m_VmaFrameIndex = 0; + + // Any of these handles null can mean it was created in original but couldn't be created now. + struct Pool + { + VmaPool pool; + }; + struct Allocation + { + uint32_t allocationFlags = 0; + VmaAllocation allocation = VK_NULL_HANDLE; + VkBuffer buffer = VK_NULL_HANDLE; + VkImage image = VK_NULL_HANDLE; + }; + std::unordered_map<uint64_t, Pool> m_Pools; + std::unordered_map<uint64_t, Allocation> m_Allocations; + std::unordered_map<uint64_t, VmaDefragmentationContext> m_DefragmentationContexts; + + struct Thread + { + uint32_t callCount; + }; + std::unordered_map<uint32_t, Thread> m_Threads; + + // Copy of column [1] from previously parsed line. + std::string m_LastLineTimeStr; + Statistics m_Stats; + + std::vector<char> m_UserDataTmpStr; + + void Destroy(const Allocation& alloc); + + // Finds VmaPool bu original pointer. + // If origPool = null, returns true and outPool = null. + // If failed, prints warning, returns false and outPool = null. + bool FindPool(size_t lineNumber, uint64_t origPool, VmaPool& outPool); + // If allocation with that origPtr already exists, prints warning and replaces it. + void AddAllocation(size_t lineNumber, uint64_t origPtr, VkResult res, const char* functionName, Allocation&& allocDesc); + + // Increments warning counter. Returns true if warning message should be printed. + bool IssueWarning(); + + int InitVulkan(); + void FinalizeVulkan(); + void RegisterDebugCallbacks(); + void UnregisterDebugCallbacks(); + + // If parmeter count doesn't match, issues warning and returns false. + bool ValidateFunctionParameterCount(size_t lineNumber, const CsvSplit& csvSplit, size_t expectedParamCount, bool lastUnbound); + + // If failed, prints warning, returns false, and sets allocCreateInfo.pUserData to null. + bool PrepareUserData(size_t lineNumber, uint32_t allocCreateFlags, const StrRange& userDataColumn, const StrRange& wholeLine, void*& outUserData); + + void UpdateMemStats(); + + void ExecuteCreatePool(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteDestroyPool(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteSetAllocationUserData(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteCreateBuffer(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteDestroyBuffer(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::DestroyBuffer); DestroyAllocation(lineNumber, csvSplit, "vmaDestroyBuffer"); } + void ExecuteCreateImage(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteDestroyImage(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::DestroyImage); DestroyAllocation(lineNumber, csvSplit, "vmaDestroyImage"); } + void ExecuteFreeMemory(size_t lineNumber, const CsvSplit& csvSplit) { m_Stats.RegisterFunctionCall(VMA_FUNCTION::FreeMemory); DestroyAllocation(lineNumber, csvSplit, "vmaFreeMemory"); } + void ExecuteFreeMemoryPages(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteCreateLostAllocation(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteAllocateMemory(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteAllocateMemoryPages(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteAllocateMemoryForBufferOrImage(size_t lineNumber, const CsvSplit& csvSplit, OBJECT_TYPE objType); + void ExecuteMapMemory(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteUnmapMemory(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteFlushAllocation(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteInvalidateAllocation(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteTouchAllocation(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteGetAllocationInfo(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteMakePoolAllocationsLost(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteResizeAllocation(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteDefragmentationBegin(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteDefragmentationEnd(size_t lineNumber, const CsvSplit& csvSplit); + void ExecuteSetPoolName(size_t lineNumber, const CsvSplit& csvSplit); + + void DestroyAllocation(size_t lineNumber, const CsvSplit& csvSplit, const char* functionName); + + void PrintStats(const VmaStats& stats, const char* suffix); + void PrintStatInfo(const VmaStatInfo& info); +}; + +Player::Player() +{ +} + +int Player::Init() +{ + int result = InitVulkan(); + + if(result == 0) + { + m_Stats.Init(m_MemProps->memoryHeapCount, m_MemProps->memoryTypeCount); + UpdateMemStats(); + } + + return result; +} + +Player::~Player() +{ + FinalizeVulkan(); + + if(g_Verbosity < VERBOSITY::MAXIMUM && m_WarningCount > MAX_WARNINGS_TO_SHOW) + printf("WARNING: %zu more warnings not shown.\n", m_WarningCount - MAX_WARNINGS_TO_SHOW); +} + +void Player::ApplyConfig(ConfigurationParser& configParser) +{ + configParser.Compare(*m_DevProps, *m_MemProps, + VULKAN_API_VERSION, + m_MemoryBudgetEnabled); +} + +void Player::ExecuteLine(size_t lineNumber, const StrRange& line) +{ + CsvSplit csvSplit; + csvSplit.Set(line); + + if(csvSplit.GetCount() >= FIRST_PARAM_INDEX) + { + // Check thread ID. + uint32_t threadId; + if(StrRangeToUint(csvSplit.GetRange(0), threadId)) + { + const auto it = m_Threads.find(threadId); + if(it != m_Threads.end()) + { + ++it->second.callCount; + } + else + { + Thread threadInfo{}; + threadInfo.callCount = 1; + m_Threads[threadId] = threadInfo; + } + } + else + { + if(IssueWarning()) + { + printf("Line %zu: Incorrect thread ID.\n", lineNumber); + } + } + + // Save time. + csvSplit.GetRange(1).to_str(m_LastLineTimeStr); + + // Update VMA current frame index. + StrRange frameIndexStr = csvSplit.GetRange(2); + uint32_t frameIndex; + if(StrRangeToUint(frameIndexStr, frameIndex)) + { + if(frameIndex != m_VmaFrameIndex) + { + vmaSetCurrentFrameIndex(m_Allocator, frameIndex); + m_VmaFrameIndex = frameIndex; + } + } + else + { + if(IssueWarning()) + { + printf("Line %zu: Incorrect frame index.\n", lineNumber); + } + } + + StrRange functionName = csvSplit.GetRange(3); + + if(StrRangeEq(functionName, "vmaCreateAllocator")) + { + if(ValidateFunctionParameterCount(lineNumber, csvSplit, 0, false)) + { + // Nothing. + } + } + else if(StrRangeEq(functionName, "vmaDestroyAllocator")) + { + if(ValidateFunctionParameterCount(lineNumber, csvSplit, 0, false)) + { + // Nothing. + } + } + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreatePool])) + ExecuteCreatePool(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DestroyPool])) + ExecuteDestroyPool(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::SetAllocationUserData])) + ExecuteSetAllocationUserData(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreateBuffer])) + ExecuteCreateBuffer(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DestroyBuffer])) + ExecuteDestroyBuffer(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreateImage])) + ExecuteCreateImage(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DestroyImage])) + ExecuteDestroyImage(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::FreeMemory])) + ExecuteFreeMemory(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::FreeMemoryPages])) + ExecuteFreeMemoryPages(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::CreateLostAllocation])) + ExecuteCreateLostAllocation(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemory])) + ExecuteAllocateMemory(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryPages])) + ExecuteAllocateMemoryPages(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryForBuffer])) + ExecuteAllocateMemoryForBufferOrImage(lineNumber, csvSplit, OBJECT_TYPE::BUFFER); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::AllocateMemoryForImage])) + ExecuteAllocateMemoryForBufferOrImage(lineNumber, csvSplit, OBJECT_TYPE::IMAGE); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::MapMemory])) + ExecuteMapMemory(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::UnmapMemory])) + ExecuteUnmapMemory(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::FlushAllocation])) + ExecuteFlushAllocation(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::InvalidateAllocation])) + ExecuteInvalidateAllocation(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::TouchAllocation])) + ExecuteTouchAllocation(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::GetAllocationInfo])) + ExecuteGetAllocationInfo(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::MakePoolAllocationsLost])) + ExecuteMakePoolAllocationsLost(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::ResizeAllocation])) + ExecuteResizeAllocation(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DefragmentationBegin])) + ExecuteDefragmentationBegin(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::DefragmentationEnd])) + ExecuteDefragmentationEnd(lineNumber, csvSplit); + else if(StrRangeEq(functionName, VMA_FUNCTION_NAMES[(uint32_t)VMA_FUNCTION::SetPoolName])) + ExecuteSetPoolName(lineNumber, csvSplit); + else + { + if(IssueWarning()) + { + printf("Line %zu: Unknown function.\n", lineNumber); + } + } + } + else + { + if(IssueWarning()) + { + printf("Line %zu: Too few columns.\n", lineNumber); + } + } +} + +void Player::DumpStats(const char* fileNameFormat, size_t lineNumber, bool detailed) +{ + char* pStatsString = nullptr; + vmaBuildStatsString(m_Allocator, &pStatsString, detailed ? VK_TRUE : VK_FALSE); + + char fileName[MAX_PATH]; + sprintf_s(fileName, fileNameFormat, lineNumber); + + FILE* file = nullptr; + errno_t err = fopen_s(&file, fileName, "wb"); + if(err == 0) + { + fwrite(pStatsString, 1, strlen(pStatsString), file); + fclose(file); + } + else + { + printf("ERROR: Failed to write file: %s\n", fileName); + } + + vmaFreeStatsString(m_Allocator, pStatsString); +} + +void Player::Destroy(const Allocation& alloc) +{ + if(alloc.buffer) + { + assert(alloc.image == VK_NULL_HANDLE); + vmaDestroyBuffer(m_Allocator, alloc.buffer, alloc.allocation); + } + else if(alloc.image) + { + vmaDestroyImage(m_Allocator, alloc.image, alloc.allocation); + } + else + vmaFreeMemory(m_Allocator, alloc.allocation); +} + +bool Player::FindPool(size_t lineNumber, uint64_t origPool, VmaPool& outPool) +{ + outPool = VK_NULL_HANDLE; + + if(origPool != 0) + { + const auto poolIt = m_Pools.find(origPool); + if(poolIt != m_Pools.end()) + { + outPool = poolIt->second.pool; + return true; + } + else + { + if(IssueWarning()) + { + printf("Line %zu: Pool %llX not found.\n", lineNumber, origPool); + } + } + } + + return true; +} + +void Player::AddAllocation(size_t lineNumber, uint64_t origPtr, VkResult res, const char* functionName, Allocation&& allocDesc) +{ + if(origPtr) + { + if(res == VK_SUCCESS) + { + // Originally succeeded, currently succeeded. + // Just save pointer (done below). + } + else + { + // Originally succeeded, currently failed. + // Print warning. Save null pointer. + if(IssueWarning()) + { + printf("Line %zu: %s failed (%d), while originally succeeded.\n", lineNumber, functionName, res); + } + } + + const auto existingIt = m_Allocations.find(origPtr); + if(existingIt != m_Allocations.end()) + { + if(IssueWarning()) + { + printf("Line %zu: Allocation %llX already exists.\n", lineNumber, origPtr); + } + } + m_Allocations[origPtr] = std::move(allocDesc); + } + else + { + if(res == VK_SUCCESS) + { + // Originally failed, currently succeeded. + // Print warning, destroy the object. + if(IssueWarning()) + { + printf("Line %zu: %s succeeded, originally failed.\n", lineNumber, functionName); + } + + Destroy(allocDesc); + } + else + { + // Originally failed, currently failed. + // Print warning. + if(IssueWarning()) + { + printf("Line %zu: %s failed (%d), originally also failed.\n", lineNumber, functionName, res); + } + } + } +} + +bool Player::IssueWarning() +{ + if(g_Verbosity < VERBOSITY::MAXIMUM) + { + return m_WarningCount++ < MAX_WARNINGS_TO_SHOW; + } + else + { + ++m_WarningCount; + return true; + } +} + +int Player::InitVulkan() +{ + if(g_Verbosity == VERBOSITY::MAXIMUM) + { + printf("Initializing Vulkan...\n"); + } + + uint32_t instanceLayerPropCount = 0; + VkResult res = vkEnumerateInstanceLayerProperties(&instanceLayerPropCount, nullptr); + assert(res == VK_SUCCESS); + + std::vector<VkLayerProperties> instanceLayerProps(instanceLayerPropCount); + if(instanceLayerPropCount > 0) + { + res = vkEnumerateInstanceLayerProperties(&instanceLayerPropCount, instanceLayerProps.data()); + assert(res == VK_SUCCESS); + } + + const bool validationLayersAvailable = + IsLayerSupported(instanceLayerProps.data(), instanceLayerProps.size(), VALIDATION_LAYER_NAME); + + bool validationLayersEnabled = false; + switch(g_VK_LAYER_KHRONOS_validation) + { + case VULKAN_EXTENSION_REQUEST::DISABLED: + break; + case VULKAN_EXTENSION_REQUEST::DEFAULT: + validationLayersEnabled = validationLayersAvailable; + break; + case VULKAN_EXTENSION_REQUEST::ENABLED: + validationLayersEnabled = validationLayersAvailable; + if(!validationLayersAvailable) + { + printf("WARNING: %s layer cannot be enabled.\n", VALIDATION_LAYER_NAME); + } + break; + default: assert(0); + } + + uint32_t availableInstanceExtensionCount = 0; + res = vkEnumerateInstanceExtensionProperties(nullptr, &availableInstanceExtensionCount, nullptr); + assert(res == VK_SUCCESS); + std::vector<VkExtensionProperties> availableInstanceExtensions(availableInstanceExtensionCount); + if(availableInstanceExtensionCount > 0) + { + res = vkEnumerateInstanceExtensionProperties(nullptr, &availableInstanceExtensionCount, availableInstanceExtensions.data()); + assert(res == VK_SUCCESS); + } + + std::vector<const char*> enabledInstanceExtensions; + //enabledInstanceExtensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + //enabledInstanceExtensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); + + std::vector<const char*> instanceLayers; + if(validationLayersEnabled) + { + instanceLayers.push_back(VALIDATION_LAYER_NAME); + } + + bool VK_KHR_get_physical_device_properties2_enabled = false; + bool VK_EXT_debug_utils_enabled = false; + for(const auto& extensionProperties : availableInstanceExtensions) + { + if(strcmp(extensionProperties.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0) + { + enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + VK_KHR_get_physical_device_properties2_enabled = true; + } + else if(strcmp(extensionProperties.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) + { + if(validationLayersEnabled) + { + enabledInstanceExtensions.push_back("VK_EXT_debug_utils"); + VK_EXT_debug_utils_enabled = true; + } + } + } + + VkApplicationInfo appInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO }; + appInfo.pApplicationName = "VmaReplay"; + appInfo.applicationVersion = VK_MAKE_VERSION(2, 3, 0); + appInfo.pEngineName = "Vulkan Memory Allocator"; + appInfo.engineVersion = VK_MAKE_VERSION(2, 3, 0); + appInfo.apiVersion = VULKAN_API_VERSION; + + VkInstanceCreateInfo instInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; + instInfo.pApplicationInfo = &appInfo; + instInfo.enabledExtensionCount = (uint32_t)enabledInstanceExtensions.size(); + instInfo.ppEnabledExtensionNames = enabledInstanceExtensions.data(); + instInfo.enabledLayerCount = (uint32_t)instanceLayers.size(); + instInfo.ppEnabledLayerNames = instanceLayers.data(); + + res = vkCreateInstance(&instInfo, NULL, &m_VulkanInstance); + if(res != VK_SUCCESS) + { + printf("ERROR: vkCreateInstance failed (%d)\n", res); + return RESULT_ERROR_VULKAN; + } + + if(VK_EXT_debug_utils_enabled) + { + RegisterDebugCallbacks(); + } + + // Find physical device + + uint32_t physicalDeviceCount = 0; + res = vkEnumeratePhysicalDevices(m_VulkanInstance, &physicalDeviceCount, nullptr); + assert(res == VK_SUCCESS); + if(physicalDeviceCount == 0) + { + printf("ERROR: No Vulkan physical devices found.\n"); + return RESULT_ERROR_VULKAN; + } + + std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount); + res = vkEnumeratePhysicalDevices(m_VulkanInstance, &physicalDeviceCount, physicalDevices.data()); + assert(res == VK_SUCCESS); + + if(g_PhysicalDeviceIndex >= physicalDeviceCount) + { + printf("ERROR: Incorrect Vulkan physical device index %u. System has %u physical devices.\n", + g_PhysicalDeviceIndex, + physicalDeviceCount); + return RESULT_ERROR_VULKAN; + } + + m_PhysicalDevice = physicalDevices[0]; + + // Find queue family index + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(m_PhysicalDevice, &queueFamilyCount, nullptr); + if(queueFamilyCount) + { + std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(m_PhysicalDevice, &queueFamilyCount, queueFamilies.data()); + for(uint32_t i = 0; i < queueFamilyCount; ++i) + { + if(queueFamilies[i].queueCount > 0) + { + if(m_GraphicsQueueFamilyIndex == UINT32_MAX && + (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) + { + m_GraphicsQueueFamilyIndex = i; + } + if(m_TransferQueueFamilyIndex == UINT32_MAX && + (queueFamilies[i].queueFlags & VK_QUEUE_TRANSFER_BIT) != 0) + { + m_TransferQueueFamilyIndex = i; + } + } + } + } + if(m_GraphicsQueueFamilyIndex == UINT_MAX) + { + printf("ERROR: Couldn't find graphics queue.\n"); + return RESULT_ERROR_VULKAN; + } + if(m_TransferQueueFamilyIndex == UINT_MAX) + { + printf("ERROR: Couldn't find transfer queue.\n"); + return RESULT_ERROR_VULKAN; + } + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(m_PhysicalDevice, &supportedFeatures); + + // Create logical device + + const float queuePriority = 1.f; + + VkDeviceQueueCreateInfo deviceQueueCreateInfo[2] = {}; + deviceQueueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + deviceQueueCreateInfo[0].queueFamilyIndex = m_GraphicsQueueFamilyIndex; + deviceQueueCreateInfo[0].queueCount = 1; + deviceQueueCreateInfo[0].pQueuePriorities = &queuePriority; + + if(m_TransferQueueFamilyIndex != m_GraphicsQueueFamilyIndex) + { + deviceQueueCreateInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + deviceQueueCreateInfo[1].queueFamilyIndex = m_TransferQueueFamilyIndex; + deviceQueueCreateInfo[1].queueCount = 1; + deviceQueueCreateInfo[1].pQueuePriorities = &queuePriority; + } + + // Enable something what may interact with memory/buffer/image support. + VkPhysicalDeviceFeatures enabledFeatures; + InitVulkanFeatures(enabledFeatures, supportedFeatures); + + bool VK_KHR_get_memory_requirements2_available = false; + + // Determine list of device extensions to enable. + std::vector<const char*> enabledDeviceExtensions; + //enabledDeviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + bool memoryBudgetAvailable = false; + { + uint32_t propertyCount = 0; + res = vkEnumerateDeviceExtensionProperties(m_PhysicalDevice, nullptr, &propertyCount, nullptr); + assert(res == VK_SUCCESS); + + if(propertyCount) + { + std::vector<VkExtensionProperties> properties{propertyCount}; + res = vkEnumerateDeviceExtensionProperties(m_PhysicalDevice, nullptr, &propertyCount, properties.data()); + assert(res == VK_SUCCESS); + + for(uint32_t i = 0; i < propertyCount; ++i) + { + if(strcmp(properties[i].extensionName, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME) == 0) + { + VK_KHR_get_memory_requirements2_available = true; + } + else if(strcmp(properties[i].extensionName, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) == 0) + { + if(VK_KHR_get_physical_device_properties2_enabled) + { + memoryBudgetAvailable = true; + } + } + } + } + } + + switch(g_VK_EXT_memory_budget_request) + { + case VULKAN_EXTENSION_REQUEST::DISABLED: + break; + case VULKAN_EXTENSION_REQUEST::DEFAULT: + m_MemoryBudgetEnabled = memoryBudgetAvailable; + break; + case VULKAN_EXTENSION_REQUEST::ENABLED: + m_MemoryBudgetEnabled = memoryBudgetAvailable; + if(!memoryBudgetAvailable) + { + printf("WARNING: VK_EXT_memory_budget extension cannot be enabled.\n"); + } + break; + default: assert(0); + } + + if(g_VK_AMD_device_coherent_memory_request == VULKAN_EXTENSION_REQUEST::ENABLED) + { + printf("WARNING: AMD_device_coherent_memory requested but not currently supported by the player.\n"); + } + + if(m_MemoryBudgetEnabled) + { + enabledDeviceExtensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); + } + + VkDeviceCreateInfo deviceCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; + deviceCreateInfo.enabledExtensionCount = (uint32_t)enabledDeviceExtensions.size(); + deviceCreateInfo.ppEnabledExtensionNames = !enabledDeviceExtensions.empty() ? enabledDeviceExtensions.data() : nullptr; + deviceCreateInfo.queueCreateInfoCount = m_TransferQueueFamilyIndex != m_GraphicsQueueFamilyIndex ? 2 : 1; + deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfo; + deviceCreateInfo.pEnabledFeatures = &enabledFeatures; + + res = vkCreateDevice(m_PhysicalDevice, &deviceCreateInfo, nullptr, &m_Device); + if(res != VK_SUCCESS) + { + printf("ERROR: vkCreateDevice failed (%d)\n", res); + return RESULT_ERROR_VULKAN; + } + + // Fetch queues + vkGetDeviceQueue(m_Device, m_GraphicsQueueFamilyIndex, 0, &m_GraphicsQueue); + vkGetDeviceQueue(m_Device, m_TransferQueueFamilyIndex, 0, &m_TransferQueue); + + // Create memory allocator + + VmaDeviceMemoryCallbacks deviceMemoryCallbacks = {}; + deviceMemoryCallbacks.pfnAllocate = AllocateDeviceMemoryCallback; + deviceMemoryCallbacks.pfnFree = FreeDeviceMemoryCallback; + + VmaAllocatorCreateInfo allocatorInfo = {}; + allocatorInfo.instance = m_VulkanInstance; + allocatorInfo.physicalDevice = m_PhysicalDevice; + allocatorInfo.device = m_Device; + allocatorInfo.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; + allocatorInfo.pDeviceMemoryCallbacks = &deviceMemoryCallbacks; + allocatorInfo.vulkanApiVersion = VULKAN_API_VERSION; + + if(m_MemoryBudgetEnabled) + { + allocatorInfo.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; + } + + res = vmaCreateAllocator(&allocatorInfo, &m_Allocator); + if(res != VK_SUCCESS) + { + printf("ERROR: vmaCreateAllocator failed (%d)\n", res); + return RESULT_ERROR_VULKAN; + } + + vmaGetPhysicalDeviceProperties(m_Allocator, &m_DevProps); + vmaGetMemoryProperties(m_Allocator, &m_MemProps); + + // Create command pool + + VkCommandPoolCreateInfo cmdPoolCreateInfo = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; + cmdPoolCreateInfo.queueFamilyIndex = m_TransferQueueFamilyIndex; + cmdPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + + res = vkCreateCommandPool(m_Device, &cmdPoolCreateInfo, nullptr, &m_CommandPool); + if(res != VK_SUCCESS) + { + printf("ERROR: vkCreateCommandPool failed (%d)\n", res); + return RESULT_ERROR_VULKAN; + } + + // Create command buffer + + VkCommandBufferAllocateInfo cmdBufAllocInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; + cmdBufAllocInfo.commandBufferCount = 1; + cmdBufAllocInfo.commandPool = m_CommandPool; + cmdBufAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + res = vkAllocateCommandBuffers(m_Device, &cmdBufAllocInfo, &m_CommandBuffer); + if(res != VK_SUCCESS) + { + printf("ERROR: vkAllocateCommandBuffers failed (%d)\n", res); + return RESULT_ERROR_VULKAN; + } + + return 0; +} + +void Player::FinalizeVulkan() +{ + if(!m_DefragmentationContexts.empty()) + { + printf("WARNING: Defragmentation contexts not destroyed: %zu.\n", m_DefragmentationContexts.size()); + + if(CLEANUP_LEAKED_OBJECTS) + { + for(const auto& it : m_DefragmentationContexts) + { + vmaDefragmentationEnd(m_Allocator, it.second); + } + } + + m_DefragmentationContexts.clear(); + } + + if(!m_Allocations.empty()) + { + printf("WARNING: Allocations not destroyed: %zu.\n", m_Allocations.size()); + + if(CLEANUP_LEAKED_OBJECTS) + { + for(const auto it : m_Allocations) + { + Destroy(it.second); + } + } + + m_Allocations.clear(); + } + + if(!m_Pools.empty()) + { + printf("WARNING: Custom pools not destroyed: %zu.\n", m_Pools.size()); + + if(CLEANUP_LEAKED_OBJECTS) + { + for(const auto it : m_Pools) + { + vmaDestroyPool(m_Allocator, it.second.pool); + } + } + + m_Pools.clear(); + } + + vkDeviceWaitIdle(m_Device); + + if(m_CommandBuffer != VK_NULL_HANDLE) + { + vkFreeCommandBuffers(m_Device, m_CommandPool, 1, &m_CommandBuffer); + m_CommandBuffer = VK_NULL_HANDLE; + } + + if(m_CommandPool != VK_NULL_HANDLE) + { + vkDestroyCommandPool(m_Device, m_CommandPool, nullptr); + m_CommandPool = VK_NULL_HANDLE; + } + + if(m_Allocator != VK_NULL_HANDLE) + { + vmaDestroyAllocator(m_Allocator); + m_Allocator = nullptr; + } + + if(m_Device != VK_NULL_HANDLE) + { + vkDestroyDevice(m_Device, nullptr); + m_Device = nullptr; + } + + UnregisterDebugCallbacks(); + + if(m_VulkanInstance != VK_NULL_HANDLE) + { + vkDestroyInstance(m_VulkanInstance, NULL); + m_VulkanInstance = VK_NULL_HANDLE; + } +} + +void Player::RegisterDebugCallbacks() +{ + static const VkDebugUtilsMessageSeverityFlagsEXT DEBUG_UTILS_MESSENGER_MESSAGE_SEVERITY = + //VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + //VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + static const VkDebugUtilsMessageTypeFlagsEXT DEBUG_UTILS_MESSENGER_MESSAGE_TYPE = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + + m_vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + m_VulkanInstance, "vkCreateDebugUtilsMessengerEXT"); + m_vkDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + m_VulkanInstance, "vkDestroyDebugUtilsMessengerEXT"); + assert(m_vkCreateDebugUtilsMessengerEXT); + assert(m_vkDestroyDebugUtilsMessengerEXT); + + VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT }; + messengerCreateInfo.messageSeverity = DEBUG_UTILS_MESSENGER_MESSAGE_SEVERITY; + messengerCreateInfo.messageType = DEBUG_UTILS_MESSENGER_MESSAGE_TYPE; + messengerCreateInfo.pfnUserCallback = MyDebugReportCallback; + VkResult res = m_vkCreateDebugUtilsMessengerEXT(m_VulkanInstance, &messengerCreateInfo, nullptr, &m_DebugUtilsMessenger); + if(res != VK_SUCCESS) + { + printf("ERROR: vkCreateDebugUtilsMessengerEXT failed (%d)\n", res); + m_DebugUtilsMessenger = VK_NULL_HANDLE; + } +} + +void Player::UnregisterDebugCallbacks() +{ + if(m_DebugUtilsMessenger) + { + m_vkDestroyDebugUtilsMessengerEXT(m_VulkanInstance, m_DebugUtilsMessenger, nullptr); + } +} + +void Player::Defragment() +{ + VmaStats stats; + vmaCalculateStats(m_Allocator, &stats); + PrintStats(stats, "before defragmentation"); + + const size_t allocCount = m_Allocations.size(); + std::vector<VmaAllocation> allocations(allocCount); + size_t notNullAllocCount = 0; + for(const auto& it : m_Allocations) + { + if(it.second.allocation != VK_NULL_HANDLE) + { + allocations[notNullAllocCount] = it.second.allocation; + ++notNullAllocCount; + } + } + if(notNullAllocCount == 0) + { + printf(" Nothing to defragment.\n"); + return; + } + + allocations.resize(notNullAllocCount); + std::vector<VkBool32> allocationsChanged(notNullAllocCount); + + VmaDefragmentationStats defragStats = {}; + + VkCommandBufferBeginInfo cmdBufBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + cmdBufBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + VkResult res = vkBeginCommandBuffer(m_CommandBuffer, &cmdBufBeginInfo); + if(res != VK_SUCCESS) + { + printf("ERROR: vkBeginCommandBuffer failed (%d)\n", res); + return; + } + + const time_point timeBeg = std::chrono::high_resolution_clock::now(); + + VmaDefragmentationInfo2 defragInfo = {}; + defragInfo.allocationCount = (uint32_t)notNullAllocCount; + defragInfo.pAllocations = allocations.data(); + defragInfo.pAllocationsChanged = allocationsChanged.data(); + defragInfo.maxCpuAllocationsToMove = UINT32_MAX; + defragInfo.maxCpuBytesToMove = VK_WHOLE_SIZE; + defragInfo.maxGpuAllocationsToMove = UINT32_MAX; + defragInfo.maxGpuBytesToMove = VK_WHOLE_SIZE; + defragInfo.flags = g_DefragmentationFlags; + defragInfo.commandBuffer = m_CommandBuffer; + + VmaDefragmentationContext defragCtx = VK_NULL_HANDLE; + res = vmaDefragmentationBegin(m_Allocator, &defragInfo, &defragStats, &defragCtx); + + const time_point timeAfterDefragBegin = std::chrono::high_resolution_clock::now(); + + vkEndCommandBuffer(m_CommandBuffer); + + if(res >= VK_SUCCESS) + { + VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &m_CommandBuffer; + vkQueueSubmit(m_TransferQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(m_TransferQueue); + + const time_point timeAfterGpu = std::chrono::high_resolution_clock::now(); + + vmaDefragmentationEnd(m_Allocator, defragCtx); + + const time_point timeAfterDefragEnd = std::chrono::high_resolution_clock::now(); + + const duration defragDurationBegin = timeAfterDefragBegin - timeBeg; + const duration defragDurationGpu = timeAfterGpu - timeAfterDefragBegin; + const duration defragDurationEnd = timeAfterDefragEnd - timeAfterGpu; + + // If anything changed. + if(defragStats.allocationsMoved > 0) + { + // Go over allocation that changed and destroy their buffers and images. + size_t i = 0; + 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()) + { + // Do nothing - the function was deprecated and has been removed. + //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_KHRONOS_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_KHRONOS_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_KHRONOS_VALIDATION: + { + bool newValue; + if(StrRangeToBool(StrRange(cmdLineParser.GetParameter()), newValue)) + { + g_VK_LAYER_KHRONOS_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; + } +}
diff --git a/src/VmaReplay/VmaUsage.cpp b/src/VmaReplay/VmaUsage.cpp index 6353bcf..c4a6db2 100644 --- a/src/VmaReplay/VmaUsage.cpp +++ b/src/VmaReplay/VmaUsage.cpp
@@ -1,24 +1,24 @@ -// -// Copyright (c) 2017-2021 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. -// - -#define VMA_IMPLEMENTATION -#include "VmaUsage.h" +// +// Copyright (c) 2017-2021 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. +// + +#define VMA_IMPLEMENTATION +#include "VmaUsage.h"
diff --git a/src/VmaReplay/VmaUsage.h b/src/VmaReplay/VmaUsage.h index a2d2e97..36c9a1f 100644 --- a/src/VmaReplay/VmaUsage.h +++ b/src/VmaReplay/VmaUsage.h
@@ -1,52 +1,52 @@ -// -// Copyright (c) 2017-2021 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. -// - -#pragma once - -#define NOMINMAX -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> - -#if !defined(VK_USE_PLATFORM_WIN32_KHR) - #define VK_USE_PLATFORM_WIN32_KHR -#endif // #if !defined(VK_USE_PLATFORM_WIN32_KHR) -#include <vulkan/vulkan.h> - -//#define VMA_USE_STL_CONTAINERS 1 - -//#define VMA_HEAVY_ASSERT(expr) assert(expr) - -//#define VMA_DEDICATED_ALLOCATION 0 - -//#define VMA_DEBUG_MARGIN 16 -//#define VMA_DEBUG_DETECT_CORRUPTION 1 -//#define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1 - -#pragma warning(push, 4) -#pragma warning(disable: 4127) // conditional expression is constant -#pragma warning(disable: 4100) // unreferenced formal parameter -#pragma warning(disable: 4189) // local variable is initialized but not referenced -#pragma warning(disable: 4324) // structure was padded due to alignment specifier - -#include "../../include/vk_mem_alloc.h" - -#pragma warning(pop) +// +// Copyright (c) 2017-2021 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. +// + +#pragma once + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +#if !defined(VK_USE_PLATFORM_WIN32_KHR) + #define VK_USE_PLATFORM_WIN32_KHR +#endif // #if !defined(VK_USE_PLATFORM_WIN32_KHR) +#include <vulkan/vulkan.h> + +//#define VMA_USE_STL_CONTAINERS 1 + +//#define VMA_HEAVY_ASSERT(expr) assert(expr) + +//#define VMA_DEDICATED_ALLOCATION 0 + +//#define VMA_DEBUG_MARGIN 16 +//#define VMA_DEBUG_DETECT_CORRUPTION 1 +//#define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1 + +#pragma warning(push, 4) +#pragma warning(disable: 4127) // conditional expression is constant +#pragma warning(disable: 4100) // unreferenced formal parameter +#pragma warning(disable: 4189) // local variable is initialized but not referenced +#pragma warning(disable: 4324) // structure was padded due to alignment specifier + +#include "../../include/vk_mem_alloc.h" + +#pragma warning(pop)
diff --git a/src/VmaUsage.cpp b/src/VmaUsage.cpp index cd2d783..b7f5498 100644 --- a/src/VmaUsage.cpp +++ b/src/VmaUsage.cpp
@@ -1,30 +1,30 @@ -// -// Copyright (c) 2017-2021 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. -// - -/* -In exactly one CPP file define macro VMA_IMPLEMENTATION and then include -vk_mem_alloc.h to include definitions of its internal implementation -*/ - -#define VMA_IMPLEMENTATION - -#include "VmaUsage.h" +// +// Copyright (c) 2017-2021 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. +// + +/* +In exactly one CPP file define macro VMA_IMPLEMENTATION and then include +vk_mem_alloc.h to include definitions of its internal implementation +*/ + +#define VMA_IMPLEMENTATION + +#include "VmaUsage.h"
diff --git a/src/VmaUsage.h b/src/VmaUsage.h index 10714e7..2e53c75 100644 --- a/src/VmaUsage.h +++ b/src/VmaUsage.h
@@ -1,98 +1,98 @@ -// -// Copyright (c) 2017-2021 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. -// - -#ifndef VMA_USAGE_H_ -#define VMA_USAGE_H_ - -#ifdef _WIN32 - -#define NOMINMAX -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> -#if !defined(VK_USE_PLATFORM_WIN32_KHR) - #define VK_USE_PLATFORM_WIN32_KHR -#endif // #if !defined(VK_USE_PLATFORM_WIN32_KHR) - -#else // #ifdef _WIN32 - -#include <vulkan/vulkan.h> - -#endif // #ifdef _WIN32 - -#ifdef _MSVC_LANG - -// Uncomment to test including `vulkan.h` on your own before including VMA. -//#include <vulkan/vulkan.h> - -/* -In every place where you want to use Vulkan Memory Allocator, define appropriate -macros if you want to configure the library and then include its header to -include all public interface declarations. Example: -*/ - -//#define VMA_HEAVY_ASSERT(expr) assert(expr) -//#define VMA_DEDICATED_ALLOCATION 0 -//#define VMA_DEBUG_MARGIN 16 -//#define VMA_DEBUG_DETECT_CORRUPTION 1 -//#define VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY 256 -//#define VMA_USE_STL_SHARED_MUTEX 0 -//#define VMA_MEMORY_BUDGET 0 - -#define VMA_VULKAN_VERSION 1002000 // Vulkan 1.2 -//#define VMA_VULKAN_VERSION 1001000 // Vulkan 1.1 -//#define VMA_VULKAN_VERSION 1000000 // Vulkan 1.0 - -/* -#define VMA_DEBUG_LOG(format, ...) do { \ - printf(format, __VA_ARGS__); \ - printf("\n"); \ - } while(false) -*/ - -#pragma warning(push, 4) -#pragma warning(disable: 4127) // conditional expression is constant -#pragma warning(disable: 4100) // unreferenced formal parameter -#pragma warning(disable: 4189) // local variable is initialized but not referenced -#pragma warning(disable: 4324) // structure was padded due to alignment specifier - -#endif // #ifdef _MSVC_LANG - -#ifdef __clang__ - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wtautological-compare" // comparison of unsigned expression < 0 is always false - #pragma clang diagnostic ignored "-Wunused-private-field" - #pragma clang diagnostic ignored "-Wunused-parameter" - #pragma clang diagnostic ignored "-Wmissing-field-initializers" - #pragma clang diagnostic ignored "-Wnullability-completeness" -#endif - -#include "../include/vk_mem_alloc.h" - -#ifdef __clang__ - #pragma clang diagnostic pop -#endif - -#ifdef _MSVC_LANG - #pragma warning(pop) -#endif - -#endif +// +// Copyright (c) 2017-2021 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. +// + +#ifndef VMA_USAGE_H_ +#define VMA_USAGE_H_ + +#ifdef _WIN32 + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#if !defined(VK_USE_PLATFORM_WIN32_KHR) + #define VK_USE_PLATFORM_WIN32_KHR +#endif // #if !defined(VK_USE_PLATFORM_WIN32_KHR) + +#else // #ifdef _WIN32 + +#include <vulkan/vulkan.h> + +#endif // #ifdef _WIN32 + +#ifdef _MSVC_LANG + +// Uncomment to test including `vulkan.h` on your own before including VMA. +//#include <vulkan/vulkan.h> + +/* +In every place where you want to use Vulkan Memory Allocator, define appropriate +macros if you want to configure the library and then include its header to +include all public interface declarations. Example: +*/ + +//#define VMA_HEAVY_ASSERT(expr) assert(expr) +//#define VMA_DEDICATED_ALLOCATION 0 +//#define VMA_DEBUG_MARGIN 16 +//#define VMA_DEBUG_DETECT_CORRUPTION 1 +//#define VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY 256 +//#define VMA_USE_STL_SHARED_MUTEX 0 +//#define VMA_MEMORY_BUDGET 0 + +#define VMA_VULKAN_VERSION 1002000 // Vulkan 1.2 +//#define VMA_VULKAN_VERSION 1001000 // Vulkan 1.1 +//#define VMA_VULKAN_VERSION 1000000 // Vulkan 1.0 + +/* +#define VMA_DEBUG_LOG(format, ...) do { \ + printf(format, __VA_ARGS__); \ + printf("\n"); \ + } while(false) +*/ + +#pragma warning(push, 4) +#pragma warning(disable: 4127) // conditional expression is constant +#pragma warning(disable: 4100) // unreferenced formal parameter +#pragma warning(disable: 4189) // local variable is initialized but not referenced +#pragma warning(disable: 4324) // structure was padded due to alignment specifier + +#endif // #ifdef _MSVC_LANG + +#ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wtautological-compare" // comparison of unsigned expression < 0 is always false + #pragma clang diagnostic ignored "-Wunused-private-field" + #pragma clang diagnostic ignored "-Wunused-parameter" + #pragma clang diagnostic ignored "-Wmissing-field-initializers" + #pragma clang diagnostic ignored "-Wnullability-completeness" +#endif + +#include "../include/vk_mem_alloc.h" + +#ifdef __clang__ + #pragma clang diagnostic pop +#endif + +#ifdef _MSVC_LANG + #pragma warning(pop) +#endif + +#endif
diff --git a/src/VulkanSample.cpp b/src/VulkanSample.cpp index 0d07cfb..a313083 100644 --- a/src/VulkanSample.cpp +++ b/src/VulkanSample.cpp
@@ -1,2615 +1,2615 @@ -// -// Copyright (c) 2017-2021 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. -// - -#ifdef _WIN32 - -#include "SparseBindingTest.h" -#include "Tests.h" -#include "VmaUsage.h" -#include "Common.h" -#include <atomic> -#include <Shlwapi.h> - -#pragma comment(lib, "shlwapi.lib") - -static const char* const SHADER_PATH1 = "./"; -static const char* const SHADER_PATH2 = "../bin/"; -static const wchar_t* const WINDOW_CLASS_NAME = L"VULKAN_MEMORY_ALLOCATOR_SAMPLE"; -static const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; -static const char* const APP_TITLE_A = "Vulkan Memory Allocator Sample 2.4.0"; -static const wchar_t* const APP_TITLE_W = L"Vulkan Memory Allocator Sample 2.4.0"; - -static const bool VSYNC = true; -static const uint32_t COMMAND_BUFFER_COUNT = 2; -static void* const CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA = (void*)(intptr_t)43564544; -static const bool USE_CUSTOM_CPU_ALLOCATION_CALLBACKS = true; - -enum class ExitCode : int -{ - GPUList = 2, - Help = 1, - Success = 0, - RuntimeError = -1, - CommandLineError = -2, -}; - -VkPhysicalDevice g_hPhysicalDevice; -VkDevice g_hDevice; -VmaAllocator g_hAllocator; -VkInstance g_hVulkanInstance; - -bool g_EnableValidationLayer = true; -bool VK_KHR_get_memory_requirements2_enabled = false; -bool VK_KHR_get_physical_device_properties2_enabled = false; -bool VK_KHR_dedicated_allocation_enabled = false; -bool VK_KHR_bind_memory2_enabled = false; -bool VK_EXT_memory_budget_enabled = false; -bool VK_AMD_device_coherent_memory_enabled = false; -bool VK_KHR_buffer_device_address_enabled = false; -bool VK_EXT_memory_priority_enabled = false; -bool VK_EXT_debug_utils_enabled = false; -bool g_SparseBindingEnabled = false; - -// # Pointers to functions from extensions -PFN_vkGetBufferDeviceAddressKHR g_vkGetBufferDeviceAddressKHR; - -static HINSTANCE g_hAppInstance; -static HWND g_hWnd; -static LONG g_SizeX = 1280, g_SizeY = 720; -static VkSurfaceKHR g_hSurface; -static VkQueue g_hPresentQueue; -static VkSurfaceFormatKHR g_SurfaceFormat; -static VkExtent2D g_Extent; -static VkSwapchainKHR g_hSwapchain; -static std::vector<VkImage> g_SwapchainImages; -static std::vector<VkImageView> g_SwapchainImageViews; -static std::vector<VkFramebuffer> g_Framebuffers; -static VkCommandPool g_hCommandPool; -static VkCommandBuffer g_MainCommandBuffers[COMMAND_BUFFER_COUNT]; -static VkFence g_MainCommandBufferExecutedFances[COMMAND_BUFFER_COUNT]; -VkFence g_ImmediateFence; -static uint32_t g_NextCommandBufferIndex; -static VkSemaphore g_hImageAvailableSemaphore; -static VkSemaphore g_hRenderFinishedSemaphore; -static uint32_t g_GraphicsQueueFamilyIndex = UINT_MAX; -static uint32_t g_PresentQueueFamilyIndex = UINT_MAX; -static uint32_t g_SparseBindingQueueFamilyIndex = UINT_MAX; -static VkDescriptorSetLayout g_hDescriptorSetLayout; -static VkDescriptorPool g_hDescriptorPool; -static VkDescriptorSet g_hDescriptorSet; // Automatically destroyed with m_DescriptorPool. -static VkSampler g_hSampler; -static VkFormat g_DepthFormat; -static VkImage g_hDepthImage; -static VmaAllocation g_hDepthImageAlloc; -static VkImageView g_hDepthImageView; - -static VkSurfaceCapabilitiesKHR g_SurfaceCapabilities; -static std::vector<VkSurfaceFormatKHR> g_SurfaceFormats; -static std::vector<VkPresentModeKHR> g_PresentModes; - -static const VkDebugUtilsMessageSeverityFlagsEXT DEBUG_UTILS_MESSENGER_MESSAGE_SEVERITY = - //VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | - //VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; -static const VkDebugUtilsMessageTypeFlagsEXT DEBUG_UTILS_MESSENGER_MESSAGE_TYPE = - VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; -static PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT_Func; -static PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT_Func; -static PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT_Func; - -static VkQueue g_hGraphicsQueue; -VkQueue g_hSparseBindingQueue; -VkCommandBuffer g_hTemporaryCommandBuffer; - -static VkPipelineLayout g_hPipelineLayout; -static VkRenderPass g_hRenderPass; -static VkPipeline g_hPipeline; - -static VkBuffer g_hVertexBuffer; -static VmaAllocation g_hVertexBufferAlloc; -static VkBuffer g_hIndexBuffer; -static VmaAllocation g_hIndexBufferAlloc; -static uint32_t g_VertexCount; -static uint32_t g_IndexCount; - -static VkImage g_hTextureImage; -static VmaAllocation g_hTextureImageAlloc; -static VkImageView g_hTextureImageView; - -static std::atomic_uint32_t g_CpuAllocCount; - -static void* CustomCpuAllocation( - void* pUserData, size_t size, size_t alignment, - VkSystemAllocationScope allocationScope) -{ - assert(pUserData == CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA); - void* const result = _aligned_malloc(size, alignment); - if(result) - { - ++g_CpuAllocCount; - } - return result; -} - -static void* CustomCpuReallocation( - void* pUserData, void* pOriginal, size_t size, size_t alignment, - VkSystemAllocationScope allocationScope) -{ - assert(pUserData == CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA); - void* const result = _aligned_realloc(pOriginal, size, alignment); - if(pOriginal && !result) - { - --g_CpuAllocCount; - } - else if(!pOriginal && result) - { - ++g_CpuAllocCount; - } - return result; -} - -static void CustomCpuFree(void* pUserData, void* pMemory) -{ - assert(pUserData == CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA); - if(pMemory) - { - const uint32_t oldAllocCount = g_CpuAllocCount.fetch_sub(1); - TEST(oldAllocCount > 0); - _aligned_free(pMemory); - } -} - -static const VkAllocationCallbacks g_CpuAllocationCallbacks = { - CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA, // pUserData - &CustomCpuAllocation, // pfnAllocation - &CustomCpuReallocation, // pfnReallocation - &CustomCpuFree // pfnFree -}; - -const VkAllocationCallbacks* g_Allocs; - -struct GPUSelection -{ - uint32_t Index = UINT32_MAX; - std::wstring Substring; -}; - -class VulkanUsage -{ -public: - void Init(); - ~VulkanUsage(); - void PrintPhysicalDeviceList() const; - // If failed, returns VK_NULL_HANDLE. - VkPhysicalDevice SelectPhysicalDevice(const GPUSelection& GPUSelection) const; - -private: - VkDebugUtilsMessengerEXT m_DebugUtilsMessenger = VK_NULL_HANDLE; - - void RegisterDebugCallbacks(); - static bool IsLayerSupported(const VkLayerProperties* pProps, size_t propCount, const char* pLayerName); -}; - -struct CommandLineParameters -{ - bool m_Help = false; - bool m_List = false; - GPUSelection m_GPUSelection; - - bool Parse(int argc, wchar_t** argv) - { - for(int i = 1; i < argc; ++i) - { - if(_wcsicmp(argv[i], L"-h") == 0 || _wcsicmp(argv[i], L"--Help") == 0) - { - m_Help = true; - } - else if(_wcsicmp(argv[i], L"-l") == 0 || _wcsicmp(argv[i], L"--List") == 0) - { - m_List = true; - } - else if((_wcsicmp(argv[i], L"-g") == 0 || _wcsicmp(argv[i], L"--GPU") == 0) && i + 1 < argc) - { - m_GPUSelection.Substring = argv[i + 1]; - ++i; - } - else if((_wcsicmp(argv[i], L"-i") == 0 || _wcsicmp(argv[i], L"--GPUIndex") == 0) && i + 1 < argc) - { - m_GPUSelection.Index = _wtoi(argv[i + 1]); - ++i; - } - else - return false; - } - return true; - } -} g_CommandLineParameters; - -void SetDebugUtilsObjectName(VkObjectType type, uint64_t handle, const char* name) -{ - if(vkSetDebugUtilsObjectNameEXT_Func == nullptr) - return; - - VkDebugUtilsObjectNameInfoEXT info = { VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT }; - info.objectType = type; - info.objectHandle = handle; - info.pObjectName = name; - vkSetDebugUtilsObjectNameEXT_Func(g_hDevice, &info); -} - -void BeginSingleTimeCommands() -{ - VkCommandBufferBeginInfo cmdBufBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - cmdBufBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - ERR_GUARD_VULKAN( vkBeginCommandBuffer(g_hTemporaryCommandBuffer, &cmdBufBeginInfo) ); -} - -void EndSingleTimeCommands() -{ - ERR_GUARD_VULKAN( vkEndCommandBuffer(g_hTemporaryCommandBuffer) ); - - VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &g_hTemporaryCommandBuffer; - - ERR_GUARD_VULKAN( vkQueueSubmit(g_hGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) ); - ERR_GUARD_VULKAN( vkQueueWaitIdle(g_hGraphicsQueue) ); -} - -void LoadShader(std::vector<char>& out, const char* fileName) -{ - std::ifstream file(std::string(SHADER_PATH1) + fileName, std::ios::ate | std::ios::binary); - if(file.is_open() == false) - file.open(std::string(SHADER_PATH2) + fileName, std::ios::ate | std::ios::binary); - assert(file.is_open()); - size_t fileSize = (size_t)file.tellg(); - if(fileSize > 0) - { - out.resize(fileSize); - file.seekg(0); - file.read(out.data(), fileSize); - file.close(); - } - else - out.clear(); -} - -static VkBool32 VKAPI_PTR MyDebugReportCallback( - VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, - VkDebugUtilsMessageTypeFlagsEXT messageTypes, - const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - void* pUserData) -{ - assert(pCallbackData && pCallbackData->pMessageIdName && pCallbackData->pMessage); - - switch(messageSeverity) - { - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: - SetConsoleColor(CONSOLE_COLOR::WARNING); - break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: - SetConsoleColor(CONSOLE_COLOR::ERROR_); - break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: - SetConsoleColor(CONSOLE_COLOR::NORMAL); - break; - default: // VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT - SetConsoleColor(CONSOLE_COLOR::INFO); - } - - printf("%s \xBA %s\n", pCallbackData->pMessageIdName, pCallbackData->pMessage); - - SetConsoleColor(CONSOLE_COLOR::NORMAL); - - if(messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT || - messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) - { - OutputDebugStringA(pCallbackData->pMessage); - OutputDebugStringA("\n"); - } - - return VK_FALSE; -} - -static VkSurfaceFormatKHR ChooseSurfaceFormat() -{ - assert(!g_SurfaceFormats.empty()); - - if((g_SurfaceFormats.size() == 1) && (g_SurfaceFormats[0].format == VK_FORMAT_UNDEFINED)) - { - VkSurfaceFormatKHR result = { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; - return result; - } - - for(const auto& format : g_SurfaceFormats) - { - if((format.format == VK_FORMAT_B8G8R8A8_UNORM) && - (format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)) - { - return format; - } - } - - return g_SurfaceFormats[0]; -} - -VkPresentModeKHR ChooseSwapPresentMode() -{ - VkPresentModeKHR preferredMode = VSYNC ? VK_PRESENT_MODE_MAILBOX_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR; - - if(std::find(g_PresentModes.begin(), g_PresentModes.end(), preferredMode) != - g_PresentModes.end()) - { - return preferredMode; - } - - return VK_PRESENT_MODE_FIFO_KHR; -} - -static VkExtent2D ChooseSwapExtent() -{ - if(g_SurfaceCapabilities.currentExtent.width != UINT_MAX) - return g_SurfaceCapabilities.currentExtent; - - VkExtent2D result = { - std::max(g_SurfaceCapabilities.minImageExtent.width, - std::min(g_SurfaceCapabilities.maxImageExtent.width, (uint32_t)g_SizeX)), - std::max(g_SurfaceCapabilities.minImageExtent.height, - std::min(g_SurfaceCapabilities.maxImageExtent.height, (uint32_t)g_SizeY)) }; - return result; -} - -static constexpr uint32_t GetVulkanApiVersion() -{ -#if VMA_VULKAN_VERSION == 1002000 - return VK_API_VERSION_1_2; -#elif VMA_VULKAN_VERSION == 1001000 - return VK_API_VERSION_1_1; -#elif VMA_VULKAN_VERSION == 1000000 - return VK_API_VERSION_1_0; -#else -#error Invalid VMA_VULKAN_VERSION. - return UINT32_MAX; -#endif -} - -void VulkanUsage::Init() -{ - g_hAppInstance = (HINSTANCE)GetModuleHandle(NULL); - - if(USE_CUSTOM_CPU_ALLOCATION_CALLBACKS) - { - g_Allocs = &g_CpuAllocationCallbacks; - } - - uint32_t instanceLayerPropCount = 0; - ERR_GUARD_VULKAN( vkEnumerateInstanceLayerProperties(&instanceLayerPropCount, nullptr) ); - std::vector<VkLayerProperties> instanceLayerProps(instanceLayerPropCount); - if(instanceLayerPropCount > 0) - { - ERR_GUARD_VULKAN( vkEnumerateInstanceLayerProperties(&instanceLayerPropCount, instanceLayerProps.data()) ); - } - - if(g_EnableValidationLayer) - { - if(IsLayerSupported(instanceLayerProps.data(), instanceLayerProps.size(), VALIDATION_LAYER_NAME) == false) - { - wprintf(L"Layer \"%hs\" not supported.", VALIDATION_LAYER_NAME); - g_EnableValidationLayer = false; - } - } - - uint32_t availableInstanceExtensionCount = 0; - ERR_GUARD_VULKAN( vkEnumerateInstanceExtensionProperties(nullptr, &availableInstanceExtensionCount, nullptr) ); - std::vector<VkExtensionProperties> availableInstanceExtensions(availableInstanceExtensionCount); - if(availableInstanceExtensionCount > 0) - { - ERR_GUARD_VULKAN( vkEnumerateInstanceExtensionProperties(nullptr, &availableInstanceExtensionCount, availableInstanceExtensions.data()) ); - } - - 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(g_EnableValidationLayer) - { - instanceLayers.push_back(VALIDATION_LAYER_NAME); - } - - for(const auto& extensionProperties : availableInstanceExtensions) - { - if(strcmp(extensionProperties.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0) - { - if(GetVulkanApiVersion() == VK_API_VERSION_1_0) - { - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - VK_KHR_get_physical_device_properties2_enabled = true; - } - } - else if(strcmp(extensionProperties.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) - { - enabledInstanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - VK_EXT_debug_utils_enabled = true; - } - } - - VkApplicationInfo appInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO }; - appInfo.pApplicationName = APP_TITLE_A; - appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.pEngineName = "Adam Sawicki Engine"; - appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = GetVulkanApiVersion(); - - VkInstanceCreateInfo instInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; - instInfo.pApplicationInfo = &appInfo; - instInfo.enabledExtensionCount = static_cast<uint32_t>(enabledInstanceExtensions.size()); - instInfo.ppEnabledExtensionNames = enabledInstanceExtensions.data(); - instInfo.enabledLayerCount = static_cast<uint32_t>(instanceLayers.size()); - instInfo.ppEnabledLayerNames = instanceLayers.data(); - - wprintf(L"Vulkan API version used: "); - switch(appInfo.apiVersion) - { - case VK_API_VERSION_1_0: wprintf(L"1.0\n"); break; - case VK_API_VERSION_1_1: wprintf(L"1.1\n"); break; - case VK_API_VERSION_1_2: wprintf(L"1.2\n"); break; - default: assert(0); - } - - ERR_GUARD_VULKAN( vkCreateInstance(&instInfo, g_Allocs, &g_hVulkanInstance) ); - - if(VK_EXT_debug_utils_enabled) - { - RegisterDebugCallbacks(); - } -} - -VulkanUsage::~VulkanUsage() -{ - if(m_DebugUtilsMessenger) - { - vkDestroyDebugUtilsMessengerEXT_Func(g_hVulkanInstance, m_DebugUtilsMessenger, g_Allocs); - } - - if(g_hVulkanInstance) - { - vkDestroyInstance(g_hVulkanInstance, g_Allocs); - g_hVulkanInstance = VK_NULL_HANDLE; - } -} - -void VulkanUsage::PrintPhysicalDeviceList() const -{ - uint32_t deviceCount = 0; - ERR_GUARD_VULKAN(vkEnumeratePhysicalDevices(g_hVulkanInstance, &deviceCount, nullptr)); - std::vector<VkPhysicalDevice> physicalDevices(deviceCount); - if(deviceCount > 0) - { - ERR_GUARD_VULKAN(vkEnumeratePhysicalDevices(g_hVulkanInstance, &deviceCount, physicalDevices.data())); - } - - for(size_t i = 0; i < deviceCount; ++i) - { - VkPhysicalDeviceProperties props = {}; - vkGetPhysicalDeviceProperties(physicalDevices[i], &props); - wprintf(L"Physical device %zu: %hs\n", i, props.deviceName); - } -} - -VkPhysicalDevice VulkanUsage::SelectPhysicalDevice(const GPUSelection& GPUSelection) const -{ - uint32_t deviceCount = 0; - ERR_GUARD_VULKAN(vkEnumeratePhysicalDevices(g_hVulkanInstance, &deviceCount, nullptr)); - std::vector<VkPhysicalDevice> physicalDevices(deviceCount); - if(deviceCount > 0) - { - ERR_GUARD_VULKAN(vkEnumeratePhysicalDevices(g_hVulkanInstance, &deviceCount, physicalDevices.data())); - } - - if(GPUSelection.Index != UINT32_MAX) - { - // Cannot specify both index and name. - if(!GPUSelection.Substring.empty()) - { - return VK_NULL_HANDLE; - } - - return GPUSelection.Index < deviceCount ? physicalDevices[GPUSelection.Index] : VK_NULL_HANDLE; - } - - if(!GPUSelection.Substring.empty()) - { - VkPhysicalDevice result = VK_NULL_HANDLE; - std::wstring name; - for(uint32_t i = 0; i < deviceCount; ++i) - { - VkPhysicalDeviceProperties props = {}; - vkGetPhysicalDeviceProperties(physicalDevices[i], &props); - if(ConvertCharsToUnicode(&name, props.deviceName, strlen(props.deviceName), CP_UTF8) && - StrStrI(name.c_str(), GPUSelection.Substring.c_str())) - { - // Second matching device found - error. - if(result != VK_NULL_HANDLE) - { - return VK_NULL_HANDLE; - } - // First matching device found. - result = physicalDevices[i]; - } - } - // Found or not, return it. - return result; - } - - // Select first one. - return deviceCount > 0 ? physicalDevices[0] : VK_NULL_HANDLE; -} - -void VulkanUsage::RegisterDebugCallbacks() -{ - vkCreateDebugUtilsMessengerEXT_Func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( - g_hVulkanInstance, "vkCreateDebugUtilsMessengerEXT"); - vkDestroyDebugUtilsMessengerEXT_Func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( - g_hVulkanInstance, "vkDestroyDebugUtilsMessengerEXT"); - vkSetDebugUtilsObjectNameEXT_Func = (PFN_vkSetDebugUtilsObjectNameEXT)vkGetInstanceProcAddr( - g_hVulkanInstance, "vkSetDebugUtilsObjectNameEXT"); - assert(vkCreateDebugUtilsMessengerEXT_Func); - assert(vkDestroyDebugUtilsMessengerEXT_Func); - assert(vkSetDebugUtilsObjectNameEXT_Func); - - VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT }; - messengerCreateInfo.messageSeverity = DEBUG_UTILS_MESSENGER_MESSAGE_SEVERITY; - messengerCreateInfo.messageType = DEBUG_UTILS_MESSENGER_MESSAGE_TYPE; - messengerCreateInfo.pfnUserCallback = MyDebugReportCallback; - ERR_GUARD_VULKAN( vkCreateDebugUtilsMessengerEXT_Func(g_hVulkanInstance, &messengerCreateInfo, g_Allocs, &m_DebugUtilsMessenger) ); -} - -bool VulkanUsage::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; -} - -struct Vertex -{ - float pos[3]; - float color[3]; - float texCoord[2]; -}; - -static void CreateMesh() -{ - assert(g_hAllocator); - - static Vertex vertices[] = { - // -X - { { -1.f, -1.f, -1.f}, {1.0f, 0.0f, 0.0f}, {0.f, 0.f} }, - { { -1.f, -1.f, 1.f}, {1.0f, 0.0f, 0.0f}, {1.f, 0.f} }, - { { -1.f, 1.f, -1.f}, {1.0f, 0.0f, 0.0f}, {0.f, 1.f} }, - { { -1.f, 1.f, 1.f}, {1.0f, 0.0f, 0.0f}, {1.f, 1.f} }, - // +X - { { 1.f, -1.f, 1.f}, {0.0f, 1.0f, 0.0f}, {0.f, 0.f} }, - { { 1.f, -1.f, -1.f}, {0.0f, 1.0f, 0.0f}, {1.f, 0.f} }, - { { 1.f, 1.f, 1.f}, {0.0f, 1.0f, 0.0f}, {0.f, 1.f} }, - { { 1.f, 1.f, -1.f}, {0.0f, 1.0f, 0.0f}, {1.f, 1.f} }, - // -Z - { { 1.f, -1.f, -1.f}, {0.0f, 0.0f, 1.0f}, {0.f, 0.f} }, - { {-1.f, -1.f, -1.f}, {0.0f, 0.0f, 1.0f}, {1.f, 0.f} }, - { { 1.f, 1.f, -1.f}, {0.0f, 0.0f, 1.0f}, {0.f, 1.f} }, - { {-1.f, 1.f, -1.f}, {0.0f, 0.0f, 1.0f}, {1.f, 1.f} }, - // +Z - { {-1.f, -1.f, 1.f}, {1.0f, 1.0f, 0.0f}, {0.f, 0.f} }, - { { 1.f, -1.f, 1.f}, {1.0f, 1.0f, 0.0f}, {1.f, 0.f} }, - { {-1.f, 1.f, 1.f}, {1.0f, 1.0f, 0.0f}, {0.f, 1.f} }, - { { 1.f, 1.f, 1.f}, {1.0f, 1.0f, 0.0f}, {1.f, 1.f} }, - // -Y - { {-1.f, -1.f, -1.f}, {0.0f, 1.0f, 1.0f}, {0.f, 0.f} }, - { { 1.f, -1.f, -1.f}, {0.0f, 1.0f, 1.0f}, {1.f, 0.f} }, - { {-1.f, -1.f, 1.f}, {0.0f, 1.0f, 1.0f}, {0.f, 1.f} }, - { { 1.f, -1.f, 1.f}, {0.0f, 1.0f, 1.0f}, {1.f, 1.f} }, - // +Y - { { 1.f, 1.f, -1.f}, {1.0f, 0.0f, 1.0f}, {0.f, 0.f} }, - { {-1.f, 1.f, -1.f}, {1.0f, 0.0f, 1.0f}, {1.f, 0.f} }, - { { 1.f, 1.f, 1.f}, {1.0f, 0.0f, 1.0f}, {0.f, 1.f} }, - { {-1.f, 1.f, 1.f}, {1.0f, 0.0f, 1.0f}, {1.f, 1.f} }, - }; - static uint16_t indices[] = { - 0, 1, 2, 3, USHRT_MAX, - 4, 5, 6, 7, USHRT_MAX, - 8, 9, 10, 11, USHRT_MAX, - 12, 13, 14, 15, USHRT_MAX, - 16, 17, 18, 19, USHRT_MAX, - 20, 21, 22, 23, USHRT_MAX, - }; - - size_t vertexBufferSize = sizeof(Vertex) * _countof(vertices); - size_t indexBufferSize = sizeof(uint16_t) * _countof(indices); - g_IndexCount = (uint32_t)_countof(indices); - - // Create vertex buffer - - VkBufferCreateInfo vbInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - vbInfo.size = vertexBufferSize; - vbInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - vbInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - VmaAllocationCreateInfo vbAllocCreateInfo = {}; - vbAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - vbAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - - VkBuffer stagingVertexBuffer = VK_NULL_HANDLE; - VmaAllocation stagingVertexBufferAlloc = VK_NULL_HANDLE; - VmaAllocationInfo stagingVertexBufferAllocInfo = {}; - ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &vbInfo, &vbAllocCreateInfo, &stagingVertexBuffer, &stagingVertexBufferAlloc, &stagingVertexBufferAllocInfo) ); - - memcpy(stagingVertexBufferAllocInfo.pMappedData, vertices, vertexBufferSize); - - // No need to flush stagingVertexBuffer memory because CPU_ONLY memory is always HOST_COHERENT. - - vbInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - vbAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - vbAllocCreateInfo.flags = 0; - ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &vbInfo, &vbAllocCreateInfo, &g_hVertexBuffer, &g_hVertexBufferAlloc, nullptr) ); - - // Create index buffer - - VkBufferCreateInfo ibInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - ibInfo.size = indexBufferSize; - ibInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - ibInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - VmaAllocationCreateInfo ibAllocCreateInfo = {}; - ibAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - ibAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - - VkBuffer stagingIndexBuffer = VK_NULL_HANDLE; - VmaAllocation stagingIndexBufferAlloc = VK_NULL_HANDLE; - VmaAllocationInfo stagingIndexBufferAllocInfo = {}; - ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &ibInfo, &ibAllocCreateInfo, &stagingIndexBuffer, &stagingIndexBufferAlloc, &stagingIndexBufferAllocInfo) ); - - memcpy(stagingIndexBufferAllocInfo.pMappedData, indices, indexBufferSize); - - // No need to flush stagingIndexBuffer memory because CPU_ONLY memory is always HOST_COHERENT. - - ibInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; - ibAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - ibAllocCreateInfo.flags = 0; - ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &ibInfo, &ibAllocCreateInfo, &g_hIndexBuffer, &g_hIndexBufferAlloc, nullptr) ); - - // Copy buffers - - BeginSingleTimeCommands(); - - VkBufferCopy vbCopyRegion = {}; - vbCopyRegion.srcOffset = 0; - vbCopyRegion.dstOffset = 0; - vbCopyRegion.size = vbInfo.size; - vkCmdCopyBuffer(g_hTemporaryCommandBuffer, stagingVertexBuffer, g_hVertexBuffer, 1, &vbCopyRegion); - - VkBufferCopy ibCopyRegion = {}; - ibCopyRegion.srcOffset = 0; - ibCopyRegion.dstOffset = 0; - ibCopyRegion.size = ibInfo.size; - vkCmdCopyBuffer(g_hTemporaryCommandBuffer, stagingIndexBuffer, g_hIndexBuffer, 1, &ibCopyRegion); - - EndSingleTimeCommands(); - - vmaDestroyBuffer(g_hAllocator, stagingIndexBuffer, stagingIndexBufferAlloc); - vmaDestroyBuffer(g_hAllocator, stagingVertexBuffer, stagingVertexBufferAlloc); -} - -static void CreateTexture(uint32_t sizeX, uint32_t sizeY) -{ - // Create staging buffer. - - const VkDeviceSize imageSize = sizeX * sizeY * 4; - - VkBufferCreateInfo stagingBufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - stagingBufInfo.size = imageSize; - stagingBufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - - VmaAllocationCreateInfo stagingBufAllocCreateInfo = {}; - stagingBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; - stagingBufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - - VkBuffer stagingBuf = VK_NULL_HANDLE; - VmaAllocation stagingBufAlloc = VK_NULL_HANDLE; - VmaAllocationInfo stagingBufAllocInfo = {}; - ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &stagingBufInfo, &stagingBufAllocCreateInfo, &stagingBuf, &stagingBufAlloc, &stagingBufAllocInfo) ); - - char* const pImageData = (char*)stagingBufAllocInfo.pMappedData; - uint8_t* pRowData = (uint8_t*)pImageData; - for(uint32_t y = 0; y < sizeY; ++y) - { - uint32_t* pPixelData = (uint32_t*)pRowData; - for(uint32_t x = 0; x < sizeY; ++x) - { - *pPixelData = - ((x & 0x18) == 0x08 ? 0x000000FF : 0x00000000) | - ((x & 0x18) == 0x10 ? 0x0000FFFF : 0x00000000) | - ((y & 0x18) == 0x08 ? 0x0000FF00 : 0x00000000) | - ((y & 0x18) == 0x10 ? 0x00FF0000 : 0x00000000); - ++pPixelData; - } - pRowData += sizeX * 4; - } - - // No need to flush stagingImage memory because CPU_ONLY memory is always HOST_COHERENT. - - // Create g_hTextureImage in GPU memory. - - VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.extent.width = sizeX; - imageInfo.extent.height = sizeY; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.flags = 0; - - VmaAllocationCreateInfo imageAllocCreateInfo = {}; - imageAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - ERR_GUARD_VULKAN( vmaCreateImage(g_hAllocator, &imageInfo, &imageAllocCreateInfo, &g_hTextureImage, &g_hTextureImageAlloc, nullptr) ); - - // Transition image layouts, copy image. - - BeginSingleTimeCommands(); - - VkImageMemoryBarrier imgMemBarrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; - imgMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imgMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imgMemBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - imgMemBarrier.subresourceRange.baseMipLevel = 0; - imgMemBarrier.subresourceRange.levelCount = 1; - imgMemBarrier.subresourceRange.baseArrayLayer = 0; - imgMemBarrier.subresourceRange.layerCount = 1; - imgMemBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - imgMemBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - imgMemBarrier.image = g_hTextureImage; - imgMemBarrier.srcAccessMask = 0; - imgMemBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - - vkCmdPipelineBarrier( - g_hTemporaryCommandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, - 0, nullptr, - 0, nullptr, - 1, &imgMemBarrier); - - VkBufferImageCopy region = {}; - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.layerCount = 1; - region.imageExtent.width = sizeX; - region.imageExtent.height = sizeY; - region.imageExtent.depth = 1; - - vkCmdCopyBufferToImage(g_hTemporaryCommandBuffer, stagingBuf, g_hTextureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); - - imgMemBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - imgMemBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - imgMemBarrier.image = g_hTextureImage; - imgMemBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - imgMemBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - vkCmdPipelineBarrier( - g_hTemporaryCommandBuffer, - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, - 0, nullptr, - 0, nullptr, - 1, &imgMemBarrier); - - EndSingleTimeCommands(); - - vmaDestroyBuffer(g_hAllocator, stagingBuf, stagingBufAlloc); - - // Create ImageView - - VkImageViewCreateInfo textureImageViewInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; - textureImageViewInfo.image = g_hTextureImage; - textureImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - textureImageViewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - textureImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - textureImageViewInfo.subresourceRange.baseMipLevel = 0; - textureImageViewInfo.subresourceRange.levelCount = 1; - textureImageViewInfo.subresourceRange.baseArrayLayer = 0; - textureImageViewInfo.subresourceRange.layerCount = 1; - ERR_GUARD_VULKAN( vkCreateImageView(g_hDevice, &textureImageViewInfo, g_Allocs, &g_hTextureImageView) ); -} - -struct UniformBufferObject -{ - mat4 ModelViewProj; -}; - -static VkFormat FindSupportedFormat( - const std::vector<VkFormat>& candidates, - VkImageTiling tiling, - VkFormatFeatureFlags features) -{ - for (VkFormat format : candidates) - { - VkFormatProperties props; - vkGetPhysicalDeviceFormatProperties(g_hPhysicalDevice, format, &props); - - if ((tiling == VK_IMAGE_TILING_LINEAR) && - ((props.linearTilingFeatures & features) == features)) - { - return format; - } - else if ((tiling == VK_IMAGE_TILING_OPTIMAL) && - ((props.optimalTilingFeatures & features) == features)) - { - return format; - } - } - return VK_FORMAT_UNDEFINED; -} - -static VkFormat FindDepthFormat() -{ - std::vector<VkFormat> formats; - formats.push_back(VK_FORMAT_D32_SFLOAT); - formats.push_back(VK_FORMAT_D32_SFLOAT_S8_UINT); - formats.push_back(VK_FORMAT_D24_UNORM_S8_UINT); - - return FindSupportedFormat( - formats, - VK_IMAGE_TILING_OPTIMAL, - VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); -} - -static void CreateSwapchain() -{ - // Query surface formats. - - ERR_GUARD_VULKAN( vkGetPhysicalDeviceSurfaceCapabilitiesKHR(g_hPhysicalDevice, g_hSurface, &g_SurfaceCapabilities) ); - - uint32_t formatCount = 0; - ERR_GUARD_VULKAN( vkGetPhysicalDeviceSurfaceFormatsKHR(g_hPhysicalDevice, g_hSurface, &formatCount, nullptr) ); - g_SurfaceFormats.resize(formatCount); - ERR_GUARD_VULKAN( vkGetPhysicalDeviceSurfaceFormatsKHR(g_hPhysicalDevice, g_hSurface, &formatCount, g_SurfaceFormats.data()) ); - - uint32_t presentModeCount = 0; - ERR_GUARD_VULKAN( vkGetPhysicalDeviceSurfacePresentModesKHR(g_hPhysicalDevice, g_hSurface, &presentModeCount, nullptr) ); - g_PresentModes.resize(presentModeCount); - ERR_GUARD_VULKAN( vkGetPhysicalDeviceSurfacePresentModesKHR(g_hPhysicalDevice, g_hSurface, &presentModeCount, g_PresentModes.data()) ); - - // Create swap chain - - g_SurfaceFormat = ChooseSurfaceFormat(); - VkPresentModeKHR presentMode = ChooseSwapPresentMode(); - g_Extent = ChooseSwapExtent(); - - uint32_t imageCount = g_SurfaceCapabilities.minImageCount + 1; - if((g_SurfaceCapabilities.maxImageCount > 0) && - (imageCount > g_SurfaceCapabilities.maxImageCount)) - { - imageCount = g_SurfaceCapabilities.maxImageCount; - } - - VkSwapchainCreateInfoKHR swapChainInfo = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR }; - swapChainInfo.surface = g_hSurface; - swapChainInfo.minImageCount = imageCount; - swapChainInfo.imageFormat = g_SurfaceFormat.format; - swapChainInfo.imageColorSpace = g_SurfaceFormat.colorSpace; - swapChainInfo.imageExtent = g_Extent; - swapChainInfo.imageArrayLayers = 1; - swapChainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - swapChainInfo.preTransform = g_SurfaceCapabilities.currentTransform; - swapChainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - swapChainInfo.presentMode = presentMode; - swapChainInfo.clipped = VK_TRUE; - swapChainInfo.oldSwapchain = g_hSwapchain; - - uint32_t queueFamilyIndices[] = { g_GraphicsQueueFamilyIndex, g_PresentQueueFamilyIndex }; - if(g_PresentQueueFamilyIndex != g_GraphicsQueueFamilyIndex) - { - swapChainInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - swapChainInfo.queueFamilyIndexCount = 2; - swapChainInfo.pQueueFamilyIndices = queueFamilyIndices; - } - else - { - swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - } - - VkSwapchainKHR hNewSwapchain = VK_NULL_HANDLE; - ERR_GUARD_VULKAN( vkCreateSwapchainKHR(g_hDevice, &swapChainInfo, g_Allocs, &hNewSwapchain) ); - if(g_hSwapchain != VK_NULL_HANDLE) - vkDestroySwapchainKHR(g_hDevice, g_hSwapchain, g_Allocs); - g_hSwapchain = hNewSwapchain; - - // Retrieve swapchain images. - - uint32_t swapchainImageCount = 0; - ERR_GUARD_VULKAN( vkGetSwapchainImagesKHR(g_hDevice, g_hSwapchain, &swapchainImageCount, nullptr) ); - g_SwapchainImages.resize(swapchainImageCount); - ERR_GUARD_VULKAN( vkGetSwapchainImagesKHR(g_hDevice, g_hSwapchain, &swapchainImageCount, g_SwapchainImages.data()) ); - - // Create swapchain image views. - - for(size_t i = g_SwapchainImageViews.size(); i--; ) - vkDestroyImageView(g_hDevice, g_SwapchainImageViews[i], g_Allocs); - g_SwapchainImageViews.clear(); - - VkImageViewCreateInfo swapchainImageViewInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; - g_SwapchainImageViews.resize(swapchainImageCount); - for(uint32_t i = 0; i < swapchainImageCount; ++i) - { - swapchainImageViewInfo.image = g_SwapchainImages[i]; - swapchainImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - swapchainImageViewInfo.format = g_SurfaceFormat.format; - swapchainImageViewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - swapchainImageViewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - swapchainImageViewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - swapchainImageViewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - swapchainImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - swapchainImageViewInfo.subresourceRange.baseMipLevel = 0; - swapchainImageViewInfo.subresourceRange.levelCount = 1; - swapchainImageViewInfo.subresourceRange.baseArrayLayer = 0; - swapchainImageViewInfo.subresourceRange.layerCount = 1; - ERR_GUARD_VULKAN( vkCreateImageView(g_hDevice, &swapchainImageViewInfo, g_Allocs, &g_SwapchainImageViews[i]) ); - } - - // Create depth buffer - - g_DepthFormat = FindDepthFormat(); - assert(g_DepthFormat != VK_FORMAT_UNDEFINED); - - VkImageCreateInfo depthImageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - depthImageInfo.imageType = VK_IMAGE_TYPE_2D; - depthImageInfo.extent.width = g_Extent.width; - depthImageInfo.extent.height = g_Extent.height; - depthImageInfo.extent.depth = 1; - depthImageInfo.mipLevels = 1; - depthImageInfo.arrayLayers = 1; - depthImageInfo.format = g_DepthFormat; - depthImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - depthImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - depthImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; - depthImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - depthImageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - depthImageInfo.flags = 0; - - VmaAllocationCreateInfo depthImageAllocCreateInfo = {}; - depthImageAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - ERR_GUARD_VULKAN( vmaCreateImage(g_hAllocator, &depthImageInfo, &depthImageAllocCreateInfo, &g_hDepthImage, &g_hDepthImageAlloc, nullptr) ); - - VkImageViewCreateInfo depthImageViewInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; - depthImageViewInfo.image = g_hDepthImage; - depthImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - depthImageViewInfo.format = g_DepthFormat; - depthImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - depthImageViewInfo.subresourceRange.baseMipLevel = 0; - depthImageViewInfo.subresourceRange.levelCount = 1; - depthImageViewInfo.subresourceRange.baseArrayLayer = 0; - depthImageViewInfo.subresourceRange.layerCount = 1; - - ERR_GUARD_VULKAN( vkCreateImageView(g_hDevice, &depthImageViewInfo, g_Allocs, &g_hDepthImageView) ); - - // Create pipeline layout - { - if(g_hPipelineLayout != VK_NULL_HANDLE) - { - vkDestroyPipelineLayout(g_hDevice, g_hPipelineLayout, g_Allocs); - g_hPipelineLayout = VK_NULL_HANDLE; - } - - VkPushConstantRange pushConstantRanges[1]; - ZeroMemory(&pushConstantRanges, sizeof pushConstantRanges); - pushConstantRanges[0].offset = 0; - pushConstantRanges[0].size = sizeof(UniformBufferObject); - pushConstantRanges[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - - VkDescriptorSetLayout descriptorSetLayouts[] = { g_hDescriptorSetLayout }; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; - pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = descriptorSetLayouts; - pipelineLayoutInfo.pushConstantRangeCount = 1; - pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges; - ERR_GUARD_VULKAN( vkCreatePipelineLayout(g_hDevice, &pipelineLayoutInfo, g_Allocs, &g_hPipelineLayout) ); - } - - // Create render pass - { - if(g_hRenderPass != VK_NULL_HANDLE) - { - vkDestroyRenderPass(g_hDevice, g_hRenderPass, g_Allocs); - g_hRenderPass = VK_NULL_HANDLE; - } - - VkAttachmentDescription attachments[2]; - ZeroMemory(attachments, sizeof(attachments)); - - attachments[0].format = g_SurfaceFormat.format; - attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; - attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - attachments[1].format = g_DepthFormat; - attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkAttachmentReference colorAttachmentRef = {}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkAttachmentReference depthStencilAttachmentRef = {}; - depthStencilAttachmentRef.attachment = 1; - depthStencilAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpassDesc = {}; - subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpassDesc.colorAttachmentCount = 1; - subpassDesc.pColorAttachments = &colorAttachmentRef; - subpassDesc.pDepthStencilAttachment = &depthStencilAttachmentRef; - - VkRenderPassCreateInfo renderPassInfo = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; - renderPassInfo.attachmentCount = (uint32_t)_countof(attachments); - renderPassInfo.pAttachments = attachments; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpassDesc; - renderPassInfo.dependencyCount = 0; - ERR_GUARD_VULKAN( vkCreateRenderPass(g_hDevice, &renderPassInfo, g_Allocs, &g_hRenderPass) ); - } - - // Create pipeline - { - std::vector<char> vertShaderCode; - LoadShader(vertShaderCode, "Shader.vert.spv"); - VkShaderModuleCreateInfo shaderModuleInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; - shaderModuleInfo.codeSize = vertShaderCode.size(); - shaderModuleInfo.pCode = (const uint32_t*)vertShaderCode.data(); - VkShaderModule hVertShaderModule = VK_NULL_HANDLE; - ERR_GUARD_VULKAN( vkCreateShaderModule(g_hDevice, &shaderModuleInfo, g_Allocs, &hVertShaderModule) ); - - std::vector<char> hFragShaderCode; - LoadShader(hFragShaderCode, "Shader.frag.spv"); - shaderModuleInfo.codeSize = hFragShaderCode.size(); - shaderModuleInfo.pCode = (const uint32_t*)hFragShaderCode.data(); - VkShaderModule fragShaderModule = VK_NULL_HANDLE; - ERR_GUARD_VULKAN( vkCreateShaderModule(g_hDevice, &shaderModuleInfo, g_Allocs, &fragShaderModule) ); - - VkPipelineShaderStageCreateInfo vertPipelineShaderStageInfo = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }; - vertPipelineShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; - vertPipelineShaderStageInfo.module = hVertShaderModule; - vertPipelineShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo fragPipelineShaderStageInfo = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }; - fragPipelineShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - fragPipelineShaderStageInfo.module = fragShaderModule; - fragPipelineShaderStageInfo.pName = "main"; - - VkPipelineShaderStageCreateInfo pipelineShaderStageInfos[] = { - vertPipelineShaderStageInfo, - fragPipelineShaderStageInfo - }; - - VkVertexInputBindingDescription bindingDescription = {}; - bindingDescription.binding = 0; - bindingDescription.stride = sizeof(Vertex); - bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - - VkVertexInputAttributeDescription attributeDescriptions[3]; - ZeroMemory(attributeDescriptions, sizeof(attributeDescriptions)); - - attributeDescriptions[0].binding = 0; - attributeDescriptions[0].location = 0; - attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; - attributeDescriptions[0].offset = offsetof(Vertex, pos); - - attributeDescriptions[1].binding = 0; - attributeDescriptions[1].location = 1; - attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; - attributeDescriptions[1].offset = offsetof(Vertex, color); - - attributeDescriptions[2].binding = 0; - attributeDescriptions[2].location = 2; - attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; - attributeDescriptions[2].offset = offsetof(Vertex, texCoord); - - VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; - pipelineVertexInputStateInfo.vertexBindingDescriptionCount = 1; - pipelineVertexInputStateInfo.pVertexBindingDescriptions = &bindingDescription; - pipelineVertexInputStateInfo.vertexAttributeDescriptionCount = _countof(attributeDescriptions); - pipelineVertexInputStateInfo.pVertexAttributeDescriptions = attributeDescriptions; - - VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; - pipelineInputAssemblyStateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; - pipelineInputAssemblyStateInfo.primitiveRestartEnable = VK_TRUE; - - VkViewport viewport = {}; - viewport.x = 0.f; - viewport.y = 0.f; - viewport.width = (float)g_Extent.width; - viewport.height = (float)g_Extent.height; - viewport.minDepth = 0.f; - viewport.maxDepth = 1.f; - - VkRect2D scissor = {}; - scissor.offset.x = 0; - scissor.offset.y = 0; - scissor.extent = g_Extent; - - VkPipelineViewportStateCreateInfo pipelineViewportStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; - pipelineViewportStateInfo.viewportCount = 1; - pipelineViewportStateInfo.pViewports = &viewport; - pipelineViewportStateInfo.scissorCount = 1; - pipelineViewportStateInfo.pScissors = &scissor; - - VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; - pipelineRasterizationStateInfo.depthClampEnable = VK_FALSE; - pipelineRasterizationStateInfo.rasterizerDiscardEnable = VK_FALSE; - pipelineRasterizationStateInfo.polygonMode = VK_POLYGON_MODE_FILL; - pipelineRasterizationStateInfo.lineWidth = 1.f; - pipelineRasterizationStateInfo.cullMode = VK_CULL_MODE_BACK_BIT; - pipelineRasterizationStateInfo.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; - pipelineRasterizationStateInfo.depthBiasEnable = VK_FALSE; - pipelineRasterizationStateInfo.depthBiasConstantFactor = 0.f; - pipelineRasterizationStateInfo.depthBiasClamp = 0.f; - pipelineRasterizationStateInfo.depthBiasSlopeFactor = 0.f; - - VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; - pipelineMultisampleStateInfo.sampleShadingEnable = VK_FALSE; - pipelineMultisampleStateInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - pipelineMultisampleStateInfo.minSampleShading = 1.f; - pipelineMultisampleStateInfo.pSampleMask = nullptr; - pipelineMultisampleStateInfo.alphaToCoverageEnable = VK_FALSE; - pipelineMultisampleStateInfo.alphaToOneEnable = VK_FALSE; - - VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState = {}; - pipelineColorBlendAttachmentState.colorWriteMask = - VK_COLOR_COMPONENT_R_BIT | - VK_COLOR_COMPONENT_G_BIT | - VK_COLOR_COMPONENT_B_BIT | - VK_COLOR_COMPONENT_A_BIT; - pipelineColorBlendAttachmentState.blendEnable = VK_FALSE; - pipelineColorBlendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional - pipelineColorBlendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional - pipelineColorBlendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; // Optional - pipelineColorBlendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional - pipelineColorBlendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional - pipelineColorBlendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; // Optional - - VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; - pipelineColorBlendStateInfo.logicOpEnable = VK_FALSE; - pipelineColorBlendStateInfo.logicOp = VK_LOGIC_OP_COPY; - pipelineColorBlendStateInfo.attachmentCount = 1; - pipelineColorBlendStateInfo.pAttachments = &pipelineColorBlendAttachmentState; - - VkPipelineDepthStencilStateCreateInfo depthStencilStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; - depthStencilStateInfo.depthTestEnable = VK_TRUE; - depthStencilStateInfo.depthWriteEnable = VK_TRUE; - depthStencilStateInfo.depthCompareOp = VK_COMPARE_OP_LESS; - depthStencilStateInfo.depthBoundsTestEnable = VK_FALSE; - depthStencilStateInfo.stencilTestEnable = VK_FALSE; - - VkGraphicsPipelineCreateInfo pipelineInfo = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; - pipelineInfo.stageCount = 2; - pipelineInfo.pStages = pipelineShaderStageInfos; - pipelineInfo.pVertexInputState = &pipelineVertexInputStateInfo; - pipelineInfo.pInputAssemblyState = &pipelineInputAssemblyStateInfo; - pipelineInfo.pViewportState = &pipelineViewportStateInfo; - pipelineInfo.pRasterizationState = &pipelineRasterizationStateInfo; - pipelineInfo.pMultisampleState = &pipelineMultisampleStateInfo; - pipelineInfo.pDepthStencilState = &depthStencilStateInfo; - pipelineInfo.pColorBlendState = &pipelineColorBlendStateInfo; - pipelineInfo.pDynamicState = nullptr; - pipelineInfo.layout = g_hPipelineLayout; - pipelineInfo.renderPass = g_hRenderPass; - pipelineInfo.subpass = 0; - pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - pipelineInfo.basePipelineIndex = -1; - ERR_GUARD_VULKAN( vkCreateGraphicsPipelines( - g_hDevice, - VK_NULL_HANDLE, - 1, - &pipelineInfo, - g_Allocs, - &g_hPipeline) ); - - vkDestroyShaderModule(g_hDevice, fragShaderModule, g_Allocs); - vkDestroyShaderModule(g_hDevice, hVertShaderModule, g_Allocs); - } - - // Create frambuffers - - for(size_t i = g_Framebuffers.size(); i--; ) - vkDestroyFramebuffer(g_hDevice, g_Framebuffers[i], g_Allocs); - g_Framebuffers.clear(); - - g_Framebuffers.resize(g_SwapchainImageViews.size()); - for(size_t i = 0; i < g_SwapchainImages.size(); ++i) - { - VkImageView attachments[] = { g_SwapchainImageViews[i], g_hDepthImageView }; - - VkFramebufferCreateInfo framebufferInfo = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; - framebufferInfo.renderPass = g_hRenderPass; - framebufferInfo.attachmentCount = (uint32_t)_countof(attachments); - framebufferInfo.pAttachments = attachments; - framebufferInfo.width = g_Extent.width; - framebufferInfo.height = g_Extent.height; - framebufferInfo.layers = 1; - ERR_GUARD_VULKAN( vkCreateFramebuffer(g_hDevice, &framebufferInfo, g_Allocs, &g_Framebuffers[i]) ); - } - - // Create semaphores - - if(g_hImageAvailableSemaphore != VK_NULL_HANDLE) - { - vkDestroySemaphore(g_hDevice, g_hImageAvailableSemaphore, g_Allocs); - g_hImageAvailableSemaphore = VK_NULL_HANDLE; - } - if(g_hRenderFinishedSemaphore != VK_NULL_HANDLE) - { - vkDestroySemaphore(g_hDevice, g_hRenderFinishedSemaphore, g_Allocs); - g_hRenderFinishedSemaphore = VK_NULL_HANDLE; - } - - VkSemaphoreCreateInfo semaphoreInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; - ERR_GUARD_VULKAN( vkCreateSemaphore(g_hDevice, &semaphoreInfo, g_Allocs, &g_hImageAvailableSemaphore) ); - ERR_GUARD_VULKAN( vkCreateSemaphore(g_hDevice, &semaphoreInfo, g_Allocs, &g_hRenderFinishedSemaphore) ); -} - -static void DestroySwapchain(bool destroyActualSwapchain) -{ - if(g_hImageAvailableSemaphore != VK_NULL_HANDLE) - { - vkDestroySemaphore(g_hDevice, g_hImageAvailableSemaphore, g_Allocs); - g_hImageAvailableSemaphore = VK_NULL_HANDLE; - } - if(g_hRenderFinishedSemaphore != VK_NULL_HANDLE) - { - vkDestroySemaphore(g_hDevice, g_hRenderFinishedSemaphore, g_Allocs); - g_hRenderFinishedSemaphore = VK_NULL_HANDLE; - } - - for(size_t i = g_Framebuffers.size(); i--; ) - vkDestroyFramebuffer(g_hDevice, g_Framebuffers[i], g_Allocs); - g_Framebuffers.clear(); - - if(g_hDepthImageView != VK_NULL_HANDLE) - { - vkDestroyImageView(g_hDevice, g_hDepthImageView, g_Allocs); - g_hDepthImageView = VK_NULL_HANDLE; - } - if(g_hDepthImage != VK_NULL_HANDLE) - { - vmaDestroyImage(g_hAllocator, g_hDepthImage, g_hDepthImageAlloc); - g_hDepthImage = VK_NULL_HANDLE; - } - - if(g_hPipeline != VK_NULL_HANDLE) - { - vkDestroyPipeline(g_hDevice, g_hPipeline, g_Allocs); - g_hPipeline = VK_NULL_HANDLE; - } - - if(g_hRenderPass != VK_NULL_HANDLE) - { - vkDestroyRenderPass(g_hDevice, g_hRenderPass, g_Allocs); - g_hRenderPass = VK_NULL_HANDLE; - } - - if(g_hPipelineLayout != VK_NULL_HANDLE) - { - vkDestroyPipelineLayout(g_hDevice, g_hPipelineLayout, g_Allocs); - g_hPipelineLayout = VK_NULL_HANDLE; - } - - for(size_t i = g_SwapchainImageViews.size(); i--; ) - vkDestroyImageView(g_hDevice, g_SwapchainImageViews[i], g_Allocs); - g_SwapchainImageViews.clear(); - - if(destroyActualSwapchain && (g_hSwapchain != VK_NULL_HANDLE)) - { - vkDestroySwapchainKHR(g_hDevice, g_hSwapchain, g_Allocs); - g_hSwapchain = VK_NULL_HANDLE; - } -} - -static void PrintEnabledFeatures() -{ - wprintf(L"Enabled extensions and features:\n"); - wprintf(L"Validation layer: %d\n", g_EnableValidationLayer ? 1 : 0); - wprintf(L"Sparse binding: %d\n", g_SparseBindingEnabled ? 1 : 0); - if(GetVulkanApiVersion() == VK_API_VERSION_1_0) - { - wprintf(L"VK_KHR_get_memory_requirements2: %d\n", VK_KHR_get_memory_requirements2_enabled ? 1 : 0); - wprintf(L"VK_KHR_get_physical_device_properties2: %d\n", VK_KHR_get_physical_device_properties2_enabled ? 1 : 0); - wprintf(L"VK_KHR_dedicated_allocation: %d\n", VK_KHR_dedicated_allocation_enabled ? 1 : 0); - wprintf(L"VK_KHR_bind_memory2: %d\n", VK_KHR_bind_memory2_enabled ? 1 : 0); - } - wprintf(L"VK_EXT_memory_budget: %d\n", VK_EXT_memory_budget_enabled ? 1 : 0); - wprintf(L"VK_AMD_device_coherent_memory: %d\n", VK_AMD_device_coherent_memory_enabled ? 1 : 0); - if(GetVulkanApiVersion() < VK_API_VERSION_1_2) - { - wprintf(L"VK_KHR_buffer_device_address: %d\n", VK_KHR_buffer_device_address_enabled ? 1 : 0); - } - else - { - wprintf(L"bufferDeviceAddress: %d\n", VK_KHR_buffer_device_address_enabled ? 1 : 0); - } - wprintf(L"VK_EXT_memory_priority: %d\n", VK_EXT_memory_priority ? 1 : 0); -} - -void SetAllocatorCreateInfo(VmaAllocatorCreateInfo& outInfo) -{ - outInfo = {}; - - outInfo.physicalDevice = g_hPhysicalDevice; - outInfo.device = g_hDevice; - outInfo.instance = g_hVulkanInstance; - outInfo.vulkanApiVersion = GetVulkanApiVersion(); - - if(VK_KHR_dedicated_allocation_enabled) - { - outInfo.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT; - } - if(VK_KHR_bind_memory2_enabled) - { - outInfo.flags |= VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT; - } -#if !defined(VMA_MEMORY_BUDGET) || VMA_MEMORY_BUDGET == 1 - if(VK_EXT_memory_budget_enabled && ( - GetVulkanApiVersion() >= VK_API_VERSION_1_1 || VK_KHR_get_physical_device_properties2_enabled)) - { - outInfo.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; - } -#endif - if(VK_AMD_device_coherent_memory_enabled) - { - outInfo.flags |= VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT; - } - if(VK_KHR_buffer_device_address_enabled) - { - outInfo.flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; - } -#if !defined(VMA_MEMORY_PRIORITY) || VMA_MEMORY_PRIORITY == 1 - if(VK_EXT_memory_priority_enabled) - { - outInfo.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT; - } -#endif - - if(USE_CUSTOM_CPU_ALLOCATION_CALLBACKS) - { - outInfo.pAllocationCallbacks = &g_CpuAllocationCallbacks; - } - - // Uncomment to enable recording to CSV file. - /* - static VmaRecordSettings recordSettings = {}; - recordSettings.pFilePath = "VulkanSample.csv"; - outInfo.pRecordSettings = &recordSettings; - */ - - // Uncomment to enable HeapSizeLimit. - /* - static std::array<VkDeviceSize, VK_MAX_MEMORY_HEAPS> heapSizeLimit; - std::fill(heapSizeLimit.begin(), heapSizeLimit.end(), VK_WHOLE_SIZE); - heapSizeLimit[0] = 512ull * 1024 * 1024; - outInfo.pHeapSizeLimit = heapSizeLimit.data(); - */ -} - -static void PrintPhysicalDeviceProperties(const VkPhysicalDeviceProperties& properties) -{ - wprintf(L"physicalDeviceProperties:\n"); - wprintf(L" driverVersion: 0x%X\n", properties.driverVersion); - wprintf(L" vendorID: 0x%X (%s)\n", properties.vendorID, VendorIDToStr(properties.vendorID)); - wprintf(L" deviceID: 0x%X\n", properties.deviceID); - wprintf(L" deviceType: %u (%s)\n", properties.deviceType, PhysicalDeviceTypeToStr(properties.deviceType)); - wprintf(L" deviceName: %hs\n", properties.deviceName); - wprintf(L" limits:\n"); - wprintf(L" maxMemoryAllocationCount: %u\n", properties.limits.maxMemoryAllocationCount); - wprintf(L" bufferImageGranularity: %llu B\n", properties.limits.bufferImageGranularity); - wprintf(L" nonCoherentAtomSize: %llu B\n", properties.limits.nonCoherentAtomSize); -} - -#if VMA_VULKAN_VERSION >= 1002000 -static void PrintPhysicalDeviceVulkan11Properties(const VkPhysicalDeviceVulkan11Properties& properties) -{ - wprintf(L"physicalDeviceVulkan11Properties:\n"); - std::wstring sizeStr = SizeToStr(properties.maxMemoryAllocationSize); - wprintf(L" maxMemoryAllocationSize: %llu B (%s)\n", properties.maxMemoryAllocationSize, sizeStr.c_str()); -} -static void PrintPhysicalDeviceVulkan12Properties(const VkPhysicalDeviceVulkan12Properties& properties) -{ - wprintf(L"physicalDeviceVulkan12Properties:\n"); - std::wstring str = DriverIDToStr(properties.driverID); - wprintf(L" driverID: %u (%s)\n", properties.driverID, str.c_str()); - wprintf(L" driverName: %hs\n", properties.driverName); - wprintf(L" driverInfo: %hs\n", properties.driverInfo); -} -#endif // #if VMA_VULKAN_VERSION > 1002000 - -static void AddFlagToStr(std::wstring& inout, const wchar_t* flagStr) -{ - if(!inout.empty()) - inout += L", "; - inout += flagStr; -} - -static std::wstring HeapFlagsToStr(VkMemoryHeapFlags flags) -{ - std::wstring result; - if(flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) - AddFlagToStr(result, L"DEVICE_LOCAL"); - if(flags & VK_MEMORY_HEAP_MULTI_INSTANCE_BIT) - AddFlagToStr(result, L"MULTI_INSTANCE"); - return result; -} - -static std::wstring PropertyFlagsToStr(VkMemoryPropertyFlags flags) -{ - std::wstring result; - if(flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) - AddFlagToStr(result, L"DEVICE_LOCAL"); - if(flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) - AddFlagToStr(result, L"HOST_VISIBLE"); - if(flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) - AddFlagToStr(result, L"HOST_COHERENT"); - if(flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) - AddFlagToStr(result, L"HOST_CACHED"); - if(flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) - AddFlagToStr(result, L"LAZILY_ALLOCATED"); - -#if VMA_VULKAN_VERSION >= 1001000 - if(flags & VK_MEMORY_PROPERTY_PROTECTED_BIT) - AddFlagToStr(result, L"PROTECTED"); -#endif - -#if VK_AMD_device_coherent_memory - if(flags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD) - AddFlagToStr(result, L"DEVICE_COHERENT (AMD)"); - if(flags & VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD) - AddFlagToStr(result, L"DEVICE_UNCACHED (AMD)"); -#endif - - return result; -} - -static void PrintMemoryTypes() -{ - wprintf(L"MEMORY HEAPS:\n"); - const VkPhysicalDeviceMemoryProperties* memProps = nullptr; - vmaGetMemoryProperties(g_hAllocator, &memProps); - - wprintf(L"heapCount=%u, typeCount=%u\n", memProps->memoryHeapCount, memProps->memoryTypeCount); - - std::wstring sizeStr, flagsStr; - for(uint32_t heapIndex = 0; heapIndex < memProps->memoryHeapCount; ++heapIndex) - { - const VkMemoryHeap& heap = memProps->memoryHeaps[heapIndex]; - sizeStr = SizeToStr(heap.size); - flagsStr = HeapFlagsToStr(heap.flags); - wprintf(L"Heap %u: %llu B (%s) %s\n", heapIndex, heap.size, sizeStr.c_str(), flagsStr.c_str()); - - for(uint32_t typeIndex = 0; typeIndex < memProps->memoryTypeCount; ++typeIndex) - { - const VkMemoryType& type = memProps->memoryTypes[typeIndex]; - if(type.heapIndex == heapIndex) - { - flagsStr = PropertyFlagsToStr(type.propertyFlags); - wprintf(L" Type %u: %s\n", typeIndex, flagsStr.c_str()); - } - } - } -} - -#if 0 -template<typename It, typename MapFunc> -inline VkDeviceSize MapSum(It beg, It end, MapFunc mapFunc) -{ - VkDeviceSize result = 0; - for(It it = beg; it != end; ++it) - result += mapFunc(*it); - return result; -} -#endif - -static bool CanCreateVertexBuffer(uint32_t allowedMemoryTypeBits) -{ - VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufCreateInfo.size = 0x10000; - bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - - VkBuffer buf = VK_NULL_HANDLE; - VkResult res = vkCreateBuffer(g_hDevice, &bufCreateInfo, g_Allocs, &buf); - assert(res == VK_SUCCESS); - - VkMemoryRequirements memReq = {}; - vkGetBufferMemoryRequirements(g_hDevice, buf, &memReq); - - vkDestroyBuffer(g_hDevice, buf, g_Allocs); - - return (memReq.memoryTypeBits & allowedMemoryTypeBits) != 0; -} - -static bool CanCreateOptimalSampledImage(uint32_t allowedMemoryTypeBits) -{ - VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imgCreateInfo.extent.width = 256; - imgCreateInfo.extent.height = 256; - imgCreateInfo.extent.depth = 1; - imgCreateInfo.mipLevels = 1; - imgCreateInfo.arrayLayers = 1; - imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; - imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; - - VkImage img = VK_NULL_HANDLE; - VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img); - assert(res == VK_SUCCESS); - - VkMemoryRequirements memReq = {}; - vkGetImageMemoryRequirements(g_hDevice, img, &memReq); - - vkDestroyImage(g_hDevice, img, g_Allocs); - - return (memReq.memoryTypeBits & allowedMemoryTypeBits) != 0; -} - -static void PrintMemoryConclusions() -{ - wprintf(L"Conclusions:\n"); - - const VkPhysicalDeviceProperties* props = nullptr; - const VkPhysicalDeviceMemoryProperties* memProps = nullptr; - vmaGetPhysicalDeviceProperties(g_hAllocator, &props); - vmaGetMemoryProperties(g_hAllocator, &memProps); - - const uint32_t heapCount = memProps->memoryHeapCount; - - uint32_t deviceLocalHeapCount = 0; - uint32_t hostVisibleHeapCount = 0; - uint32_t deviceLocalAndHostVisibleHeapCount = 0; - VkDeviceSize deviceLocalHeapSumSize = 0; - VkDeviceSize hostVisibleHeapSumSize = 0; - VkDeviceSize deviceLocalAndHostVisibleHeapSumSize = 0; - - for(uint32_t heapIndex = 0; heapIndex < heapCount; ++heapIndex) - { - const VkMemoryHeap& heap = memProps->memoryHeaps[heapIndex]; - const bool isDeviceLocal = (heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0; - bool isHostVisible = false; - for(uint32_t typeIndex = 0; typeIndex < memProps->memoryTypeCount; ++typeIndex) - { - const VkMemoryType& type = memProps->memoryTypes[typeIndex]; - if(type.heapIndex == heapIndex && (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) - { - isHostVisible = true; - break; - } - } - if(isDeviceLocal) - { - ++deviceLocalHeapCount; - deviceLocalHeapSumSize += heap.size; - } - if(isHostVisible) - { - ++hostVisibleHeapCount; - hostVisibleHeapSumSize += heap.size; - if(isDeviceLocal) - { - ++deviceLocalAndHostVisibleHeapCount; - deviceLocalAndHostVisibleHeapSumSize += heap.size; - } - } - } - - uint32_t hostVisibleNotHostCoherentTypeCount = 0; - uint32_t notDeviceLocalNotHostVisibleTypeCount = 0; - uint32_t amdSpecificTypeCount = 0; - uint32_t lazilyAllocatedTypeCount = 0; - uint32_t allTypeBits = 0; - uint32_t deviceLocalTypeBits = 0; - for(uint32_t typeIndex = 0; typeIndex < memProps->memoryTypeCount; ++typeIndex) - { - const VkMemoryType& type = memProps->memoryTypes[typeIndex]; - allTypeBits |= 1u << typeIndex; - if(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) - { - deviceLocalTypeBits |= 1u << typeIndex; - } - if((type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) && - (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) - { - ++hostVisibleNotHostCoherentTypeCount; - } - if((type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == 0 && - (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) - { - ++notDeviceLocalNotHostVisibleTypeCount; - } - if(type.propertyFlags & (VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD | VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD)) - { - ++amdSpecificTypeCount; - } - if(type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) - { - ++lazilyAllocatedTypeCount; - } - } - - assert(deviceLocalHeapCount > 0); - if(deviceLocalHeapCount == heapCount) - wprintf(L"- All heaps are DEVICE_LOCAL.\n"); - else - wprintf(L"- %u heaps are DEVICE_LOCAL, total %s.\n", deviceLocalHeapCount, SizeToStr(deviceLocalHeapSumSize).c_str()); - - assert(hostVisibleHeapCount > 0); - if(hostVisibleHeapCount == heapCount) - wprintf(L"- All heaps are HOST_VISIBLE.\n"); - else - wprintf(L"- %u heaps are HOST_VISIBLE, total %s.\n", deviceLocalHeapCount, SizeToStr(hostVisibleHeapSumSize).c_str()); - - if(deviceLocalHeapCount < heapCount && hostVisibleHeapCount < heapCount) - { - if(deviceLocalAndHostVisibleHeapCount == 0) - wprintf(L"- No heaps are DEVICE_LOCAL and HOST_VISIBLE.\n"); - if(deviceLocalAndHostVisibleHeapCount == heapCount) - wprintf(L"- All heaps are DEVICE_LOCAL and HOST_VISIBLE.\n"); - else - wprintf(L"- %u heaps are DEVICE_LOCAL and HOST_VISIBLE, total %s.\n", deviceLocalAndHostVisibleHeapCount, SizeToStr(deviceLocalAndHostVisibleHeapSumSize).c_str()); - } - - if(hostVisibleNotHostCoherentTypeCount == 0) - wprintf(L"- No types are HOST_VISIBLE but not HOST_COHERENT.\n"); - else - wprintf(L"- %u types are HOST_VISIBLE but not HOST_COHERENT.\n", hostVisibleNotHostCoherentTypeCount); - - if(notDeviceLocalNotHostVisibleTypeCount == 0) - wprintf(L"- No types are not DEVICE_LOCAL and not HOST_VISIBLE.\n"); - else - wprintf(L"- %u types are not DEVICE_LOCAL and not HOST_VISIBLE.\n", notDeviceLocalNotHostVisibleTypeCount); - - if(amdSpecificTypeCount == 0) - wprintf(L"- No types are AMD-specific DEVICE_COHERENT or DEVICE_UNCACHED.\n"); - else - wprintf(L"- %u types are AMD-specific DEVICE_COHERENT or DEVICE_UNCACHED.\n", amdSpecificTypeCount); - - if(lazilyAllocatedTypeCount == 0) - wprintf(L"- No types are LAZILY_ALLOCATED.\n"); - else - wprintf(L"- %u types are LAZILY_ALLOCATED.\n", lazilyAllocatedTypeCount); - - if(props->vendorID == VENDOR_ID_AMD && - props->deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && - deviceLocalAndHostVisibleHeapSumSize > 256llu * 1024 * 1024) - { - wprintf(L"- AMD Smart Access Memory (SAM) is enabled!\n"); - } - - if(deviceLocalHeapCount < heapCount) - { - const uint32_t nonDeviceLocalTypeBits = ~deviceLocalTypeBits & allTypeBits; - - if(CanCreateVertexBuffer(nonDeviceLocalTypeBits)) - wprintf(L"- A buffer with VERTEX_BUFFER usage can be created in some non-DEVICE_LOCAL type.\n"); - else - wprintf(L"- A buffer with VERTEX_BUFFER usage cannot be created in some non-DEVICE_LOCAL type.\n"); - - if(CanCreateOptimalSampledImage(nonDeviceLocalTypeBits)) - wprintf(L"- An image with OPTIMAL tiling and SAMPLED usage can be created in some non-DEVICE_LOCAL type.\n"); - else - wprintf(L"- An image with OPTIMAL tiling and SAMPLED usage cannot be created in some non-DEVICE_LOCAL type.\n"); - } - - //wprintf(L"\n"); -} - -static void InitializeApplication() -{ - // Create VkSurfaceKHR. - VkWin32SurfaceCreateInfoKHR surfaceInfo = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR }; - surfaceInfo.hinstance = g_hAppInstance; - surfaceInfo.hwnd = g_hWnd; - VkResult result = vkCreateWin32SurfaceKHR(g_hVulkanInstance, &surfaceInfo, g_Allocs, &g_hSurface); - assert(result == VK_SUCCESS); - - // Query for device extensions - - uint32_t physicalDeviceExtensionPropertyCount = 0; - ERR_GUARD_VULKAN( vkEnumerateDeviceExtensionProperties(g_hPhysicalDevice, nullptr, &physicalDeviceExtensionPropertyCount, nullptr) ); - std::vector<VkExtensionProperties> physicalDeviceExtensionProperties{physicalDeviceExtensionPropertyCount}; - if(physicalDeviceExtensionPropertyCount) - { - ERR_GUARD_VULKAN( vkEnumerateDeviceExtensionProperties( - g_hPhysicalDevice, - nullptr, - &physicalDeviceExtensionPropertyCount, - physicalDeviceExtensionProperties.data()) ); - } - - for(uint32_t i = 0; i < physicalDeviceExtensionPropertyCount; ++i) - { - if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME) == 0) - { - if(GetVulkanApiVersion() == VK_API_VERSION_1_0) - { - VK_KHR_get_memory_requirements2_enabled = true; - } - } - else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME) == 0) - { - if(GetVulkanApiVersion() == VK_API_VERSION_1_0) - { - VK_KHR_dedicated_allocation_enabled = true; - } - } - else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_KHR_BIND_MEMORY_2_EXTENSION_NAME) == 0) - { - if(GetVulkanApiVersion() == VK_API_VERSION_1_0) - { - VK_KHR_bind_memory2_enabled = true; - } - } - else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) == 0) - VK_EXT_memory_budget_enabled = true; - else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME) == 0) - VK_AMD_device_coherent_memory_enabled = true; - else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) == 0) - { - if(GetVulkanApiVersion() < VK_API_VERSION_1_2) - { - VK_KHR_buffer_device_address_enabled = true; - } - } - else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) == 0) - VK_EXT_memory_priority_enabled = true; - } - - if(GetVulkanApiVersion() >= VK_API_VERSION_1_2) - VK_KHR_buffer_device_address_enabled = true; // Promoted to core Vulkan 1.2. - - // Query for features - -#if VMA_VULKAN_VERSION >= 1001000 - VkPhysicalDeviceProperties2 physicalDeviceProperties2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; - -#if VMA_VULKAN_VERSION >= 1002000 - // Vulkan spec says structure VkPhysicalDeviceVulkan11Properties is "Provided by VK_VERSION_1_2" - is this a mistake? Assuming not... - VkPhysicalDeviceVulkan11Properties physicalDeviceVulkan11Properties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES }; - VkPhysicalDeviceVulkan12Properties physicalDeviceVulkan12Properties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES }; - PnextChainPushFront(&physicalDeviceProperties2, &physicalDeviceVulkan11Properties); - PnextChainPushFront(&physicalDeviceProperties2, &physicalDeviceVulkan12Properties); -#endif - - vkGetPhysicalDeviceProperties2(g_hPhysicalDevice, &physicalDeviceProperties2); - - PrintPhysicalDeviceProperties(physicalDeviceProperties2.properties); -#if VMA_VULKAN_VERSION >= 1002000 - PrintPhysicalDeviceVulkan11Properties(physicalDeviceVulkan11Properties); - PrintPhysicalDeviceVulkan12Properties(physicalDeviceVulkan12Properties); -#endif - -#else // #if VMA_VULKAN_VERSION >= 1001000 - VkPhysicalDeviceProperties physicalDeviceProperties = {}; - vkGetPhysicalDeviceProperties(g_hPhysicalDevice, &physicalDeviceProperties); - PrintPhysicalDeviceProperties(physicalDeviceProperties); - -#endif // #if VMA_VULKAN_VERSION >= 1001000 - - wprintf(L"\n"); - - VkPhysicalDeviceFeatures2 physicalDeviceFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; - - VkPhysicalDeviceCoherentMemoryFeaturesAMD physicalDeviceCoherentMemoryFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COHERENT_MEMORY_FEATURES_AMD }; - if(VK_AMD_device_coherent_memory_enabled) - { - PnextChainPushFront(&physicalDeviceFeatures, &physicalDeviceCoherentMemoryFeatures); - } - - VkPhysicalDeviceBufferDeviceAddressFeaturesKHR physicalDeviceBufferDeviceAddressFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR }; - if(VK_KHR_buffer_device_address_enabled) - { - PnextChainPushFront(&physicalDeviceFeatures, &physicalDeviceBufferDeviceAddressFeatures); - } - - VkPhysicalDeviceMemoryPriorityFeaturesEXT physicalDeviceMemoryPriorityFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT }; - if(VK_EXT_memory_priority_enabled) - { - PnextChainPushFront(&physicalDeviceFeatures, &physicalDeviceMemoryPriorityFeatures); - } - - vkGetPhysicalDeviceFeatures2(g_hPhysicalDevice, &physicalDeviceFeatures); - - g_SparseBindingEnabled = physicalDeviceFeatures.features.sparseBinding != 0; - - // The extension is supported as fake with no real support for this feature? Don't use it. - if(VK_AMD_device_coherent_memory_enabled && !physicalDeviceCoherentMemoryFeatures.deviceCoherentMemory) - VK_AMD_device_coherent_memory_enabled = false; - if(VK_KHR_buffer_device_address_enabled && !physicalDeviceBufferDeviceAddressFeatures.bufferDeviceAddress) - VK_KHR_buffer_device_address_enabled = false; - if(VK_EXT_memory_priority_enabled && !physicalDeviceMemoryPriorityFeatures.memoryPriority) - VK_EXT_memory_priority_enabled = false; - - // Find queue family index - - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(g_hPhysicalDevice, &queueFamilyCount, nullptr); - assert(queueFamilyCount > 0); - std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(g_hPhysicalDevice, &queueFamilyCount, queueFamilies.data()); - for(uint32_t i = 0; - (i < queueFamilyCount) && - (g_GraphicsQueueFamilyIndex == UINT_MAX || - g_PresentQueueFamilyIndex == UINT_MAX || - (g_SparseBindingEnabled && g_SparseBindingQueueFamilyIndex == UINT_MAX)); - ++i) - { - if(queueFamilies[i].queueCount > 0) - { - const uint32_t flagsForGraphicsQueue = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT; - if((g_GraphicsQueueFamilyIndex != 0) && - ((queueFamilies[i].queueFlags & flagsForGraphicsQueue) == flagsForGraphicsQueue)) - { - g_GraphicsQueueFamilyIndex = i; - } - - VkBool32 surfaceSupported = 0; - VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR(g_hPhysicalDevice, i, g_hSurface, &surfaceSupported); - if((res >= 0) && (surfaceSupported == VK_TRUE)) - { - g_PresentQueueFamilyIndex = i; - } - - if(g_SparseBindingEnabled && - g_SparseBindingQueueFamilyIndex == UINT32_MAX && - (queueFamilies[i].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0) - { - g_SparseBindingQueueFamilyIndex = i; - } - } - } - assert(g_GraphicsQueueFamilyIndex != UINT_MAX); - - g_SparseBindingEnabled = g_SparseBindingEnabled && g_SparseBindingQueueFamilyIndex != UINT32_MAX; - - // Create logical device - - const float queuePriority = 1.f; - - VkDeviceQueueCreateInfo queueCreateInfo[3] = {}; - uint32_t queueCount = 1; - queueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo[0].queueFamilyIndex = g_GraphicsQueueFamilyIndex; - queueCreateInfo[0].queueCount = 1; - queueCreateInfo[0].pQueuePriorities = &queuePriority; - - if(g_PresentQueueFamilyIndex != g_GraphicsQueueFamilyIndex) - { - - queueCreateInfo[queueCount].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo[queueCount].queueFamilyIndex = g_PresentQueueFamilyIndex; - queueCreateInfo[queueCount].queueCount = 1; - queueCreateInfo[queueCount].pQueuePriorities = &queuePriority; - ++queueCount; - } - - if(g_SparseBindingEnabled && - g_SparseBindingQueueFamilyIndex != g_GraphicsQueueFamilyIndex && - g_SparseBindingQueueFamilyIndex != g_PresentQueueFamilyIndex) - { - - queueCreateInfo[queueCount].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo[queueCount].queueFamilyIndex = g_SparseBindingQueueFamilyIndex; - queueCreateInfo[queueCount].queueCount = 1; - queueCreateInfo[queueCount].pQueuePriorities = &queuePriority; - ++queueCount; - } - - std::vector<const char*> enabledDeviceExtensions; - enabledDeviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); - if(VK_KHR_get_memory_requirements2_enabled) - enabledDeviceExtensions.push_back(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME); - if(VK_KHR_dedicated_allocation_enabled) - enabledDeviceExtensions.push_back(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); - if(VK_KHR_bind_memory2_enabled) - enabledDeviceExtensions.push_back(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME); - if(VK_EXT_memory_budget_enabled) - enabledDeviceExtensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); - if(VK_AMD_device_coherent_memory_enabled) - enabledDeviceExtensions.push_back(VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME); - if(VK_KHR_buffer_device_address_enabled && GetVulkanApiVersion() < VK_API_VERSION_1_2) - enabledDeviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); - if(VK_EXT_memory_priority_enabled) - enabledDeviceExtensions.push_back(VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME); - - VkPhysicalDeviceFeatures2 deviceFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; - deviceFeatures.features.samplerAnisotropy = VK_TRUE; - deviceFeatures.features.sparseBinding = g_SparseBindingEnabled ? VK_TRUE : VK_FALSE; - - if(VK_AMD_device_coherent_memory_enabled) - { - physicalDeviceCoherentMemoryFeatures.deviceCoherentMemory = VK_TRUE; - PnextChainPushBack(&deviceFeatures, &physicalDeviceCoherentMemoryFeatures); - } - if(VK_KHR_buffer_device_address_enabled) - { - physicalDeviceBufferDeviceAddressFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR }; - physicalDeviceBufferDeviceAddressFeatures.bufferDeviceAddress = VK_TRUE; - PnextChainPushBack(&deviceFeatures, &physicalDeviceBufferDeviceAddressFeatures); - } - if(VK_EXT_memory_priority_enabled) - { - PnextChainPushBack(&deviceFeatures, &physicalDeviceMemoryPriorityFeatures); - } - - VkDeviceCreateInfo deviceCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; - deviceCreateInfo.pNext = &deviceFeatures; - deviceCreateInfo.enabledLayerCount = 0; - deviceCreateInfo.ppEnabledLayerNames = nullptr; - deviceCreateInfo.enabledExtensionCount = (uint32_t)enabledDeviceExtensions.size(); - deviceCreateInfo.ppEnabledExtensionNames = !enabledDeviceExtensions.empty() ? enabledDeviceExtensions.data() : nullptr; - deviceCreateInfo.queueCreateInfoCount = queueCount; - deviceCreateInfo.pQueueCreateInfos = queueCreateInfo; - - ERR_GUARD_VULKAN( vkCreateDevice(g_hPhysicalDevice, &deviceCreateInfo, g_Allocs, &g_hDevice) ); - - // Fetch pointers to extension functions - if(VK_KHR_buffer_device_address_enabled) - { - if(GetVulkanApiVersion() >= VK_API_VERSION_1_2) - { - g_vkGetBufferDeviceAddressKHR = (PFN_vkGetBufferDeviceAddressEXT)vkGetDeviceProcAddr(g_hDevice, "vkGetBufferDeviceAddress"); - } - else if(VK_KHR_buffer_device_address_enabled) - { - g_vkGetBufferDeviceAddressKHR = (PFN_vkGetBufferDeviceAddressEXT)vkGetDeviceProcAddr(g_hDevice, "vkGetBufferDeviceAddressKHR"); - } - assert(g_vkGetBufferDeviceAddressKHR != nullptr); - } - - // Create memory allocator - - VmaAllocatorCreateInfo allocatorInfo = {}; - SetAllocatorCreateInfo(allocatorInfo); - ERR_GUARD_VULKAN( vmaCreateAllocator(&allocatorInfo, &g_hAllocator) ); - - PrintMemoryTypes(); - wprintf(L"\n"); - PrintMemoryConclusions(); - wprintf(L"\n"); - PrintEnabledFeatures(); - wprintf(L"\n"); - - // Retrieve queues (don't need to be destroyed). - - vkGetDeviceQueue(g_hDevice, g_GraphicsQueueFamilyIndex, 0, &g_hGraphicsQueue); - vkGetDeviceQueue(g_hDevice, g_PresentQueueFamilyIndex, 0, &g_hPresentQueue); - assert(g_hGraphicsQueue); - assert(g_hPresentQueue); - - if(g_SparseBindingEnabled) - { - vkGetDeviceQueue(g_hDevice, g_SparseBindingQueueFamilyIndex, 0, &g_hSparseBindingQueue); - assert(g_hSparseBindingQueue); - } - - // Create command pool - - VkCommandPoolCreateInfo commandPoolInfo = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; - commandPoolInfo.queueFamilyIndex = g_GraphicsQueueFamilyIndex; - commandPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - ERR_GUARD_VULKAN( vkCreateCommandPool(g_hDevice, &commandPoolInfo, g_Allocs, &g_hCommandPool) ); - - VkCommandBufferAllocateInfo commandBufferInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; - commandBufferInfo.commandPool = g_hCommandPool; - commandBufferInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - commandBufferInfo.commandBufferCount = COMMAND_BUFFER_COUNT; - ERR_GUARD_VULKAN( vkAllocateCommandBuffers(g_hDevice, &commandBufferInfo, g_MainCommandBuffers) ); - - VkFenceCreateInfo fenceInfo = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; - fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - for(size_t i = 0; i < COMMAND_BUFFER_COUNT; ++i) - { - ERR_GUARD_VULKAN( vkCreateFence(g_hDevice, &fenceInfo, g_Allocs, &g_MainCommandBufferExecutedFances[i]) ); - } - - ERR_GUARD_VULKAN( vkCreateFence(g_hDevice, &fenceInfo, g_Allocs, &g_ImmediateFence) ); - - commandBufferInfo.commandBufferCount = 1; - ERR_GUARD_VULKAN( vkAllocateCommandBuffers(g_hDevice, &commandBufferInfo, &g_hTemporaryCommandBuffer) ); - - // Create texture sampler - - VkSamplerCreateInfo samplerInfo = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; - samplerInfo.magFilter = VK_FILTER_LINEAR; - samplerInfo.minFilter = VK_FILTER_LINEAR; - samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.anisotropyEnable = VK_TRUE; - samplerInfo.maxAnisotropy = 16; - samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; - samplerInfo.unnormalizedCoordinates = VK_FALSE; - samplerInfo.compareEnable = VK_FALSE; - samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; - samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - samplerInfo.mipLodBias = 0.f; - samplerInfo.minLod = 0.f; - samplerInfo.maxLod = FLT_MAX; - ERR_GUARD_VULKAN( vkCreateSampler(g_hDevice, &samplerInfo, g_Allocs, &g_hSampler) ); - - CreateTexture(128, 128); - CreateMesh(); - - VkDescriptorSetLayoutBinding samplerLayoutBinding = {}; - samplerLayoutBinding.binding = 1; - samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - samplerLayoutBinding.descriptorCount = 1; - samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - - VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; - descriptorSetLayoutInfo.bindingCount = 1; - descriptorSetLayoutInfo.pBindings = &samplerLayoutBinding; - ERR_GUARD_VULKAN( vkCreateDescriptorSetLayout(g_hDevice, &descriptorSetLayoutInfo, g_Allocs, &g_hDescriptorSetLayout) ); - - // Create descriptor pool - - VkDescriptorPoolSize descriptorPoolSizes[2]; - ZeroMemory(descriptorPoolSizes, sizeof(descriptorPoolSizes)); - descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptorPoolSizes[0].descriptorCount = 1; - descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - descriptorPoolSizes[1].descriptorCount = 1; - - VkDescriptorPoolCreateInfo descriptorPoolInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; - descriptorPoolInfo.poolSizeCount = (uint32_t)_countof(descriptorPoolSizes); - descriptorPoolInfo.pPoolSizes = descriptorPoolSizes; - descriptorPoolInfo.maxSets = 1; - ERR_GUARD_VULKAN( vkCreateDescriptorPool(g_hDevice, &descriptorPoolInfo, g_Allocs, &g_hDescriptorPool) ); - - // Create descriptor set layout - - VkDescriptorSetLayout descriptorSetLayouts[] = { g_hDescriptorSetLayout }; - VkDescriptorSetAllocateInfo descriptorSetInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; - descriptorSetInfo.descriptorPool = g_hDescriptorPool; - descriptorSetInfo.descriptorSetCount = 1; - descriptorSetInfo.pSetLayouts = descriptorSetLayouts; - ERR_GUARD_VULKAN( vkAllocateDescriptorSets(g_hDevice, &descriptorSetInfo, &g_hDescriptorSet) ); - - VkDescriptorImageInfo descriptorImageInfo = {}; - descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - descriptorImageInfo.imageView = g_hTextureImageView; - descriptorImageInfo.sampler = g_hSampler; - - VkWriteDescriptorSet writeDescriptorSet = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; - writeDescriptorSet.dstSet = g_hDescriptorSet; - writeDescriptorSet.dstBinding = 1; - writeDescriptorSet.dstArrayElement = 0; - writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - writeDescriptorSet.descriptorCount = 1; - writeDescriptorSet.pImageInfo = &descriptorImageInfo; - - vkUpdateDescriptorSets(g_hDevice, 1, &writeDescriptorSet, 0, nullptr); - - CreateSwapchain(); -} - -static void FinalizeApplication() -{ - vkDeviceWaitIdle(g_hDevice); - - DestroySwapchain(true); - - if(g_hDescriptorPool != VK_NULL_HANDLE) - { - vkDestroyDescriptorPool(g_hDevice, g_hDescriptorPool, g_Allocs); - g_hDescriptorPool = VK_NULL_HANDLE; - } - - if(g_hDescriptorSetLayout != VK_NULL_HANDLE) - { - vkDestroyDescriptorSetLayout(g_hDevice, g_hDescriptorSetLayout, g_Allocs); - g_hDescriptorSetLayout = VK_NULL_HANDLE; - } - - if(g_hTextureImageView != VK_NULL_HANDLE) - { - vkDestroyImageView(g_hDevice, g_hTextureImageView, g_Allocs); - g_hTextureImageView = VK_NULL_HANDLE; - } - if(g_hTextureImage != VK_NULL_HANDLE) - { - vmaDestroyImage(g_hAllocator, g_hTextureImage, g_hTextureImageAlloc); - g_hTextureImage = VK_NULL_HANDLE; - } - - if(g_hIndexBuffer != VK_NULL_HANDLE) - { - vmaDestroyBuffer(g_hAllocator, g_hIndexBuffer, g_hIndexBufferAlloc); - g_hIndexBuffer = VK_NULL_HANDLE; - } - if(g_hVertexBuffer != VK_NULL_HANDLE) - { - vmaDestroyBuffer(g_hAllocator, g_hVertexBuffer, g_hVertexBufferAlloc); - g_hVertexBuffer = VK_NULL_HANDLE; - } - - if(g_hSampler != VK_NULL_HANDLE) - { - vkDestroySampler(g_hDevice, g_hSampler, g_Allocs); - g_hSampler = VK_NULL_HANDLE; - } - - if(g_ImmediateFence) - { - vkDestroyFence(g_hDevice, g_ImmediateFence, g_Allocs); - g_ImmediateFence = VK_NULL_HANDLE; - } - - for(size_t i = COMMAND_BUFFER_COUNT; i--; ) - { - if(g_MainCommandBufferExecutedFances[i] != VK_NULL_HANDLE) - { - vkDestroyFence(g_hDevice, g_MainCommandBufferExecutedFances[i], g_Allocs); - g_MainCommandBufferExecutedFances[i] = VK_NULL_HANDLE; - } - } - if(g_MainCommandBuffers[0] != VK_NULL_HANDLE) - { - vkFreeCommandBuffers(g_hDevice, g_hCommandPool, COMMAND_BUFFER_COUNT, g_MainCommandBuffers); - ZeroMemory(g_MainCommandBuffers, sizeof(g_MainCommandBuffers)); - } - if(g_hTemporaryCommandBuffer != VK_NULL_HANDLE) - { - vkFreeCommandBuffers(g_hDevice, g_hCommandPool, 1, &g_hTemporaryCommandBuffer); - g_hTemporaryCommandBuffer = VK_NULL_HANDLE; - } - - if(g_hCommandPool != VK_NULL_HANDLE) - { - vkDestroyCommandPool(g_hDevice, g_hCommandPool, g_Allocs); - g_hCommandPool = VK_NULL_HANDLE; - } - - if(g_hAllocator != VK_NULL_HANDLE) - { - vmaDestroyAllocator(g_hAllocator); - g_hAllocator = nullptr; - } - - if(g_hDevice != VK_NULL_HANDLE) - { - vkDestroyDevice(g_hDevice, g_Allocs); - g_hDevice = nullptr; - } - - if(g_hSurface != VK_NULL_HANDLE) - { - vkDestroySurfaceKHR(g_hVulkanInstance, g_hSurface, g_Allocs); - g_hSurface = VK_NULL_HANDLE; - } -} - -static void PrintAllocatorStats() -{ -#if VMA_STATS_STRING_ENABLED - char* statsString = nullptr; - vmaBuildStatsString(g_hAllocator, &statsString, true); - printf("%s\n", statsString); - vmaFreeStatsString(g_hAllocator, statsString); -#endif -} - -static void RecreateSwapChain() -{ - vkDeviceWaitIdle(g_hDevice); - DestroySwapchain(false); - CreateSwapchain(); -} - -static void DrawFrame() -{ - // Begin main command buffer - size_t cmdBufIndex = (g_NextCommandBufferIndex++) % COMMAND_BUFFER_COUNT; - VkCommandBuffer hCommandBuffer = g_MainCommandBuffers[cmdBufIndex]; - VkFence hCommandBufferExecutedFence = g_MainCommandBufferExecutedFances[cmdBufIndex]; - - ERR_GUARD_VULKAN( vkWaitForFences(g_hDevice, 1, &hCommandBufferExecutedFence, VK_TRUE, UINT64_MAX) ); - ERR_GUARD_VULKAN( vkResetFences(g_hDevice, 1, &hCommandBufferExecutedFence) ); - - VkCommandBufferBeginInfo commandBufferBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - ERR_GUARD_VULKAN( vkBeginCommandBuffer(hCommandBuffer, &commandBufferBeginInfo) ); - - // Acquire swapchain image - uint32_t imageIndex = 0; - VkResult res = vkAcquireNextImageKHR(g_hDevice, g_hSwapchain, UINT64_MAX, g_hImageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); - if(res == VK_ERROR_OUT_OF_DATE_KHR) - { - RecreateSwapChain(); - return; - } - else if(res < 0) - { - ERR_GUARD_VULKAN(res); - } - - // Record geometry pass - - VkClearValue clearValues[2]; - ZeroMemory(clearValues, sizeof(clearValues)); - clearValues[0].color.float32[0] = 0.25f; - clearValues[0].color.float32[1] = 0.25f; - clearValues[0].color.float32[2] = 0.5f; - clearValues[0].color.float32[3] = 1.0f; - clearValues[1].depthStencil.depth = 1.0f; - - VkRenderPassBeginInfo renderPassBeginInfo = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; - renderPassBeginInfo.renderPass = g_hRenderPass; - renderPassBeginInfo.framebuffer = g_Framebuffers[imageIndex]; - renderPassBeginInfo.renderArea.offset.x = 0; - renderPassBeginInfo.renderArea.offset.y = 0; - renderPassBeginInfo.renderArea.extent = g_Extent; - renderPassBeginInfo.clearValueCount = (uint32_t)_countof(clearValues); - renderPassBeginInfo.pClearValues = clearValues; - vkCmdBeginRenderPass(hCommandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); - - vkCmdBindPipeline( - hCommandBuffer, - VK_PIPELINE_BIND_POINT_GRAPHICS, - g_hPipeline); - - mat4 view = mat4::LookAt( - vec3(0.f, 0.f, 0.f), - vec3(0.f, -2.f, 4.f), - vec3(0.f, 1.f, 0.f)); - mat4 proj = mat4::Perspective( - 1.0471975511966f, // 60 degrees - (float)g_Extent.width / (float)g_Extent.height, - 0.1f, - 1000.f); - mat4 viewProj = view * proj; - - vkCmdBindDescriptorSets( - hCommandBuffer, - VK_PIPELINE_BIND_POINT_GRAPHICS, - g_hPipelineLayout, - 0, - 1, - &g_hDescriptorSet, - 0, - nullptr); - - float rotationAngle = (float)GetTickCount() * 0.001f * (float)PI * 0.2f; - mat4 model = mat4::RotationY(rotationAngle); - - UniformBufferObject ubo = {}; - ubo.ModelViewProj = model * viewProj; - vkCmdPushConstants(hCommandBuffer, g_hPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(UniformBufferObject), &ubo); - - VkBuffer vertexBuffers[] = { g_hVertexBuffer }; - VkDeviceSize offsets[] = { 0 }; - vkCmdBindVertexBuffers(hCommandBuffer, 0, 1, vertexBuffers, offsets); - - vkCmdBindIndexBuffer(hCommandBuffer, g_hIndexBuffer, 0, VK_INDEX_TYPE_UINT16); - - vkCmdDrawIndexed(hCommandBuffer, g_IndexCount, 1, 0, 0, 0); - - vkCmdEndRenderPass(hCommandBuffer); - - vkEndCommandBuffer(hCommandBuffer); - - // Submit command buffer - - VkSemaphore submitWaitSemaphores[] = { g_hImageAvailableSemaphore }; - VkPipelineStageFlags submitWaitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; - VkSemaphore submitSignalSemaphores[] = { g_hRenderFinishedSemaphore }; - VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitSemaphores = submitWaitSemaphores; - submitInfo.pWaitDstStageMask = submitWaitStages; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &hCommandBuffer; - submitInfo.signalSemaphoreCount = _countof(submitSignalSemaphores); - submitInfo.pSignalSemaphores = submitSignalSemaphores; - ERR_GUARD_VULKAN( vkQueueSubmit(g_hGraphicsQueue, 1, &submitInfo, hCommandBufferExecutedFence) ); - - VkSemaphore presentWaitSemaphores[] = { g_hRenderFinishedSemaphore }; - - VkSwapchainKHR swapchains[] = { g_hSwapchain }; - VkPresentInfoKHR presentInfo = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; - presentInfo.waitSemaphoreCount = _countof(presentWaitSemaphores); - presentInfo.pWaitSemaphores = presentWaitSemaphores; - presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = swapchains; - presentInfo.pImageIndices = &imageIndex; - presentInfo.pResults = nullptr; - res = vkQueuePresentKHR(g_hPresentQueue, &presentInfo); - if(res == VK_ERROR_OUT_OF_DATE_KHR) - { - RecreateSwapChain(); - } - else - ERR_GUARD_VULKAN(res); -} - -static void HandlePossibleSizeChange() -{ - RECT clientRect; - GetClientRect(g_hWnd, &clientRect); - LONG newSizeX = clientRect.right - clientRect.left; - LONG newSizeY = clientRect.bottom - clientRect.top; - if((newSizeX > 0) && - (newSizeY > 0) && - ((newSizeX != g_SizeX) || (newSizeY != g_SizeY))) - { - g_SizeX = newSizeX; - g_SizeY = newSizeY; - - RecreateSwapChain(); - } -} - -#define CATCH_PRINT_ERROR(extraCatchCode) \ - catch(const std::exception& ex) \ - { \ - fwprintf(stderr, L"ERROR: %hs\n", ex.what()); \ - extraCatchCode \ - } \ - catch(...) \ - { \ - fwprintf(stderr, L"UNKNOWN ERROR.\n"); \ - extraCatchCode \ - } - -static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch(msg) - { - case WM_CREATE: - // This is intentionally assigned here because we are now inside CreateWindow, before it returns. - g_hWnd = hWnd; - try - { - InitializeApplication(); - } - CATCH_PRINT_ERROR(return -1;) - //PrintAllocatorStats(); - return 0; - - case WM_DESTROY: - try - { - FinalizeApplication(); - } - CATCH_PRINT_ERROR(;) - PostQuitMessage(0); - return 0; - - // This prevents app from freezing when left Alt is pressed - // (which normally enters modal menu loop). - case WM_SYSKEYDOWN: - case WM_SYSKEYUP: - return 0; - - case WM_SIZE: - if((wParam == SIZE_MAXIMIZED) || (wParam == SIZE_RESTORED)) - { - try - { - HandlePossibleSizeChange(); - } - CATCH_PRINT_ERROR(DestroyWindow(hWnd);) - } - return 0; - - case WM_EXITSIZEMOVE: - try - { - HandlePossibleSizeChange(); - } - CATCH_PRINT_ERROR(DestroyWindow(hWnd);) - return 0; - - case WM_KEYDOWN: - switch(wParam) - { - case VK_ESCAPE: - PostMessage(hWnd, WM_CLOSE, 0, 0); - break; - case 'T': - try - { - Test(); - } - CATCH_PRINT_ERROR(;) - break; - case 'S': - try - { - if(g_SparseBindingEnabled) - { - try - { - TestSparseBinding(); - } - CATCH_PRINT_ERROR(;) - } - else - { - printf("Sparse binding not supported.\n"); - } - } - catch(const std::exception& ex) - { - printf("ERROR: %s\n", ex.what()); - } - break; - } - return 0; - - default: - break; - } - - return DefWindowProc(hWnd, msg, wParam, lParam); -} - -static void PrintLogo() -{ - wprintf(L"%s\n", APP_TITLE_W); -} - -static void PrintHelp() -{ - wprintf( - L"Command line syntax:\n" - L"-h, --Help Print this information\n" - L"-l, --List Print list of GPUs\n" - L"-g S, --GPU S Select GPU with name containing S\n" - L"-i N, --GPUIndex N Select GPU index N\n" - ); -} - -int MainWindow() -{ - WNDCLASSEX wndClassDesc = { sizeof(WNDCLASSEX) }; - wndClassDesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS; - wndClassDesc.hbrBackground = NULL; - wndClassDesc.hCursor = LoadCursor(NULL, IDC_CROSS); - wndClassDesc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wndClassDesc.hInstance = g_hAppInstance; - wndClassDesc.lpfnWndProc = WndProc; - wndClassDesc.lpszClassName = WINDOW_CLASS_NAME; - - const ATOM hWndClass = RegisterClassEx(&wndClassDesc); - assert(hWndClass); - - const DWORD style = WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME; - const DWORD exStyle = 0; - - RECT rect = { 0, 0, g_SizeX, g_SizeY }; - AdjustWindowRectEx(&rect, style, FALSE, exStyle); - - CreateWindowEx( - exStyle, WINDOW_CLASS_NAME, APP_TITLE_W, style, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - NULL, NULL, g_hAppInstance, NULL); - - MSG msg; - for(;;) - { - if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - if(msg.message == WM_QUIT) - break; - TranslateMessage(&msg); - DispatchMessage(&msg); - } - if(g_hDevice != VK_NULL_HANDLE) - DrawFrame(); - } - - return (int)msg.wParam;; -} - -int Main2(int argc, wchar_t** argv) -{ - PrintLogo(); - - if(!g_CommandLineParameters.Parse(argc, argv)) - { - wprintf(L"ERROR: Invalid command line syntax.\n"); - PrintHelp(); - return (int)ExitCode::CommandLineError; - } - - if(g_CommandLineParameters.m_Help) - { - PrintHelp(); - return (int)ExitCode::Help; - } - - VulkanUsage vulkanUsage; - vulkanUsage.Init(); - - if(g_CommandLineParameters.m_List) - { - vulkanUsage.PrintPhysicalDeviceList(); - return (int)ExitCode::GPUList; - } - - g_hPhysicalDevice = vulkanUsage.SelectPhysicalDevice(g_CommandLineParameters.m_GPUSelection); - TEST(g_hPhysicalDevice); - - return MainWindow(); -} - -int wmain(int argc, wchar_t** argv) -{ - try - { - return Main2(argc, argv); - TEST(g_CpuAllocCount.load() == 0); - } - CATCH_PRINT_ERROR(return (int)ExitCode::RuntimeError;) -} - -#else // #ifdef _WIN32 - -#include "VmaUsage.h" - -int main() -{ -} - -#endif // #ifdef _WIN32 +// +// Copyright (c) 2017-2021 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. +// + +#ifdef _WIN32 + +#include "SparseBindingTest.h" +#include "Tests.h" +#include "VmaUsage.h" +#include "Common.h" +#include <atomic> +#include <Shlwapi.h> + +#pragma comment(lib, "shlwapi.lib") + +static const char* const SHADER_PATH1 = "./"; +static const char* const SHADER_PATH2 = "../bin/"; +static const wchar_t* const WINDOW_CLASS_NAME = L"VULKAN_MEMORY_ALLOCATOR_SAMPLE"; +static const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; +static const char* const APP_TITLE_A = "Vulkan Memory Allocator Sample 2.4.0"; +static const wchar_t* const APP_TITLE_W = L"Vulkan Memory Allocator Sample 2.4.0"; + +static const bool VSYNC = true; +static const uint32_t COMMAND_BUFFER_COUNT = 2; +static void* const CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA = (void*)(intptr_t)43564544; +static const bool USE_CUSTOM_CPU_ALLOCATION_CALLBACKS = true; + +enum class ExitCode : int +{ + GPUList = 2, + Help = 1, + Success = 0, + RuntimeError = -1, + CommandLineError = -2, +}; + +VkPhysicalDevice g_hPhysicalDevice; +VkDevice g_hDevice; +VmaAllocator g_hAllocator; +VkInstance g_hVulkanInstance; + +bool g_EnableValidationLayer = true; +bool VK_KHR_get_memory_requirements2_enabled = false; +bool VK_KHR_get_physical_device_properties2_enabled = false; +bool VK_KHR_dedicated_allocation_enabled = false; +bool VK_KHR_bind_memory2_enabled = false; +bool VK_EXT_memory_budget_enabled = false; +bool VK_AMD_device_coherent_memory_enabled = false; +bool VK_KHR_buffer_device_address_enabled = false; +bool VK_EXT_memory_priority_enabled = false; +bool VK_EXT_debug_utils_enabled = false; +bool g_SparseBindingEnabled = false; + +// # Pointers to functions from extensions +PFN_vkGetBufferDeviceAddressKHR g_vkGetBufferDeviceAddressKHR; + +static HINSTANCE g_hAppInstance; +static HWND g_hWnd; +static LONG g_SizeX = 1280, g_SizeY = 720; +static VkSurfaceKHR g_hSurface; +static VkQueue g_hPresentQueue; +static VkSurfaceFormatKHR g_SurfaceFormat; +static VkExtent2D g_Extent; +static VkSwapchainKHR g_hSwapchain; +static std::vector<VkImage> g_SwapchainImages; +static std::vector<VkImageView> g_SwapchainImageViews; +static std::vector<VkFramebuffer> g_Framebuffers; +static VkCommandPool g_hCommandPool; +static VkCommandBuffer g_MainCommandBuffers[COMMAND_BUFFER_COUNT]; +static VkFence g_MainCommandBufferExecutedFances[COMMAND_BUFFER_COUNT]; +VkFence g_ImmediateFence; +static uint32_t g_NextCommandBufferIndex; +static VkSemaphore g_hImageAvailableSemaphore; +static VkSemaphore g_hRenderFinishedSemaphore; +static uint32_t g_GraphicsQueueFamilyIndex = UINT_MAX; +static uint32_t g_PresentQueueFamilyIndex = UINT_MAX; +static uint32_t g_SparseBindingQueueFamilyIndex = UINT_MAX; +static VkDescriptorSetLayout g_hDescriptorSetLayout; +static VkDescriptorPool g_hDescriptorPool; +static VkDescriptorSet g_hDescriptorSet; // Automatically destroyed with m_DescriptorPool. +static VkSampler g_hSampler; +static VkFormat g_DepthFormat; +static VkImage g_hDepthImage; +static VmaAllocation g_hDepthImageAlloc; +static VkImageView g_hDepthImageView; + +static VkSurfaceCapabilitiesKHR g_SurfaceCapabilities; +static std::vector<VkSurfaceFormatKHR> g_SurfaceFormats; +static std::vector<VkPresentModeKHR> g_PresentModes; + +static const VkDebugUtilsMessageSeverityFlagsEXT DEBUG_UTILS_MESSENGER_MESSAGE_SEVERITY = + //VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + //VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; +static const VkDebugUtilsMessageTypeFlagsEXT DEBUG_UTILS_MESSENGER_MESSAGE_TYPE = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; +static PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT_Func; +static PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT_Func; +static PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT_Func; + +static VkQueue g_hGraphicsQueue; +VkQueue g_hSparseBindingQueue; +VkCommandBuffer g_hTemporaryCommandBuffer; + +static VkPipelineLayout g_hPipelineLayout; +static VkRenderPass g_hRenderPass; +static VkPipeline g_hPipeline; + +static VkBuffer g_hVertexBuffer; +static VmaAllocation g_hVertexBufferAlloc; +static VkBuffer g_hIndexBuffer; +static VmaAllocation g_hIndexBufferAlloc; +static uint32_t g_VertexCount; +static uint32_t g_IndexCount; + +static VkImage g_hTextureImage; +static VmaAllocation g_hTextureImageAlloc; +static VkImageView g_hTextureImageView; + +static std::atomic_uint32_t g_CpuAllocCount; + +static void* CustomCpuAllocation( + void* pUserData, size_t size, size_t alignment, + VkSystemAllocationScope allocationScope) +{ + assert(pUserData == CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA); + void* const result = _aligned_malloc(size, alignment); + if(result) + { + ++g_CpuAllocCount; + } + return result; +} + +static void* CustomCpuReallocation( + void* pUserData, void* pOriginal, size_t size, size_t alignment, + VkSystemAllocationScope allocationScope) +{ + assert(pUserData == CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA); + void* const result = _aligned_realloc(pOriginal, size, alignment); + if(pOriginal && !result) + { + --g_CpuAllocCount; + } + else if(!pOriginal && result) + { + ++g_CpuAllocCount; + } + return result; +} + +static void CustomCpuFree(void* pUserData, void* pMemory) +{ + assert(pUserData == CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA); + if(pMemory) + { + const uint32_t oldAllocCount = g_CpuAllocCount.fetch_sub(1); + TEST(oldAllocCount > 0); + _aligned_free(pMemory); + } +} + +static const VkAllocationCallbacks g_CpuAllocationCallbacks = { + CUSTOM_CPU_ALLOCATION_CALLBACK_USER_DATA, // pUserData + &CustomCpuAllocation, // pfnAllocation + &CustomCpuReallocation, // pfnReallocation + &CustomCpuFree // pfnFree +}; + +const VkAllocationCallbacks* g_Allocs; + +struct GPUSelection +{ + uint32_t Index = UINT32_MAX; + std::wstring Substring; +}; + +class VulkanUsage +{ +public: + void Init(); + ~VulkanUsage(); + void PrintPhysicalDeviceList() const; + // If failed, returns VK_NULL_HANDLE. + VkPhysicalDevice SelectPhysicalDevice(const GPUSelection& GPUSelection) const; + +private: + VkDebugUtilsMessengerEXT m_DebugUtilsMessenger = VK_NULL_HANDLE; + + void RegisterDebugCallbacks(); + static bool IsLayerSupported(const VkLayerProperties* pProps, size_t propCount, const char* pLayerName); +}; + +struct CommandLineParameters +{ + bool m_Help = false; + bool m_List = false; + GPUSelection m_GPUSelection; + + bool Parse(int argc, wchar_t** argv) + { + for(int i = 1; i < argc; ++i) + { + if(_wcsicmp(argv[i], L"-h") == 0 || _wcsicmp(argv[i], L"--Help") == 0) + { + m_Help = true; + } + else if(_wcsicmp(argv[i], L"-l") == 0 || _wcsicmp(argv[i], L"--List") == 0) + { + m_List = true; + } + else if((_wcsicmp(argv[i], L"-g") == 0 || _wcsicmp(argv[i], L"--GPU") == 0) && i + 1 < argc) + { + m_GPUSelection.Substring = argv[i + 1]; + ++i; + } + else if((_wcsicmp(argv[i], L"-i") == 0 || _wcsicmp(argv[i], L"--GPUIndex") == 0) && i + 1 < argc) + { + m_GPUSelection.Index = _wtoi(argv[i + 1]); + ++i; + } + else + return false; + } + return true; + } +} g_CommandLineParameters; + +void SetDebugUtilsObjectName(VkObjectType type, uint64_t handle, const char* name) +{ + if(vkSetDebugUtilsObjectNameEXT_Func == nullptr) + return; + + VkDebugUtilsObjectNameInfoEXT info = { VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT }; + info.objectType = type; + info.objectHandle = handle; + info.pObjectName = name; + vkSetDebugUtilsObjectNameEXT_Func(g_hDevice, &info); +} + +void BeginSingleTimeCommands() +{ + VkCommandBufferBeginInfo cmdBufBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + cmdBufBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + ERR_GUARD_VULKAN( vkBeginCommandBuffer(g_hTemporaryCommandBuffer, &cmdBufBeginInfo) ); +} + +void EndSingleTimeCommands() +{ + ERR_GUARD_VULKAN( vkEndCommandBuffer(g_hTemporaryCommandBuffer) ); + + VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &g_hTemporaryCommandBuffer; + + ERR_GUARD_VULKAN( vkQueueSubmit(g_hGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) ); + ERR_GUARD_VULKAN( vkQueueWaitIdle(g_hGraphicsQueue) ); +} + +void LoadShader(std::vector<char>& out, const char* fileName) +{ + std::ifstream file(std::string(SHADER_PATH1) + fileName, std::ios::ate | std::ios::binary); + if(file.is_open() == false) + file.open(std::string(SHADER_PATH2) + fileName, std::ios::ate | std::ios::binary); + assert(file.is_open()); + size_t fileSize = (size_t)file.tellg(); + if(fileSize > 0) + { + out.resize(fileSize); + file.seekg(0); + file.read(out.data(), fileSize); + file.close(); + } + else + out.clear(); +} + +static VkBool32 VKAPI_PTR MyDebugReportCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) +{ + assert(pCallbackData && pCallbackData->pMessageIdName && pCallbackData->pMessage); + + switch(messageSeverity) + { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + SetConsoleColor(CONSOLE_COLOR::WARNING); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + SetConsoleColor(CONSOLE_COLOR::ERROR_); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + SetConsoleColor(CONSOLE_COLOR::NORMAL); + break; + default: // VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT + SetConsoleColor(CONSOLE_COLOR::INFO); + } + + printf("%s \xBA %s\n", pCallbackData->pMessageIdName, pCallbackData->pMessage); + + SetConsoleColor(CONSOLE_COLOR::NORMAL); + + if(messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT || + messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + { + OutputDebugStringA(pCallbackData->pMessage); + OutputDebugStringA("\n"); + } + + return VK_FALSE; +} + +static VkSurfaceFormatKHR ChooseSurfaceFormat() +{ + assert(!g_SurfaceFormats.empty()); + + if((g_SurfaceFormats.size() == 1) && (g_SurfaceFormats[0].format == VK_FORMAT_UNDEFINED)) + { + VkSurfaceFormatKHR result = { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; + return result; + } + + for(const auto& format : g_SurfaceFormats) + { + if((format.format == VK_FORMAT_B8G8R8A8_UNORM) && + (format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)) + { + return format; + } + } + + return g_SurfaceFormats[0]; +} + +VkPresentModeKHR ChooseSwapPresentMode() +{ + VkPresentModeKHR preferredMode = VSYNC ? VK_PRESENT_MODE_MAILBOX_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR; + + if(std::find(g_PresentModes.begin(), g_PresentModes.end(), preferredMode) != + g_PresentModes.end()) + { + return preferredMode; + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +static VkExtent2D ChooseSwapExtent() +{ + if(g_SurfaceCapabilities.currentExtent.width != UINT_MAX) + return g_SurfaceCapabilities.currentExtent; + + VkExtent2D result = { + std::max(g_SurfaceCapabilities.minImageExtent.width, + std::min(g_SurfaceCapabilities.maxImageExtent.width, (uint32_t)g_SizeX)), + std::max(g_SurfaceCapabilities.minImageExtent.height, + std::min(g_SurfaceCapabilities.maxImageExtent.height, (uint32_t)g_SizeY)) }; + return result; +} + +static constexpr uint32_t GetVulkanApiVersion() +{ +#if VMA_VULKAN_VERSION == 1002000 + return VK_API_VERSION_1_2; +#elif VMA_VULKAN_VERSION == 1001000 + return VK_API_VERSION_1_1; +#elif VMA_VULKAN_VERSION == 1000000 + return VK_API_VERSION_1_0; +#else +#error Invalid VMA_VULKAN_VERSION. + return UINT32_MAX; +#endif +} + +void VulkanUsage::Init() +{ + g_hAppInstance = (HINSTANCE)GetModuleHandle(NULL); + + if(USE_CUSTOM_CPU_ALLOCATION_CALLBACKS) + { + g_Allocs = &g_CpuAllocationCallbacks; + } + + uint32_t instanceLayerPropCount = 0; + ERR_GUARD_VULKAN( vkEnumerateInstanceLayerProperties(&instanceLayerPropCount, nullptr) ); + std::vector<VkLayerProperties> instanceLayerProps(instanceLayerPropCount); + if(instanceLayerPropCount > 0) + { + ERR_GUARD_VULKAN( vkEnumerateInstanceLayerProperties(&instanceLayerPropCount, instanceLayerProps.data()) ); + } + + if(g_EnableValidationLayer) + { + if(IsLayerSupported(instanceLayerProps.data(), instanceLayerProps.size(), VALIDATION_LAYER_NAME) == false) + { + wprintf(L"Layer \"%hs\" not supported.", VALIDATION_LAYER_NAME); + g_EnableValidationLayer = false; + } + } + + uint32_t availableInstanceExtensionCount = 0; + ERR_GUARD_VULKAN( vkEnumerateInstanceExtensionProperties(nullptr, &availableInstanceExtensionCount, nullptr) ); + std::vector<VkExtensionProperties> availableInstanceExtensions(availableInstanceExtensionCount); + if(availableInstanceExtensionCount > 0) + { + ERR_GUARD_VULKAN( vkEnumerateInstanceExtensionProperties(nullptr, &availableInstanceExtensionCount, availableInstanceExtensions.data()) ); + } + + 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(g_EnableValidationLayer) + { + instanceLayers.push_back(VALIDATION_LAYER_NAME); + } + + for(const auto& extensionProperties : availableInstanceExtensions) + { + if(strcmp(extensionProperties.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0) + { + if(GetVulkanApiVersion() == VK_API_VERSION_1_0) + { + enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + VK_KHR_get_physical_device_properties2_enabled = true; + } + } + else if(strcmp(extensionProperties.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) + { + enabledInstanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + VK_EXT_debug_utils_enabled = true; + } + } + + VkApplicationInfo appInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO }; + appInfo.pApplicationName = APP_TITLE_A; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "Adam Sawicki Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = GetVulkanApiVersion(); + + VkInstanceCreateInfo instInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; + instInfo.pApplicationInfo = &appInfo; + instInfo.enabledExtensionCount = static_cast<uint32_t>(enabledInstanceExtensions.size()); + instInfo.ppEnabledExtensionNames = enabledInstanceExtensions.data(); + instInfo.enabledLayerCount = static_cast<uint32_t>(instanceLayers.size()); + instInfo.ppEnabledLayerNames = instanceLayers.data(); + + wprintf(L"Vulkan API version used: "); + switch(appInfo.apiVersion) + { + case VK_API_VERSION_1_0: wprintf(L"1.0\n"); break; + case VK_API_VERSION_1_1: wprintf(L"1.1\n"); break; + case VK_API_VERSION_1_2: wprintf(L"1.2\n"); break; + default: assert(0); + } + + ERR_GUARD_VULKAN( vkCreateInstance(&instInfo, g_Allocs, &g_hVulkanInstance) ); + + if(VK_EXT_debug_utils_enabled) + { + RegisterDebugCallbacks(); + } +} + +VulkanUsage::~VulkanUsage() +{ + if(m_DebugUtilsMessenger) + { + vkDestroyDebugUtilsMessengerEXT_Func(g_hVulkanInstance, m_DebugUtilsMessenger, g_Allocs); + } + + if(g_hVulkanInstance) + { + vkDestroyInstance(g_hVulkanInstance, g_Allocs); + g_hVulkanInstance = VK_NULL_HANDLE; + } +} + +void VulkanUsage::PrintPhysicalDeviceList() const +{ + uint32_t deviceCount = 0; + ERR_GUARD_VULKAN(vkEnumeratePhysicalDevices(g_hVulkanInstance, &deviceCount, nullptr)); + std::vector<VkPhysicalDevice> physicalDevices(deviceCount); + if(deviceCount > 0) + { + ERR_GUARD_VULKAN(vkEnumeratePhysicalDevices(g_hVulkanInstance, &deviceCount, physicalDevices.data())); + } + + for(size_t i = 0; i < deviceCount; ++i) + { + VkPhysicalDeviceProperties props = {}; + vkGetPhysicalDeviceProperties(physicalDevices[i], &props); + wprintf(L"Physical device %zu: %hs\n", i, props.deviceName); + } +} + +VkPhysicalDevice VulkanUsage::SelectPhysicalDevice(const GPUSelection& GPUSelection) const +{ + uint32_t deviceCount = 0; + ERR_GUARD_VULKAN(vkEnumeratePhysicalDevices(g_hVulkanInstance, &deviceCount, nullptr)); + std::vector<VkPhysicalDevice> physicalDevices(deviceCount); + if(deviceCount > 0) + { + ERR_GUARD_VULKAN(vkEnumeratePhysicalDevices(g_hVulkanInstance, &deviceCount, physicalDevices.data())); + } + + if(GPUSelection.Index != UINT32_MAX) + { + // Cannot specify both index and name. + if(!GPUSelection.Substring.empty()) + { + return VK_NULL_HANDLE; + } + + return GPUSelection.Index < deviceCount ? physicalDevices[GPUSelection.Index] : VK_NULL_HANDLE; + } + + if(!GPUSelection.Substring.empty()) + { + VkPhysicalDevice result = VK_NULL_HANDLE; + std::wstring name; + for(uint32_t i = 0; i < deviceCount; ++i) + { + VkPhysicalDeviceProperties props = {}; + vkGetPhysicalDeviceProperties(physicalDevices[i], &props); + if(ConvertCharsToUnicode(&name, props.deviceName, strlen(props.deviceName), CP_UTF8) && + StrStrI(name.c_str(), GPUSelection.Substring.c_str())) + { + // Second matching device found - error. + if(result != VK_NULL_HANDLE) + { + return VK_NULL_HANDLE; + } + // First matching device found. + result = physicalDevices[i]; + } + } + // Found or not, return it. + return result; + } + + // Select first one. + return deviceCount > 0 ? physicalDevices[0] : VK_NULL_HANDLE; +} + +void VulkanUsage::RegisterDebugCallbacks() +{ + vkCreateDebugUtilsMessengerEXT_Func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + g_hVulkanInstance, "vkCreateDebugUtilsMessengerEXT"); + vkDestroyDebugUtilsMessengerEXT_Func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + g_hVulkanInstance, "vkDestroyDebugUtilsMessengerEXT"); + vkSetDebugUtilsObjectNameEXT_Func = (PFN_vkSetDebugUtilsObjectNameEXT)vkGetInstanceProcAddr( + g_hVulkanInstance, "vkSetDebugUtilsObjectNameEXT"); + assert(vkCreateDebugUtilsMessengerEXT_Func); + assert(vkDestroyDebugUtilsMessengerEXT_Func); + assert(vkSetDebugUtilsObjectNameEXT_Func); + + VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT }; + messengerCreateInfo.messageSeverity = DEBUG_UTILS_MESSENGER_MESSAGE_SEVERITY; + messengerCreateInfo.messageType = DEBUG_UTILS_MESSENGER_MESSAGE_TYPE; + messengerCreateInfo.pfnUserCallback = MyDebugReportCallback; + ERR_GUARD_VULKAN( vkCreateDebugUtilsMessengerEXT_Func(g_hVulkanInstance, &messengerCreateInfo, g_Allocs, &m_DebugUtilsMessenger) ); +} + +bool VulkanUsage::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; +} + +struct Vertex +{ + float pos[3]; + float color[3]; + float texCoord[2]; +}; + +static void CreateMesh() +{ + assert(g_hAllocator); + + static Vertex vertices[] = { + // -X + { { -1.f, -1.f, -1.f}, {1.0f, 0.0f, 0.0f}, {0.f, 0.f} }, + { { -1.f, -1.f, 1.f}, {1.0f, 0.0f, 0.0f}, {1.f, 0.f} }, + { { -1.f, 1.f, -1.f}, {1.0f, 0.0f, 0.0f}, {0.f, 1.f} }, + { { -1.f, 1.f, 1.f}, {1.0f, 0.0f, 0.0f}, {1.f, 1.f} }, + // +X + { { 1.f, -1.f, 1.f}, {0.0f, 1.0f, 0.0f}, {0.f, 0.f} }, + { { 1.f, -1.f, -1.f}, {0.0f, 1.0f, 0.0f}, {1.f, 0.f} }, + { { 1.f, 1.f, 1.f}, {0.0f, 1.0f, 0.0f}, {0.f, 1.f} }, + { { 1.f, 1.f, -1.f}, {0.0f, 1.0f, 0.0f}, {1.f, 1.f} }, + // -Z + { { 1.f, -1.f, -1.f}, {0.0f, 0.0f, 1.0f}, {0.f, 0.f} }, + { {-1.f, -1.f, -1.f}, {0.0f, 0.0f, 1.0f}, {1.f, 0.f} }, + { { 1.f, 1.f, -1.f}, {0.0f, 0.0f, 1.0f}, {0.f, 1.f} }, + { {-1.f, 1.f, -1.f}, {0.0f, 0.0f, 1.0f}, {1.f, 1.f} }, + // +Z + { {-1.f, -1.f, 1.f}, {1.0f, 1.0f, 0.0f}, {0.f, 0.f} }, + { { 1.f, -1.f, 1.f}, {1.0f, 1.0f, 0.0f}, {1.f, 0.f} }, + { {-1.f, 1.f, 1.f}, {1.0f, 1.0f, 0.0f}, {0.f, 1.f} }, + { { 1.f, 1.f, 1.f}, {1.0f, 1.0f, 0.0f}, {1.f, 1.f} }, + // -Y + { {-1.f, -1.f, -1.f}, {0.0f, 1.0f, 1.0f}, {0.f, 0.f} }, + { { 1.f, -1.f, -1.f}, {0.0f, 1.0f, 1.0f}, {1.f, 0.f} }, + { {-1.f, -1.f, 1.f}, {0.0f, 1.0f, 1.0f}, {0.f, 1.f} }, + { { 1.f, -1.f, 1.f}, {0.0f, 1.0f, 1.0f}, {1.f, 1.f} }, + // +Y + { { 1.f, 1.f, -1.f}, {1.0f, 0.0f, 1.0f}, {0.f, 0.f} }, + { {-1.f, 1.f, -1.f}, {1.0f, 0.0f, 1.0f}, {1.f, 0.f} }, + { { 1.f, 1.f, 1.f}, {1.0f, 0.0f, 1.0f}, {0.f, 1.f} }, + { {-1.f, 1.f, 1.f}, {1.0f, 0.0f, 1.0f}, {1.f, 1.f} }, + }; + static uint16_t indices[] = { + 0, 1, 2, 3, USHRT_MAX, + 4, 5, 6, 7, USHRT_MAX, + 8, 9, 10, 11, USHRT_MAX, + 12, 13, 14, 15, USHRT_MAX, + 16, 17, 18, 19, USHRT_MAX, + 20, 21, 22, 23, USHRT_MAX, + }; + + size_t vertexBufferSize = sizeof(Vertex) * _countof(vertices); + size_t indexBufferSize = sizeof(uint16_t) * _countof(indices); + g_IndexCount = (uint32_t)_countof(indices); + + // Create vertex buffer + + VkBufferCreateInfo vbInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + vbInfo.size = vertexBufferSize; + vbInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + vbInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo vbAllocCreateInfo = {}; + vbAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + vbAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VkBuffer stagingVertexBuffer = VK_NULL_HANDLE; + VmaAllocation stagingVertexBufferAlloc = VK_NULL_HANDLE; + VmaAllocationInfo stagingVertexBufferAllocInfo = {}; + ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &vbInfo, &vbAllocCreateInfo, &stagingVertexBuffer, &stagingVertexBufferAlloc, &stagingVertexBufferAllocInfo) ); + + memcpy(stagingVertexBufferAllocInfo.pMappedData, vertices, vertexBufferSize); + + // No need to flush stagingVertexBuffer memory because CPU_ONLY memory is always HOST_COHERENT. + + vbInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + vbAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + vbAllocCreateInfo.flags = 0; + ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &vbInfo, &vbAllocCreateInfo, &g_hVertexBuffer, &g_hVertexBufferAlloc, nullptr) ); + + // Create index buffer + + VkBufferCreateInfo ibInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + ibInfo.size = indexBufferSize; + ibInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + ibInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo ibAllocCreateInfo = {}; + ibAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + ibAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VkBuffer stagingIndexBuffer = VK_NULL_HANDLE; + VmaAllocation stagingIndexBufferAlloc = VK_NULL_HANDLE; + VmaAllocationInfo stagingIndexBufferAllocInfo = {}; + ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &ibInfo, &ibAllocCreateInfo, &stagingIndexBuffer, &stagingIndexBufferAlloc, &stagingIndexBufferAllocInfo) ); + + memcpy(stagingIndexBufferAllocInfo.pMappedData, indices, indexBufferSize); + + // No need to flush stagingIndexBuffer memory because CPU_ONLY memory is always HOST_COHERENT. + + ibInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + ibAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + ibAllocCreateInfo.flags = 0; + ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &ibInfo, &ibAllocCreateInfo, &g_hIndexBuffer, &g_hIndexBufferAlloc, nullptr) ); + + // Copy buffers + + BeginSingleTimeCommands(); + + VkBufferCopy vbCopyRegion = {}; + vbCopyRegion.srcOffset = 0; + vbCopyRegion.dstOffset = 0; + vbCopyRegion.size = vbInfo.size; + vkCmdCopyBuffer(g_hTemporaryCommandBuffer, stagingVertexBuffer, g_hVertexBuffer, 1, &vbCopyRegion); + + VkBufferCopy ibCopyRegion = {}; + ibCopyRegion.srcOffset = 0; + ibCopyRegion.dstOffset = 0; + ibCopyRegion.size = ibInfo.size; + vkCmdCopyBuffer(g_hTemporaryCommandBuffer, stagingIndexBuffer, g_hIndexBuffer, 1, &ibCopyRegion); + + EndSingleTimeCommands(); + + vmaDestroyBuffer(g_hAllocator, stagingIndexBuffer, stagingIndexBufferAlloc); + vmaDestroyBuffer(g_hAllocator, stagingVertexBuffer, stagingVertexBufferAlloc); +} + +static void CreateTexture(uint32_t sizeX, uint32_t sizeY) +{ + // Create staging buffer. + + const VkDeviceSize imageSize = sizeX * sizeY * 4; + + VkBufferCreateInfo stagingBufInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + stagingBufInfo.size = imageSize; + stagingBufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo stagingBufAllocCreateInfo = {}; + stagingBufAllocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + stagingBufAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VkBuffer stagingBuf = VK_NULL_HANDLE; + VmaAllocation stagingBufAlloc = VK_NULL_HANDLE; + VmaAllocationInfo stagingBufAllocInfo = {}; + ERR_GUARD_VULKAN( vmaCreateBuffer(g_hAllocator, &stagingBufInfo, &stagingBufAllocCreateInfo, &stagingBuf, &stagingBufAlloc, &stagingBufAllocInfo) ); + + char* const pImageData = (char*)stagingBufAllocInfo.pMappedData; + uint8_t* pRowData = (uint8_t*)pImageData; + for(uint32_t y = 0; y < sizeY; ++y) + { + uint32_t* pPixelData = (uint32_t*)pRowData; + for(uint32_t x = 0; x < sizeY; ++x) + { + *pPixelData = + ((x & 0x18) == 0x08 ? 0x000000FF : 0x00000000) | + ((x & 0x18) == 0x10 ? 0x0000FFFF : 0x00000000) | + ((y & 0x18) == 0x08 ? 0x0000FF00 : 0x00000000) | + ((y & 0x18) == 0x10 ? 0x00FF0000 : 0x00000000); + ++pPixelData; + } + pRowData += sizeX * 4; + } + + // No need to flush stagingImage memory because CPU_ONLY memory is always HOST_COHERENT. + + // Create g_hTextureImage in GPU memory. + + VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = sizeX; + imageInfo.extent.height = sizeY; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.flags = 0; + + VmaAllocationCreateInfo imageAllocCreateInfo = {}; + imageAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + ERR_GUARD_VULKAN( vmaCreateImage(g_hAllocator, &imageInfo, &imageAllocCreateInfo, &g_hTextureImage, &g_hTextureImageAlloc, nullptr) ); + + // Transition image layouts, copy image. + + BeginSingleTimeCommands(); + + VkImageMemoryBarrier imgMemBarrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + imgMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imgMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imgMemBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imgMemBarrier.subresourceRange.baseMipLevel = 0; + imgMemBarrier.subresourceRange.levelCount = 1; + imgMemBarrier.subresourceRange.baseArrayLayer = 0; + imgMemBarrier.subresourceRange.layerCount = 1; + imgMemBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imgMemBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + imgMemBarrier.image = g_hTextureImage; + imgMemBarrier.srcAccessMask = 0; + imgMemBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + vkCmdPipelineBarrier( + g_hTemporaryCommandBuffer, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &imgMemBarrier); + + VkBufferImageCopy region = {}; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + region.imageExtent.width = sizeX; + region.imageExtent.height = sizeY; + region.imageExtent.depth = 1; + + vkCmdCopyBufferToImage(g_hTemporaryCommandBuffer, stagingBuf, g_hTextureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + imgMemBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + imgMemBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imgMemBarrier.image = g_hTextureImage; + imgMemBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + imgMemBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier( + g_hTemporaryCommandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &imgMemBarrier); + + EndSingleTimeCommands(); + + vmaDestroyBuffer(g_hAllocator, stagingBuf, stagingBufAlloc); + + // Create ImageView + + VkImageViewCreateInfo textureImageViewInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + textureImageViewInfo.image = g_hTextureImage; + textureImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + textureImageViewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + textureImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + textureImageViewInfo.subresourceRange.baseMipLevel = 0; + textureImageViewInfo.subresourceRange.levelCount = 1; + textureImageViewInfo.subresourceRange.baseArrayLayer = 0; + textureImageViewInfo.subresourceRange.layerCount = 1; + ERR_GUARD_VULKAN( vkCreateImageView(g_hDevice, &textureImageViewInfo, g_Allocs, &g_hTextureImageView) ); +} + +struct UniformBufferObject +{ + mat4 ModelViewProj; +}; + +static VkFormat FindSupportedFormat( + const std::vector<VkFormat>& candidates, + VkImageTiling tiling, + VkFormatFeatureFlags features) +{ + for (VkFormat format : candidates) + { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(g_hPhysicalDevice, format, &props); + + if ((tiling == VK_IMAGE_TILING_LINEAR) && + ((props.linearTilingFeatures & features) == features)) + { + return format; + } + else if ((tiling == VK_IMAGE_TILING_OPTIMAL) && + ((props.optimalTilingFeatures & features) == features)) + { + return format; + } + } + return VK_FORMAT_UNDEFINED; +} + +static VkFormat FindDepthFormat() +{ + std::vector<VkFormat> formats; + formats.push_back(VK_FORMAT_D32_SFLOAT); + formats.push_back(VK_FORMAT_D32_SFLOAT_S8_UINT); + formats.push_back(VK_FORMAT_D24_UNORM_S8_UINT); + + return FindSupportedFormat( + formats, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); +} + +static void CreateSwapchain() +{ + // Query surface formats. + + ERR_GUARD_VULKAN( vkGetPhysicalDeviceSurfaceCapabilitiesKHR(g_hPhysicalDevice, g_hSurface, &g_SurfaceCapabilities) ); + + uint32_t formatCount = 0; + ERR_GUARD_VULKAN( vkGetPhysicalDeviceSurfaceFormatsKHR(g_hPhysicalDevice, g_hSurface, &formatCount, nullptr) ); + g_SurfaceFormats.resize(formatCount); + ERR_GUARD_VULKAN( vkGetPhysicalDeviceSurfaceFormatsKHR(g_hPhysicalDevice, g_hSurface, &formatCount, g_SurfaceFormats.data()) ); + + uint32_t presentModeCount = 0; + ERR_GUARD_VULKAN( vkGetPhysicalDeviceSurfacePresentModesKHR(g_hPhysicalDevice, g_hSurface, &presentModeCount, nullptr) ); + g_PresentModes.resize(presentModeCount); + ERR_GUARD_VULKAN( vkGetPhysicalDeviceSurfacePresentModesKHR(g_hPhysicalDevice, g_hSurface, &presentModeCount, g_PresentModes.data()) ); + + // Create swap chain + + g_SurfaceFormat = ChooseSurfaceFormat(); + VkPresentModeKHR presentMode = ChooseSwapPresentMode(); + g_Extent = ChooseSwapExtent(); + + uint32_t imageCount = g_SurfaceCapabilities.minImageCount + 1; + if((g_SurfaceCapabilities.maxImageCount > 0) && + (imageCount > g_SurfaceCapabilities.maxImageCount)) + { + imageCount = g_SurfaceCapabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR swapChainInfo = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR }; + swapChainInfo.surface = g_hSurface; + swapChainInfo.minImageCount = imageCount; + swapChainInfo.imageFormat = g_SurfaceFormat.format; + swapChainInfo.imageColorSpace = g_SurfaceFormat.colorSpace; + swapChainInfo.imageExtent = g_Extent; + swapChainInfo.imageArrayLayers = 1; + swapChainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + swapChainInfo.preTransform = g_SurfaceCapabilities.currentTransform; + swapChainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapChainInfo.presentMode = presentMode; + swapChainInfo.clipped = VK_TRUE; + swapChainInfo.oldSwapchain = g_hSwapchain; + + uint32_t queueFamilyIndices[] = { g_GraphicsQueueFamilyIndex, g_PresentQueueFamilyIndex }; + if(g_PresentQueueFamilyIndex != g_GraphicsQueueFamilyIndex) + { + swapChainInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + swapChainInfo.queueFamilyIndexCount = 2; + swapChainInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else + { + swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + VkSwapchainKHR hNewSwapchain = VK_NULL_HANDLE; + ERR_GUARD_VULKAN( vkCreateSwapchainKHR(g_hDevice, &swapChainInfo, g_Allocs, &hNewSwapchain) ); + if(g_hSwapchain != VK_NULL_HANDLE) + vkDestroySwapchainKHR(g_hDevice, g_hSwapchain, g_Allocs); + g_hSwapchain = hNewSwapchain; + + // Retrieve swapchain images. + + uint32_t swapchainImageCount = 0; + ERR_GUARD_VULKAN( vkGetSwapchainImagesKHR(g_hDevice, g_hSwapchain, &swapchainImageCount, nullptr) ); + g_SwapchainImages.resize(swapchainImageCount); + ERR_GUARD_VULKAN( vkGetSwapchainImagesKHR(g_hDevice, g_hSwapchain, &swapchainImageCount, g_SwapchainImages.data()) ); + + // Create swapchain image views. + + for(size_t i = g_SwapchainImageViews.size(); i--; ) + vkDestroyImageView(g_hDevice, g_SwapchainImageViews[i], g_Allocs); + g_SwapchainImageViews.clear(); + + VkImageViewCreateInfo swapchainImageViewInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + g_SwapchainImageViews.resize(swapchainImageCount); + for(uint32_t i = 0; i < swapchainImageCount; ++i) + { + swapchainImageViewInfo.image = g_SwapchainImages[i]; + swapchainImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + swapchainImageViewInfo.format = g_SurfaceFormat.format; + swapchainImageViewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + swapchainImageViewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + swapchainImageViewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + swapchainImageViewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + swapchainImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + swapchainImageViewInfo.subresourceRange.baseMipLevel = 0; + swapchainImageViewInfo.subresourceRange.levelCount = 1; + swapchainImageViewInfo.subresourceRange.baseArrayLayer = 0; + swapchainImageViewInfo.subresourceRange.layerCount = 1; + ERR_GUARD_VULKAN( vkCreateImageView(g_hDevice, &swapchainImageViewInfo, g_Allocs, &g_SwapchainImageViews[i]) ); + } + + // Create depth buffer + + g_DepthFormat = FindDepthFormat(); + assert(g_DepthFormat != VK_FORMAT_UNDEFINED); + + VkImageCreateInfo depthImageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + depthImageInfo.imageType = VK_IMAGE_TYPE_2D; + depthImageInfo.extent.width = g_Extent.width; + depthImageInfo.extent.height = g_Extent.height; + depthImageInfo.extent.depth = 1; + depthImageInfo.mipLevels = 1; + depthImageInfo.arrayLayers = 1; + depthImageInfo.format = g_DepthFormat; + depthImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + depthImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + depthImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + depthImageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + depthImageInfo.flags = 0; + + VmaAllocationCreateInfo depthImageAllocCreateInfo = {}; + depthImageAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + ERR_GUARD_VULKAN( vmaCreateImage(g_hAllocator, &depthImageInfo, &depthImageAllocCreateInfo, &g_hDepthImage, &g_hDepthImageAlloc, nullptr) ); + + VkImageViewCreateInfo depthImageViewInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + depthImageViewInfo.image = g_hDepthImage; + depthImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + depthImageViewInfo.format = g_DepthFormat; + depthImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + depthImageViewInfo.subresourceRange.baseMipLevel = 0; + depthImageViewInfo.subresourceRange.levelCount = 1; + depthImageViewInfo.subresourceRange.baseArrayLayer = 0; + depthImageViewInfo.subresourceRange.layerCount = 1; + + ERR_GUARD_VULKAN( vkCreateImageView(g_hDevice, &depthImageViewInfo, g_Allocs, &g_hDepthImageView) ); + + // Create pipeline layout + { + if(g_hPipelineLayout != VK_NULL_HANDLE) + { + vkDestroyPipelineLayout(g_hDevice, g_hPipelineLayout, g_Allocs); + g_hPipelineLayout = VK_NULL_HANDLE; + } + + VkPushConstantRange pushConstantRanges[1]; + ZeroMemory(&pushConstantRanges, sizeof pushConstantRanges); + pushConstantRanges[0].offset = 0; + pushConstantRanges[0].size = sizeof(UniformBufferObject); + pushConstantRanges[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayout descriptorSetLayouts[] = { g_hDescriptorSetLayout }; + VkPipelineLayoutCreateInfo pipelineLayoutInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = descriptorSetLayouts; + pipelineLayoutInfo.pushConstantRangeCount = 1; + pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges; + ERR_GUARD_VULKAN( vkCreatePipelineLayout(g_hDevice, &pipelineLayoutInfo, g_Allocs, &g_hPipelineLayout) ); + } + + // Create render pass + { + if(g_hRenderPass != VK_NULL_HANDLE) + { + vkDestroyRenderPass(g_hDevice, g_hRenderPass, g_Allocs); + g_hRenderPass = VK_NULL_HANDLE; + } + + VkAttachmentDescription attachments[2]; + ZeroMemory(attachments, sizeof(attachments)); + + attachments[0].format = g_SurfaceFormat.format; + attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + attachments[1].format = g_DepthFormat; + attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; + attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthStencilAttachmentRef = {}; + depthStencilAttachmentRef.attachment = 1; + depthStencilAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpassDesc = {}; + subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpassDesc.colorAttachmentCount = 1; + subpassDesc.pColorAttachments = &colorAttachmentRef; + subpassDesc.pDepthStencilAttachment = &depthStencilAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; + renderPassInfo.attachmentCount = (uint32_t)_countof(attachments); + renderPassInfo.pAttachments = attachments; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpassDesc; + renderPassInfo.dependencyCount = 0; + ERR_GUARD_VULKAN( vkCreateRenderPass(g_hDevice, &renderPassInfo, g_Allocs, &g_hRenderPass) ); + } + + // Create pipeline + { + std::vector<char> vertShaderCode; + LoadShader(vertShaderCode, "Shader.vert.spv"); + VkShaderModuleCreateInfo shaderModuleInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; + shaderModuleInfo.codeSize = vertShaderCode.size(); + shaderModuleInfo.pCode = (const uint32_t*)vertShaderCode.data(); + VkShaderModule hVertShaderModule = VK_NULL_HANDLE; + ERR_GUARD_VULKAN( vkCreateShaderModule(g_hDevice, &shaderModuleInfo, g_Allocs, &hVertShaderModule) ); + + std::vector<char> hFragShaderCode; + LoadShader(hFragShaderCode, "Shader.frag.spv"); + shaderModuleInfo.codeSize = hFragShaderCode.size(); + shaderModuleInfo.pCode = (const uint32_t*)hFragShaderCode.data(); + VkShaderModule fragShaderModule = VK_NULL_HANDLE; + ERR_GUARD_VULKAN( vkCreateShaderModule(g_hDevice, &shaderModuleInfo, g_Allocs, &fragShaderModule) ); + + VkPipelineShaderStageCreateInfo vertPipelineShaderStageInfo = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }; + vertPipelineShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertPipelineShaderStageInfo.module = hVertShaderModule; + vertPipelineShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragPipelineShaderStageInfo = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }; + fragPipelineShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragPipelineShaderStageInfo.module = fragShaderModule; + fragPipelineShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo pipelineShaderStageInfos[] = { + vertPipelineShaderStageInfo, + fragPipelineShaderStageInfo + }; + + VkVertexInputBindingDescription bindingDescription = {}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + VkVertexInputAttributeDescription attributeDescriptions[3]; + ZeroMemory(attributeDescriptions, sizeof(attributeDescriptions)); + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; + pipelineVertexInputStateInfo.vertexBindingDescriptionCount = 1; + pipelineVertexInputStateInfo.pVertexBindingDescriptions = &bindingDescription; + pipelineVertexInputStateInfo.vertexAttributeDescriptionCount = _countof(attributeDescriptions); + pipelineVertexInputStateInfo.pVertexAttributeDescriptions = attributeDescriptions; + + VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; + pipelineInputAssemblyStateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + pipelineInputAssemblyStateInfo.primitiveRestartEnable = VK_TRUE; + + VkViewport viewport = {}; + viewport.x = 0.f; + viewport.y = 0.f; + viewport.width = (float)g_Extent.width; + viewport.height = (float)g_Extent.height; + viewport.minDepth = 0.f; + viewport.maxDepth = 1.f; + + VkRect2D scissor = {}; + scissor.offset.x = 0; + scissor.offset.y = 0; + scissor.extent = g_Extent; + + VkPipelineViewportStateCreateInfo pipelineViewportStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; + pipelineViewportStateInfo.viewportCount = 1; + pipelineViewportStateInfo.pViewports = &viewport; + pipelineViewportStateInfo.scissorCount = 1; + pipelineViewportStateInfo.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; + pipelineRasterizationStateInfo.depthClampEnable = VK_FALSE; + pipelineRasterizationStateInfo.rasterizerDiscardEnable = VK_FALSE; + pipelineRasterizationStateInfo.polygonMode = VK_POLYGON_MODE_FILL; + pipelineRasterizationStateInfo.lineWidth = 1.f; + pipelineRasterizationStateInfo.cullMode = VK_CULL_MODE_BACK_BIT; + pipelineRasterizationStateInfo.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + pipelineRasterizationStateInfo.depthBiasEnable = VK_FALSE; + pipelineRasterizationStateInfo.depthBiasConstantFactor = 0.f; + pipelineRasterizationStateInfo.depthBiasClamp = 0.f; + pipelineRasterizationStateInfo.depthBiasSlopeFactor = 0.f; + + VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; + pipelineMultisampleStateInfo.sampleShadingEnable = VK_FALSE; + pipelineMultisampleStateInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + pipelineMultisampleStateInfo.minSampleShading = 1.f; + pipelineMultisampleStateInfo.pSampleMask = nullptr; + pipelineMultisampleStateInfo.alphaToCoverageEnable = VK_FALSE; + pipelineMultisampleStateInfo.alphaToOneEnable = VK_FALSE; + + VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState = {}; + pipelineColorBlendAttachmentState.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | + VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | + VK_COLOR_COMPONENT_A_BIT; + pipelineColorBlendAttachmentState.blendEnable = VK_FALSE; + pipelineColorBlendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional + pipelineColorBlendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional + pipelineColorBlendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; // Optional + pipelineColorBlendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional + pipelineColorBlendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional + pipelineColorBlendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; // Optional + + VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; + pipelineColorBlendStateInfo.logicOpEnable = VK_FALSE; + pipelineColorBlendStateInfo.logicOp = VK_LOGIC_OP_COPY; + pipelineColorBlendStateInfo.attachmentCount = 1; + pipelineColorBlendStateInfo.pAttachments = &pipelineColorBlendAttachmentState; + + VkPipelineDepthStencilStateCreateInfo depthStencilStateInfo = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; + depthStencilStateInfo.depthTestEnable = VK_TRUE; + depthStencilStateInfo.depthWriteEnable = VK_TRUE; + depthStencilStateInfo.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencilStateInfo.depthBoundsTestEnable = VK_FALSE; + depthStencilStateInfo.stencilTestEnable = VK_FALSE; + + VkGraphicsPipelineCreateInfo pipelineInfo = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = pipelineShaderStageInfos; + pipelineInfo.pVertexInputState = &pipelineVertexInputStateInfo; + pipelineInfo.pInputAssemblyState = &pipelineInputAssemblyStateInfo; + pipelineInfo.pViewportState = &pipelineViewportStateInfo; + pipelineInfo.pRasterizationState = &pipelineRasterizationStateInfo; + pipelineInfo.pMultisampleState = &pipelineMultisampleStateInfo; + pipelineInfo.pDepthStencilState = &depthStencilStateInfo; + pipelineInfo.pColorBlendState = &pipelineColorBlendStateInfo; + pipelineInfo.pDynamicState = nullptr; + pipelineInfo.layout = g_hPipelineLayout; + pipelineInfo.renderPass = g_hRenderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineInfo.basePipelineIndex = -1; + ERR_GUARD_VULKAN( vkCreateGraphicsPipelines( + g_hDevice, + VK_NULL_HANDLE, + 1, + &pipelineInfo, + g_Allocs, + &g_hPipeline) ); + + vkDestroyShaderModule(g_hDevice, fragShaderModule, g_Allocs); + vkDestroyShaderModule(g_hDevice, hVertShaderModule, g_Allocs); + } + + // Create frambuffers + + for(size_t i = g_Framebuffers.size(); i--; ) + vkDestroyFramebuffer(g_hDevice, g_Framebuffers[i], g_Allocs); + g_Framebuffers.clear(); + + g_Framebuffers.resize(g_SwapchainImageViews.size()); + for(size_t i = 0; i < g_SwapchainImages.size(); ++i) + { + VkImageView attachments[] = { g_SwapchainImageViews[i], g_hDepthImageView }; + + VkFramebufferCreateInfo framebufferInfo = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; + framebufferInfo.renderPass = g_hRenderPass; + framebufferInfo.attachmentCount = (uint32_t)_countof(attachments); + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = g_Extent.width; + framebufferInfo.height = g_Extent.height; + framebufferInfo.layers = 1; + ERR_GUARD_VULKAN( vkCreateFramebuffer(g_hDevice, &framebufferInfo, g_Allocs, &g_Framebuffers[i]) ); + } + + // Create semaphores + + if(g_hImageAvailableSemaphore != VK_NULL_HANDLE) + { + vkDestroySemaphore(g_hDevice, g_hImageAvailableSemaphore, g_Allocs); + g_hImageAvailableSemaphore = VK_NULL_HANDLE; + } + if(g_hRenderFinishedSemaphore != VK_NULL_HANDLE) + { + vkDestroySemaphore(g_hDevice, g_hRenderFinishedSemaphore, g_Allocs); + g_hRenderFinishedSemaphore = VK_NULL_HANDLE; + } + + VkSemaphoreCreateInfo semaphoreInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + ERR_GUARD_VULKAN( vkCreateSemaphore(g_hDevice, &semaphoreInfo, g_Allocs, &g_hImageAvailableSemaphore) ); + ERR_GUARD_VULKAN( vkCreateSemaphore(g_hDevice, &semaphoreInfo, g_Allocs, &g_hRenderFinishedSemaphore) ); +} + +static void DestroySwapchain(bool destroyActualSwapchain) +{ + if(g_hImageAvailableSemaphore != VK_NULL_HANDLE) + { + vkDestroySemaphore(g_hDevice, g_hImageAvailableSemaphore, g_Allocs); + g_hImageAvailableSemaphore = VK_NULL_HANDLE; + } + if(g_hRenderFinishedSemaphore != VK_NULL_HANDLE) + { + vkDestroySemaphore(g_hDevice, g_hRenderFinishedSemaphore, g_Allocs); + g_hRenderFinishedSemaphore = VK_NULL_HANDLE; + } + + for(size_t i = g_Framebuffers.size(); i--; ) + vkDestroyFramebuffer(g_hDevice, g_Framebuffers[i], g_Allocs); + g_Framebuffers.clear(); + + if(g_hDepthImageView != VK_NULL_HANDLE) + { + vkDestroyImageView(g_hDevice, g_hDepthImageView, g_Allocs); + g_hDepthImageView = VK_NULL_HANDLE; + } + if(g_hDepthImage != VK_NULL_HANDLE) + { + vmaDestroyImage(g_hAllocator, g_hDepthImage, g_hDepthImageAlloc); + g_hDepthImage = VK_NULL_HANDLE; + } + + if(g_hPipeline != VK_NULL_HANDLE) + { + vkDestroyPipeline(g_hDevice, g_hPipeline, g_Allocs); + g_hPipeline = VK_NULL_HANDLE; + } + + if(g_hRenderPass != VK_NULL_HANDLE) + { + vkDestroyRenderPass(g_hDevice, g_hRenderPass, g_Allocs); + g_hRenderPass = VK_NULL_HANDLE; + } + + if(g_hPipelineLayout != VK_NULL_HANDLE) + { + vkDestroyPipelineLayout(g_hDevice, g_hPipelineLayout, g_Allocs); + g_hPipelineLayout = VK_NULL_HANDLE; + } + + for(size_t i = g_SwapchainImageViews.size(); i--; ) + vkDestroyImageView(g_hDevice, g_SwapchainImageViews[i], g_Allocs); + g_SwapchainImageViews.clear(); + + if(destroyActualSwapchain && (g_hSwapchain != VK_NULL_HANDLE)) + { + vkDestroySwapchainKHR(g_hDevice, g_hSwapchain, g_Allocs); + g_hSwapchain = VK_NULL_HANDLE; + } +} + +static void PrintEnabledFeatures() +{ + wprintf(L"Enabled extensions and features:\n"); + wprintf(L"Validation layer: %d\n", g_EnableValidationLayer ? 1 : 0); + wprintf(L"Sparse binding: %d\n", g_SparseBindingEnabled ? 1 : 0); + if(GetVulkanApiVersion() == VK_API_VERSION_1_0) + { + wprintf(L"VK_KHR_get_memory_requirements2: %d\n", VK_KHR_get_memory_requirements2_enabled ? 1 : 0); + wprintf(L"VK_KHR_get_physical_device_properties2: %d\n", VK_KHR_get_physical_device_properties2_enabled ? 1 : 0); + wprintf(L"VK_KHR_dedicated_allocation: %d\n", VK_KHR_dedicated_allocation_enabled ? 1 : 0); + wprintf(L"VK_KHR_bind_memory2: %d\n", VK_KHR_bind_memory2_enabled ? 1 : 0); + } + wprintf(L"VK_EXT_memory_budget: %d\n", VK_EXT_memory_budget_enabled ? 1 : 0); + wprintf(L"VK_AMD_device_coherent_memory: %d\n", VK_AMD_device_coherent_memory_enabled ? 1 : 0); + if(GetVulkanApiVersion() < VK_API_VERSION_1_2) + { + wprintf(L"VK_KHR_buffer_device_address: %d\n", VK_KHR_buffer_device_address_enabled ? 1 : 0); + } + else + { + wprintf(L"bufferDeviceAddress: %d\n", VK_KHR_buffer_device_address_enabled ? 1 : 0); + } + wprintf(L"VK_EXT_memory_priority: %d\n", VK_EXT_memory_priority ? 1 : 0); +} + +void SetAllocatorCreateInfo(VmaAllocatorCreateInfo& outInfo) +{ + outInfo = {}; + + outInfo.physicalDevice = g_hPhysicalDevice; + outInfo.device = g_hDevice; + outInfo.instance = g_hVulkanInstance; + outInfo.vulkanApiVersion = GetVulkanApiVersion(); + + if(VK_KHR_dedicated_allocation_enabled) + { + outInfo.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT; + } + if(VK_KHR_bind_memory2_enabled) + { + outInfo.flags |= VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT; + } +#if !defined(VMA_MEMORY_BUDGET) || VMA_MEMORY_BUDGET == 1 + if(VK_EXT_memory_budget_enabled && ( + GetVulkanApiVersion() >= VK_API_VERSION_1_1 || VK_KHR_get_physical_device_properties2_enabled)) + { + outInfo.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; + } +#endif + if(VK_AMD_device_coherent_memory_enabled) + { + outInfo.flags |= VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT; + } + if(VK_KHR_buffer_device_address_enabled) + { + outInfo.flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + } +#if !defined(VMA_MEMORY_PRIORITY) || VMA_MEMORY_PRIORITY == 1 + if(VK_EXT_memory_priority_enabled) + { + outInfo.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT; + } +#endif + + if(USE_CUSTOM_CPU_ALLOCATION_CALLBACKS) + { + outInfo.pAllocationCallbacks = &g_CpuAllocationCallbacks; + } + + // Uncomment to enable recording to CSV file. + /* + static VmaRecordSettings recordSettings = {}; + recordSettings.pFilePath = "VulkanSample.csv"; + outInfo.pRecordSettings = &recordSettings; + */ + + // Uncomment to enable HeapSizeLimit. + /* + static std::array<VkDeviceSize, VK_MAX_MEMORY_HEAPS> heapSizeLimit; + std::fill(heapSizeLimit.begin(), heapSizeLimit.end(), VK_WHOLE_SIZE); + heapSizeLimit[0] = 512ull * 1024 * 1024; + outInfo.pHeapSizeLimit = heapSizeLimit.data(); + */ +} + +static void PrintPhysicalDeviceProperties(const VkPhysicalDeviceProperties& properties) +{ + wprintf(L"physicalDeviceProperties:\n"); + wprintf(L" driverVersion: 0x%X\n", properties.driverVersion); + wprintf(L" vendorID: 0x%X (%s)\n", properties.vendorID, VendorIDToStr(properties.vendorID)); + wprintf(L" deviceID: 0x%X\n", properties.deviceID); + wprintf(L" deviceType: %u (%s)\n", properties.deviceType, PhysicalDeviceTypeToStr(properties.deviceType)); + wprintf(L" deviceName: %hs\n", properties.deviceName); + wprintf(L" limits:\n"); + wprintf(L" maxMemoryAllocationCount: %u\n", properties.limits.maxMemoryAllocationCount); + wprintf(L" bufferImageGranularity: %llu B\n", properties.limits.bufferImageGranularity); + wprintf(L" nonCoherentAtomSize: %llu B\n", properties.limits.nonCoherentAtomSize); +} + +#if VMA_VULKAN_VERSION >= 1002000 +static void PrintPhysicalDeviceVulkan11Properties(const VkPhysicalDeviceVulkan11Properties& properties) +{ + wprintf(L"physicalDeviceVulkan11Properties:\n"); + std::wstring sizeStr = SizeToStr(properties.maxMemoryAllocationSize); + wprintf(L" maxMemoryAllocationSize: %llu B (%s)\n", properties.maxMemoryAllocationSize, sizeStr.c_str()); +} +static void PrintPhysicalDeviceVulkan12Properties(const VkPhysicalDeviceVulkan12Properties& properties) +{ + wprintf(L"physicalDeviceVulkan12Properties:\n"); + std::wstring str = DriverIDToStr(properties.driverID); + wprintf(L" driverID: %u (%s)\n", properties.driverID, str.c_str()); + wprintf(L" driverName: %hs\n", properties.driverName); + wprintf(L" driverInfo: %hs\n", properties.driverInfo); +} +#endif // #if VMA_VULKAN_VERSION > 1002000 + +static void AddFlagToStr(std::wstring& inout, const wchar_t* flagStr) +{ + if(!inout.empty()) + inout += L", "; + inout += flagStr; +} + +static std::wstring HeapFlagsToStr(VkMemoryHeapFlags flags) +{ + std::wstring result; + if(flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) + AddFlagToStr(result, L"DEVICE_LOCAL"); + if(flags & VK_MEMORY_HEAP_MULTI_INSTANCE_BIT) + AddFlagToStr(result, L"MULTI_INSTANCE"); + return result; +} + +static std::wstring PropertyFlagsToStr(VkMemoryPropertyFlags flags) +{ + std::wstring result; + if(flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) + AddFlagToStr(result, L"DEVICE_LOCAL"); + if(flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) + AddFlagToStr(result, L"HOST_VISIBLE"); + if(flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) + AddFlagToStr(result, L"HOST_COHERENT"); + if(flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) + AddFlagToStr(result, L"HOST_CACHED"); + if(flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) + AddFlagToStr(result, L"LAZILY_ALLOCATED"); + +#if VMA_VULKAN_VERSION >= 1001000 + if(flags & VK_MEMORY_PROPERTY_PROTECTED_BIT) + AddFlagToStr(result, L"PROTECTED"); +#endif + +#if VK_AMD_device_coherent_memory + if(flags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD) + AddFlagToStr(result, L"DEVICE_COHERENT (AMD)"); + if(flags & VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD) + AddFlagToStr(result, L"DEVICE_UNCACHED (AMD)"); +#endif + + return result; +} + +static void PrintMemoryTypes() +{ + wprintf(L"MEMORY HEAPS:\n"); + const VkPhysicalDeviceMemoryProperties* memProps = nullptr; + vmaGetMemoryProperties(g_hAllocator, &memProps); + + wprintf(L"heapCount=%u, typeCount=%u\n", memProps->memoryHeapCount, memProps->memoryTypeCount); + + std::wstring sizeStr, flagsStr; + for(uint32_t heapIndex = 0; heapIndex < memProps->memoryHeapCount; ++heapIndex) + { + const VkMemoryHeap& heap = memProps->memoryHeaps[heapIndex]; + sizeStr = SizeToStr(heap.size); + flagsStr = HeapFlagsToStr(heap.flags); + wprintf(L"Heap %u: %llu B (%s) %s\n", heapIndex, heap.size, sizeStr.c_str(), flagsStr.c_str()); + + for(uint32_t typeIndex = 0; typeIndex < memProps->memoryTypeCount; ++typeIndex) + { + const VkMemoryType& type = memProps->memoryTypes[typeIndex]; + if(type.heapIndex == heapIndex) + { + flagsStr = PropertyFlagsToStr(type.propertyFlags); + wprintf(L" Type %u: %s\n", typeIndex, flagsStr.c_str()); + } + } + } +} + +#if 0 +template<typename It, typename MapFunc> +inline VkDeviceSize MapSum(It beg, It end, MapFunc mapFunc) +{ + VkDeviceSize result = 0; + for(It it = beg; it != end; ++it) + result += mapFunc(*it); + return result; +} +#endif + +static bool CanCreateVertexBuffer(uint32_t allowedMemoryTypeBits) +{ + VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufCreateInfo.size = 0x10000; + bufCreateInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + VkBuffer buf = VK_NULL_HANDLE; + VkResult res = vkCreateBuffer(g_hDevice, &bufCreateInfo, g_Allocs, &buf); + assert(res == VK_SUCCESS); + + VkMemoryRequirements memReq = {}; + vkGetBufferMemoryRequirements(g_hDevice, buf, &memReq); + + vkDestroyBuffer(g_hDevice, buf, g_Allocs); + + return (memReq.memoryTypeBits & allowedMemoryTypeBits) != 0; +} + +static bool CanCreateOptimalSampledImage(uint32_t allowedMemoryTypeBits) +{ + VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; + imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imgCreateInfo.extent.width = 256; + imgCreateInfo.extent.height = 256; + imgCreateInfo.extent.depth = 1; + imgCreateInfo.mipLevels = 1; + imgCreateInfo.arrayLayers = 1; + imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imgCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + VkImage img = VK_NULL_HANDLE; + VkResult res = vkCreateImage(g_hDevice, &imgCreateInfo, g_Allocs, &img); + assert(res == VK_SUCCESS); + + VkMemoryRequirements memReq = {}; + vkGetImageMemoryRequirements(g_hDevice, img, &memReq); + + vkDestroyImage(g_hDevice, img, g_Allocs); + + return (memReq.memoryTypeBits & allowedMemoryTypeBits) != 0; +} + +static void PrintMemoryConclusions() +{ + wprintf(L"Conclusions:\n"); + + const VkPhysicalDeviceProperties* props = nullptr; + const VkPhysicalDeviceMemoryProperties* memProps = nullptr; + vmaGetPhysicalDeviceProperties(g_hAllocator, &props); + vmaGetMemoryProperties(g_hAllocator, &memProps); + + const uint32_t heapCount = memProps->memoryHeapCount; + + uint32_t deviceLocalHeapCount = 0; + uint32_t hostVisibleHeapCount = 0; + uint32_t deviceLocalAndHostVisibleHeapCount = 0; + VkDeviceSize deviceLocalHeapSumSize = 0; + VkDeviceSize hostVisibleHeapSumSize = 0; + VkDeviceSize deviceLocalAndHostVisibleHeapSumSize = 0; + + for(uint32_t heapIndex = 0; heapIndex < heapCount; ++heapIndex) + { + const VkMemoryHeap& heap = memProps->memoryHeaps[heapIndex]; + const bool isDeviceLocal = (heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0; + bool isHostVisible = false; + for(uint32_t typeIndex = 0; typeIndex < memProps->memoryTypeCount; ++typeIndex) + { + const VkMemoryType& type = memProps->memoryTypes[typeIndex]; + if(type.heapIndex == heapIndex && (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) + { + isHostVisible = true; + break; + } + } + if(isDeviceLocal) + { + ++deviceLocalHeapCount; + deviceLocalHeapSumSize += heap.size; + } + if(isHostVisible) + { + ++hostVisibleHeapCount; + hostVisibleHeapSumSize += heap.size; + if(isDeviceLocal) + { + ++deviceLocalAndHostVisibleHeapCount; + deviceLocalAndHostVisibleHeapSumSize += heap.size; + } + } + } + + uint32_t hostVisibleNotHostCoherentTypeCount = 0; + uint32_t notDeviceLocalNotHostVisibleTypeCount = 0; + uint32_t amdSpecificTypeCount = 0; + uint32_t lazilyAllocatedTypeCount = 0; + uint32_t allTypeBits = 0; + uint32_t deviceLocalTypeBits = 0; + for(uint32_t typeIndex = 0; typeIndex < memProps->memoryTypeCount; ++typeIndex) + { + const VkMemoryType& type = memProps->memoryTypes[typeIndex]; + allTypeBits |= 1u << typeIndex; + if(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) + { + deviceLocalTypeBits |= 1u << typeIndex; + } + if((type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) && + (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) + { + ++hostVisibleNotHostCoherentTypeCount; + } + if((type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == 0 && + (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) + { + ++notDeviceLocalNotHostVisibleTypeCount; + } + if(type.propertyFlags & (VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD | VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD)) + { + ++amdSpecificTypeCount; + } + if(type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) + { + ++lazilyAllocatedTypeCount; + } + } + + assert(deviceLocalHeapCount > 0); + if(deviceLocalHeapCount == heapCount) + wprintf(L"- All heaps are DEVICE_LOCAL.\n"); + else + wprintf(L"- %u heaps are DEVICE_LOCAL, total %s.\n", deviceLocalHeapCount, SizeToStr(deviceLocalHeapSumSize).c_str()); + + assert(hostVisibleHeapCount > 0); + if(hostVisibleHeapCount == heapCount) + wprintf(L"- All heaps are HOST_VISIBLE.\n"); + else + wprintf(L"- %u heaps are HOST_VISIBLE, total %s.\n", deviceLocalHeapCount, SizeToStr(hostVisibleHeapSumSize).c_str()); + + if(deviceLocalHeapCount < heapCount && hostVisibleHeapCount < heapCount) + { + if(deviceLocalAndHostVisibleHeapCount == 0) + wprintf(L"- No heaps are DEVICE_LOCAL and HOST_VISIBLE.\n"); + if(deviceLocalAndHostVisibleHeapCount == heapCount) + wprintf(L"- All heaps are DEVICE_LOCAL and HOST_VISIBLE.\n"); + else + wprintf(L"- %u heaps are DEVICE_LOCAL and HOST_VISIBLE, total %s.\n", deviceLocalAndHostVisibleHeapCount, SizeToStr(deviceLocalAndHostVisibleHeapSumSize).c_str()); + } + + if(hostVisibleNotHostCoherentTypeCount == 0) + wprintf(L"- No types are HOST_VISIBLE but not HOST_COHERENT.\n"); + else + wprintf(L"- %u types are HOST_VISIBLE but not HOST_COHERENT.\n", hostVisibleNotHostCoherentTypeCount); + + if(notDeviceLocalNotHostVisibleTypeCount == 0) + wprintf(L"- No types are not DEVICE_LOCAL and not HOST_VISIBLE.\n"); + else + wprintf(L"- %u types are not DEVICE_LOCAL and not HOST_VISIBLE.\n", notDeviceLocalNotHostVisibleTypeCount); + + if(amdSpecificTypeCount == 0) + wprintf(L"- No types are AMD-specific DEVICE_COHERENT or DEVICE_UNCACHED.\n"); + else + wprintf(L"- %u types are AMD-specific DEVICE_COHERENT or DEVICE_UNCACHED.\n", amdSpecificTypeCount); + + if(lazilyAllocatedTypeCount == 0) + wprintf(L"- No types are LAZILY_ALLOCATED.\n"); + else + wprintf(L"- %u types are LAZILY_ALLOCATED.\n", lazilyAllocatedTypeCount); + + if(props->vendorID == VENDOR_ID_AMD && + props->deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && + deviceLocalAndHostVisibleHeapSumSize > 256llu * 1024 * 1024) + { + wprintf(L"- AMD Smart Access Memory (SAM) is enabled!\n"); + } + + if(deviceLocalHeapCount < heapCount) + { + const uint32_t nonDeviceLocalTypeBits = ~deviceLocalTypeBits & allTypeBits; + + if(CanCreateVertexBuffer(nonDeviceLocalTypeBits)) + wprintf(L"- A buffer with VERTEX_BUFFER usage can be created in some non-DEVICE_LOCAL type.\n"); + else + wprintf(L"- A buffer with VERTEX_BUFFER usage cannot be created in some non-DEVICE_LOCAL type.\n"); + + if(CanCreateOptimalSampledImage(nonDeviceLocalTypeBits)) + wprintf(L"- An image with OPTIMAL tiling and SAMPLED usage can be created in some non-DEVICE_LOCAL type.\n"); + else + wprintf(L"- An image with OPTIMAL tiling and SAMPLED usage cannot be created in some non-DEVICE_LOCAL type.\n"); + } + + //wprintf(L"\n"); +} + +static void InitializeApplication() +{ + // Create VkSurfaceKHR. + VkWin32SurfaceCreateInfoKHR surfaceInfo = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR }; + surfaceInfo.hinstance = g_hAppInstance; + surfaceInfo.hwnd = g_hWnd; + VkResult result = vkCreateWin32SurfaceKHR(g_hVulkanInstance, &surfaceInfo, g_Allocs, &g_hSurface); + assert(result == VK_SUCCESS); + + // Query for device extensions + + uint32_t physicalDeviceExtensionPropertyCount = 0; + ERR_GUARD_VULKAN( vkEnumerateDeviceExtensionProperties(g_hPhysicalDevice, nullptr, &physicalDeviceExtensionPropertyCount, nullptr) ); + std::vector<VkExtensionProperties> physicalDeviceExtensionProperties{physicalDeviceExtensionPropertyCount}; + if(physicalDeviceExtensionPropertyCount) + { + ERR_GUARD_VULKAN( vkEnumerateDeviceExtensionProperties( + g_hPhysicalDevice, + nullptr, + &physicalDeviceExtensionPropertyCount, + physicalDeviceExtensionProperties.data()) ); + } + + for(uint32_t i = 0; i < physicalDeviceExtensionPropertyCount; ++i) + { + if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME) == 0) + { + if(GetVulkanApiVersion() == VK_API_VERSION_1_0) + { + VK_KHR_get_memory_requirements2_enabled = true; + } + } + else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME) == 0) + { + if(GetVulkanApiVersion() == VK_API_VERSION_1_0) + { + VK_KHR_dedicated_allocation_enabled = true; + } + } + else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_KHR_BIND_MEMORY_2_EXTENSION_NAME) == 0) + { + if(GetVulkanApiVersion() == VK_API_VERSION_1_0) + { + VK_KHR_bind_memory2_enabled = true; + } + } + else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) == 0) + VK_EXT_memory_budget_enabled = true; + else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME) == 0) + VK_AMD_device_coherent_memory_enabled = true; + else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) == 0) + { + if(GetVulkanApiVersion() < VK_API_VERSION_1_2) + { + VK_KHR_buffer_device_address_enabled = true; + } + } + else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) == 0) + VK_EXT_memory_priority_enabled = true; + } + + if(GetVulkanApiVersion() >= VK_API_VERSION_1_2) + VK_KHR_buffer_device_address_enabled = true; // Promoted to core Vulkan 1.2. + + // Query for features + +#if VMA_VULKAN_VERSION >= 1001000 + VkPhysicalDeviceProperties2 physicalDeviceProperties2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; + +#if VMA_VULKAN_VERSION >= 1002000 + // Vulkan spec says structure VkPhysicalDeviceVulkan11Properties is "Provided by VK_VERSION_1_2" - is this a mistake? Assuming not... + VkPhysicalDeviceVulkan11Properties physicalDeviceVulkan11Properties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES }; + VkPhysicalDeviceVulkan12Properties physicalDeviceVulkan12Properties = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES }; + PnextChainPushFront(&physicalDeviceProperties2, &physicalDeviceVulkan11Properties); + PnextChainPushFront(&physicalDeviceProperties2, &physicalDeviceVulkan12Properties); +#endif + + vkGetPhysicalDeviceProperties2(g_hPhysicalDevice, &physicalDeviceProperties2); + + PrintPhysicalDeviceProperties(physicalDeviceProperties2.properties); +#if VMA_VULKAN_VERSION >= 1002000 + PrintPhysicalDeviceVulkan11Properties(physicalDeviceVulkan11Properties); + PrintPhysicalDeviceVulkan12Properties(physicalDeviceVulkan12Properties); +#endif + +#else // #if VMA_VULKAN_VERSION >= 1001000 + VkPhysicalDeviceProperties physicalDeviceProperties = {}; + vkGetPhysicalDeviceProperties(g_hPhysicalDevice, &physicalDeviceProperties); + PrintPhysicalDeviceProperties(physicalDeviceProperties); + +#endif // #if VMA_VULKAN_VERSION >= 1001000 + + wprintf(L"\n"); + + VkPhysicalDeviceFeatures2 physicalDeviceFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; + + VkPhysicalDeviceCoherentMemoryFeaturesAMD physicalDeviceCoherentMemoryFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COHERENT_MEMORY_FEATURES_AMD }; + if(VK_AMD_device_coherent_memory_enabled) + { + PnextChainPushFront(&physicalDeviceFeatures, &physicalDeviceCoherentMemoryFeatures); + } + + VkPhysicalDeviceBufferDeviceAddressFeaturesKHR physicalDeviceBufferDeviceAddressFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR }; + if(VK_KHR_buffer_device_address_enabled) + { + PnextChainPushFront(&physicalDeviceFeatures, &physicalDeviceBufferDeviceAddressFeatures); + } + + VkPhysicalDeviceMemoryPriorityFeaturesEXT physicalDeviceMemoryPriorityFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT }; + if(VK_EXT_memory_priority_enabled) + { + PnextChainPushFront(&physicalDeviceFeatures, &physicalDeviceMemoryPriorityFeatures); + } + + vkGetPhysicalDeviceFeatures2(g_hPhysicalDevice, &physicalDeviceFeatures); + + g_SparseBindingEnabled = physicalDeviceFeatures.features.sparseBinding != 0; + + // The extension is supported as fake with no real support for this feature? Don't use it. + if(VK_AMD_device_coherent_memory_enabled && !physicalDeviceCoherentMemoryFeatures.deviceCoherentMemory) + VK_AMD_device_coherent_memory_enabled = false; + if(VK_KHR_buffer_device_address_enabled && !physicalDeviceBufferDeviceAddressFeatures.bufferDeviceAddress) + VK_KHR_buffer_device_address_enabled = false; + if(VK_EXT_memory_priority_enabled && !physicalDeviceMemoryPriorityFeatures.memoryPriority) + VK_EXT_memory_priority_enabled = false; + + // Find queue family index + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(g_hPhysicalDevice, &queueFamilyCount, nullptr); + assert(queueFamilyCount > 0); + std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(g_hPhysicalDevice, &queueFamilyCount, queueFamilies.data()); + for(uint32_t i = 0; + (i < queueFamilyCount) && + (g_GraphicsQueueFamilyIndex == UINT_MAX || + g_PresentQueueFamilyIndex == UINT_MAX || + (g_SparseBindingEnabled && g_SparseBindingQueueFamilyIndex == UINT_MAX)); + ++i) + { + if(queueFamilies[i].queueCount > 0) + { + const uint32_t flagsForGraphicsQueue = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT; + if((g_GraphicsQueueFamilyIndex != 0) && + ((queueFamilies[i].queueFlags & flagsForGraphicsQueue) == flagsForGraphicsQueue)) + { + g_GraphicsQueueFamilyIndex = i; + } + + VkBool32 surfaceSupported = 0; + VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR(g_hPhysicalDevice, i, g_hSurface, &surfaceSupported); + if((res >= 0) && (surfaceSupported == VK_TRUE)) + { + g_PresentQueueFamilyIndex = i; + } + + if(g_SparseBindingEnabled && + g_SparseBindingQueueFamilyIndex == UINT32_MAX && + (queueFamilies[i].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0) + { + g_SparseBindingQueueFamilyIndex = i; + } + } + } + assert(g_GraphicsQueueFamilyIndex != UINT_MAX); + + g_SparseBindingEnabled = g_SparseBindingEnabled && g_SparseBindingQueueFamilyIndex != UINT32_MAX; + + // Create logical device + + const float queuePriority = 1.f; + + VkDeviceQueueCreateInfo queueCreateInfo[3] = {}; + uint32_t queueCount = 1; + queueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo[0].queueFamilyIndex = g_GraphicsQueueFamilyIndex; + queueCreateInfo[0].queueCount = 1; + queueCreateInfo[0].pQueuePriorities = &queuePriority; + + if(g_PresentQueueFamilyIndex != g_GraphicsQueueFamilyIndex) + { + + queueCreateInfo[queueCount].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo[queueCount].queueFamilyIndex = g_PresentQueueFamilyIndex; + queueCreateInfo[queueCount].queueCount = 1; + queueCreateInfo[queueCount].pQueuePriorities = &queuePriority; + ++queueCount; + } + + if(g_SparseBindingEnabled && + g_SparseBindingQueueFamilyIndex != g_GraphicsQueueFamilyIndex && + g_SparseBindingQueueFamilyIndex != g_PresentQueueFamilyIndex) + { + + queueCreateInfo[queueCount].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo[queueCount].queueFamilyIndex = g_SparseBindingQueueFamilyIndex; + queueCreateInfo[queueCount].queueCount = 1; + queueCreateInfo[queueCount].pQueuePriorities = &queuePriority; + ++queueCount; + } + + std::vector<const char*> enabledDeviceExtensions; + enabledDeviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + if(VK_KHR_get_memory_requirements2_enabled) + enabledDeviceExtensions.push_back(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME); + if(VK_KHR_dedicated_allocation_enabled) + enabledDeviceExtensions.push_back(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + if(VK_KHR_bind_memory2_enabled) + enabledDeviceExtensions.push_back(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME); + if(VK_EXT_memory_budget_enabled) + enabledDeviceExtensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); + if(VK_AMD_device_coherent_memory_enabled) + enabledDeviceExtensions.push_back(VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME); + if(VK_KHR_buffer_device_address_enabled && GetVulkanApiVersion() < VK_API_VERSION_1_2) + enabledDeviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + if(VK_EXT_memory_priority_enabled) + enabledDeviceExtensions.push_back(VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME); + + VkPhysicalDeviceFeatures2 deviceFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; + deviceFeatures.features.samplerAnisotropy = VK_TRUE; + deviceFeatures.features.sparseBinding = g_SparseBindingEnabled ? VK_TRUE : VK_FALSE; + + if(VK_AMD_device_coherent_memory_enabled) + { + physicalDeviceCoherentMemoryFeatures.deviceCoherentMemory = VK_TRUE; + PnextChainPushBack(&deviceFeatures, &physicalDeviceCoherentMemoryFeatures); + } + if(VK_KHR_buffer_device_address_enabled) + { + physicalDeviceBufferDeviceAddressFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR }; + physicalDeviceBufferDeviceAddressFeatures.bufferDeviceAddress = VK_TRUE; + PnextChainPushBack(&deviceFeatures, &physicalDeviceBufferDeviceAddressFeatures); + } + if(VK_EXT_memory_priority_enabled) + { + PnextChainPushBack(&deviceFeatures, &physicalDeviceMemoryPriorityFeatures); + } + + VkDeviceCreateInfo deviceCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; + deviceCreateInfo.pNext = &deviceFeatures; + deviceCreateInfo.enabledLayerCount = 0; + deviceCreateInfo.ppEnabledLayerNames = nullptr; + deviceCreateInfo.enabledExtensionCount = (uint32_t)enabledDeviceExtensions.size(); + deviceCreateInfo.ppEnabledExtensionNames = !enabledDeviceExtensions.empty() ? enabledDeviceExtensions.data() : nullptr; + deviceCreateInfo.queueCreateInfoCount = queueCount; + deviceCreateInfo.pQueueCreateInfos = queueCreateInfo; + + ERR_GUARD_VULKAN( vkCreateDevice(g_hPhysicalDevice, &deviceCreateInfo, g_Allocs, &g_hDevice) ); + + // Fetch pointers to extension functions + if(VK_KHR_buffer_device_address_enabled) + { + if(GetVulkanApiVersion() >= VK_API_VERSION_1_2) + { + g_vkGetBufferDeviceAddressKHR = (PFN_vkGetBufferDeviceAddressEXT)vkGetDeviceProcAddr(g_hDevice, "vkGetBufferDeviceAddress"); + } + else if(VK_KHR_buffer_device_address_enabled) + { + g_vkGetBufferDeviceAddressKHR = (PFN_vkGetBufferDeviceAddressEXT)vkGetDeviceProcAddr(g_hDevice, "vkGetBufferDeviceAddressKHR"); + } + assert(g_vkGetBufferDeviceAddressKHR != nullptr); + } + + // Create memory allocator + + VmaAllocatorCreateInfo allocatorInfo = {}; + SetAllocatorCreateInfo(allocatorInfo); + ERR_GUARD_VULKAN( vmaCreateAllocator(&allocatorInfo, &g_hAllocator) ); + + PrintMemoryTypes(); + wprintf(L"\n"); + PrintMemoryConclusions(); + wprintf(L"\n"); + PrintEnabledFeatures(); + wprintf(L"\n"); + + // Retrieve queues (don't need to be destroyed). + + vkGetDeviceQueue(g_hDevice, g_GraphicsQueueFamilyIndex, 0, &g_hGraphicsQueue); + vkGetDeviceQueue(g_hDevice, g_PresentQueueFamilyIndex, 0, &g_hPresentQueue); + assert(g_hGraphicsQueue); + assert(g_hPresentQueue); + + if(g_SparseBindingEnabled) + { + vkGetDeviceQueue(g_hDevice, g_SparseBindingQueueFamilyIndex, 0, &g_hSparseBindingQueue); + assert(g_hSparseBindingQueue); + } + + // Create command pool + + VkCommandPoolCreateInfo commandPoolInfo = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; + commandPoolInfo.queueFamilyIndex = g_GraphicsQueueFamilyIndex; + commandPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + ERR_GUARD_VULKAN( vkCreateCommandPool(g_hDevice, &commandPoolInfo, g_Allocs, &g_hCommandPool) ); + + VkCommandBufferAllocateInfo commandBufferInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; + commandBufferInfo.commandPool = g_hCommandPool; + commandBufferInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + commandBufferInfo.commandBufferCount = COMMAND_BUFFER_COUNT; + ERR_GUARD_VULKAN( vkAllocateCommandBuffers(g_hDevice, &commandBufferInfo, g_MainCommandBuffers) ); + + VkFenceCreateInfo fenceInfo = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + for(size_t i = 0; i < COMMAND_BUFFER_COUNT; ++i) + { + ERR_GUARD_VULKAN( vkCreateFence(g_hDevice, &fenceInfo, g_Allocs, &g_MainCommandBufferExecutedFances[i]) ); + } + + ERR_GUARD_VULKAN( vkCreateFence(g_hDevice, &fenceInfo, g_Allocs, &g_ImmediateFence) ); + + commandBufferInfo.commandBufferCount = 1; + ERR_GUARD_VULKAN( vkAllocateCommandBuffers(g_hDevice, &commandBufferInfo, &g_hTemporaryCommandBuffer) ); + + // Create texture sampler + + VkSamplerCreateInfo samplerInfo = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = 16; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipLodBias = 0.f; + samplerInfo.minLod = 0.f; + samplerInfo.maxLod = FLT_MAX; + ERR_GUARD_VULKAN( vkCreateSampler(g_hDevice, &samplerInfo, g_Allocs, &g_hSampler) ); + + CreateTexture(128, 128); + CreateMesh(); + + VkDescriptorSetLayoutBinding samplerLayoutBinding = {}; + samplerLayoutBinding.binding = 1; + samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerLayoutBinding.descriptorCount = 1; + samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; + descriptorSetLayoutInfo.bindingCount = 1; + descriptorSetLayoutInfo.pBindings = &samplerLayoutBinding; + ERR_GUARD_VULKAN( vkCreateDescriptorSetLayout(g_hDevice, &descriptorSetLayoutInfo, g_Allocs, &g_hDescriptorSetLayout) ); + + // Create descriptor pool + + VkDescriptorPoolSize descriptorPoolSizes[2]; + ZeroMemory(descriptorPoolSizes, sizeof(descriptorPoolSizes)); + descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorPoolSizes[0].descriptorCount = 1; + descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorPoolSizes[1].descriptorCount = 1; + + VkDescriptorPoolCreateInfo descriptorPoolInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; + descriptorPoolInfo.poolSizeCount = (uint32_t)_countof(descriptorPoolSizes); + descriptorPoolInfo.pPoolSizes = descriptorPoolSizes; + descriptorPoolInfo.maxSets = 1; + ERR_GUARD_VULKAN( vkCreateDescriptorPool(g_hDevice, &descriptorPoolInfo, g_Allocs, &g_hDescriptorPool) ); + + // Create descriptor set layout + + VkDescriptorSetLayout descriptorSetLayouts[] = { g_hDescriptorSetLayout }; + VkDescriptorSetAllocateInfo descriptorSetInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; + descriptorSetInfo.descriptorPool = g_hDescriptorPool; + descriptorSetInfo.descriptorSetCount = 1; + descriptorSetInfo.pSetLayouts = descriptorSetLayouts; + ERR_GUARD_VULKAN( vkAllocateDescriptorSets(g_hDevice, &descriptorSetInfo, &g_hDescriptorSet) ); + + VkDescriptorImageInfo descriptorImageInfo = {}; + descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + descriptorImageInfo.imageView = g_hTextureImageView; + descriptorImageInfo.sampler = g_hSampler; + + VkWriteDescriptorSet writeDescriptorSet = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; + writeDescriptorSet.dstSet = g_hDescriptorSet; + writeDescriptorSet.dstBinding = 1; + writeDescriptorSet.dstArrayElement = 0; + writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writeDescriptorSet.descriptorCount = 1; + writeDescriptorSet.pImageInfo = &descriptorImageInfo; + + vkUpdateDescriptorSets(g_hDevice, 1, &writeDescriptorSet, 0, nullptr); + + CreateSwapchain(); +} + +static void FinalizeApplication() +{ + vkDeviceWaitIdle(g_hDevice); + + DestroySwapchain(true); + + if(g_hDescriptorPool != VK_NULL_HANDLE) + { + vkDestroyDescriptorPool(g_hDevice, g_hDescriptorPool, g_Allocs); + g_hDescriptorPool = VK_NULL_HANDLE; + } + + if(g_hDescriptorSetLayout != VK_NULL_HANDLE) + { + vkDestroyDescriptorSetLayout(g_hDevice, g_hDescriptorSetLayout, g_Allocs); + g_hDescriptorSetLayout = VK_NULL_HANDLE; + } + + if(g_hTextureImageView != VK_NULL_HANDLE) + { + vkDestroyImageView(g_hDevice, g_hTextureImageView, g_Allocs); + g_hTextureImageView = VK_NULL_HANDLE; + } + if(g_hTextureImage != VK_NULL_HANDLE) + { + vmaDestroyImage(g_hAllocator, g_hTextureImage, g_hTextureImageAlloc); + g_hTextureImage = VK_NULL_HANDLE; + } + + if(g_hIndexBuffer != VK_NULL_HANDLE) + { + vmaDestroyBuffer(g_hAllocator, g_hIndexBuffer, g_hIndexBufferAlloc); + g_hIndexBuffer = VK_NULL_HANDLE; + } + if(g_hVertexBuffer != VK_NULL_HANDLE) + { + vmaDestroyBuffer(g_hAllocator, g_hVertexBuffer, g_hVertexBufferAlloc); + g_hVertexBuffer = VK_NULL_HANDLE; + } + + if(g_hSampler != VK_NULL_HANDLE) + { + vkDestroySampler(g_hDevice, g_hSampler, g_Allocs); + g_hSampler = VK_NULL_HANDLE; + } + + if(g_ImmediateFence) + { + vkDestroyFence(g_hDevice, g_ImmediateFence, g_Allocs); + g_ImmediateFence = VK_NULL_HANDLE; + } + + for(size_t i = COMMAND_BUFFER_COUNT; i--; ) + { + if(g_MainCommandBufferExecutedFances[i] != VK_NULL_HANDLE) + { + vkDestroyFence(g_hDevice, g_MainCommandBufferExecutedFances[i], g_Allocs); + g_MainCommandBufferExecutedFances[i] = VK_NULL_HANDLE; + } + } + if(g_MainCommandBuffers[0] != VK_NULL_HANDLE) + { + vkFreeCommandBuffers(g_hDevice, g_hCommandPool, COMMAND_BUFFER_COUNT, g_MainCommandBuffers); + ZeroMemory(g_MainCommandBuffers, sizeof(g_MainCommandBuffers)); + } + if(g_hTemporaryCommandBuffer != VK_NULL_HANDLE) + { + vkFreeCommandBuffers(g_hDevice, g_hCommandPool, 1, &g_hTemporaryCommandBuffer); + g_hTemporaryCommandBuffer = VK_NULL_HANDLE; + } + + if(g_hCommandPool != VK_NULL_HANDLE) + { + vkDestroyCommandPool(g_hDevice, g_hCommandPool, g_Allocs); + g_hCommandPool = VK_NULL_HANDLE; + } + + if(g_hAllocator != VK_NULL_HANDLE) + { + vmaDestroyAllocator(g_hAllocator); + g_hAllocator = nullptr; + } + + if(g_hDevice != VK_NULL_HANDLE) + { + vkDestroyDevice(g_hDevice, g_Allocs); + g_hDevice = nullptr; + } + + if(g_hSurface != VK_NULL_HANDLE) + { + vkDestroySurfaceKHR(g_hVulkanInstance, g_hSurface, g_Allocs); + g_hSurface = VK_NULL_HANDLE; + } +} + +static void PrintAllocatorStats() +{ +#if VMA_STATS_STRING_ENABLED + char* statsString = nullptr; + vmaBuildStatsString(g_hAllocator, &statsString, true); + printf("%s\n", statsString); + vmaFreeStatsString(g_hAllocator, statsString); +#endif +} + +static void RecreateSwapChain() +{ + vkDeviceWaitIdle(g_hDevice); + DestroySwapchain(false); + CreateSwapchain(); +} + +static void DrawFrame() +{ + // Begin main command buffer + size_t cmdBufIndex = (g_NextCommandBufferIndex++) % COMMAND_BUFFER_COUNT; + VkCommandBuffer hCommandBuffer = g_MainCommandBuffers[cmdBufIndex]; + VkFence hCommandBufferExecutedFence = g_MainCommandBufferExecutedFances[cmdBufIndex]; + + ERR_GUARD_VULKAN( vkWaitForFences(g_hDevice, 1, &hCommandBufferExecutedFence, VK_TRUE, UINT64_MAX) ); + ERR_GUARD_VULKAN( vkResetFences(g_hDevice, 1, &hCommandBufferExecutedFence) ); + + VkCommandBufferBeginInfo commandBufferBeginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + ERR_GUARD_VULKAN( vkBeginCommandBuffer(hCommandBuffer, &commandBufferBeginInfo) ); + + // Acquire swapchain image + uint32_t imageIndex = 0; + VkResult res = vkAcquireNextImageKHR(g_hDevice, g_hSwapchain, UINT64_MAX, g_hImageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + if(res == VK_ERROR_OUT_OF_DATE_KHR) + { + RecreateSwapChain(); + return; + } + else if(res < 0) + { + ERR_GUARD_VULKAN(res); + } + + // Record geometry pass + + VkClearValue clearValues[2]; + ZeroMemory(clearValues, sizeof(clearValues)); + clearValues[0].color.float32[0] = 0.25f; + clearValues[0].color.float32[1] = 0.25f; + clearValues[0].color.float32[2] = 0.5f; + clearValues[0].color.float32[3] = 1.0f; + clearValues[1].depthStencil.depth = 1.0f; + + VkRenderPassBeginInfo renderPassBeginInfo = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; + renderPassBeginInfo.renderPass = g_hRenderPass; + renderPassBeginInfo.framebuffer = g_Framebuffers[imageIndex]; + renderPassBeginInfo.renderArea.offset.x = 0; + renderPassBeginInfo.renderArea.offset.y = 0; + renderPassBeginInfo.renderArea.extent = g_Extent; + renderPassBeginInfo.clearValueCount = (uint32_t)_countof(clearValues); + renderPassBeginInfo.pClearValues = clearValues; + vkCmdBeginRenderPass(hCommandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline( + hCommandBuffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + g_hPipeline); + + mat4 view = mat4::LookAt( + vec3(0.f, 0.f, 0.f), + vec3(0.f, -2.f, 4.f), + vec3(0.f, 1.f, 0.f)); + mat4 proj = mat4::Perspective( + 1.0471975511966f, // 60 degrees + (float)g_Extent.width / (float)g_Extent.height, + 0.1f, + 1000.f); + mat4 viewProj = view * proj; + + vkCmdBindDescriptorSets( + hCommandBuffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + g_hPipelineLayout, + 0, + 1, + &g_hDescriptorSet, + 0, + nullptr); + + float rotationAngle = (float)GetTickCount() * 0.001f * (float)PI * 0.2f; + mat4 model = mat4::RotationY(rotationAngle); + + UniformBufferObject ubo = {}; + ubo.ModelViewProj = model * viewProj; + vkCmdPushConstants(hCommandBuffer, g_hPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(UniformBufferObject), &ubo); + + VkBuffer vertexBuffers[] = { g_hVertexBuffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(hCommandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(hCommandBuffer, g_hIndexBuffer, 0, VK_INDEX_TYPE_UINT16); + + vkCmdDrawIndexed(hCommandBuffer, g_IndexCount, 1, 0, 0, 0); + + vkCmdEndRenderPass(hCommandBuffer); + + vkEndCommandBuffer(hCommandBuffer); + + // Submit command buffer + + VkSemaphore submitWaitSemaphores[] = { g_hImageAvailableSemaphore }; + VkPipelineStageFlags submitWaitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + VkSemaphore submitSignalSemaphores[] = { g_hRenderFinishedSemaphore }; + VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = submitWaitSemaphores; + submitInfo.pWaitDstStageMask = submitWaitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &hCommandBuffer; + submitInfo.signalSemaphoreCount = _countof(submitSignalSemaphores); + submitInfo.pSignalSemaphores = submitSignalSemaphores; + ERR_GUARD_VULKAN( vkQueueSubmit(g_hGraphicsQueue, 1, &submitInfo, hCommandBufferExecutedFence) ); + + VkSemaphore presentWaitSemaphores[] = { g_hRenderFinishedSemaphore }; + + VkSwapchainKHR swapchains[] = { g_hSwapchain }; + VkPresentInfoKHR presentInfo = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; + presentInfo.waitSemaphoreCount = _countof(presentWaitSemaphores); + presentInfo.pWaitSemaphores = presentWaitSemaphores; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapchains; + presentInfo.pImageIndices = &imageIndex; + presentInfo.pResults = nullptr; + res = vkQueuePresentKHR(g_hPresentQueue, &presentInfo); + if(res == VK_ERROR_OUT_OF_DATE_KHR) + { + RecreateSwapChain(); + } + else + ERR_GUARD_VULKAN(res); +} + +static void HandlePossibleSizeChange() +{ + RECT clientRect; + GetClientRect(g_hWnd, &clientRect); + LONG newSizeX = clientRect.right - clientRect.left; + LONG newSizeY = clientRect.bottom - clientRect.top; + if((newSizeX > 0) && + (newSizeY > 0) && + ((newSizeX != g_SizeX) || (newSizeY != g_SizeY))) + { + g_SizeX = newSizeX; + g_SizeY = newSizeY; + + RecreateSwapChain(); + } +} + +#define CATCH_PRINT_ERROR(extraCatchCode) \ + catch(const std::exception& ex) \ + { \ + fwprintf(stderr, L"ERROR: %hs\n", ex.what()); \ + extraCatchCode \ + } \ + catch(...) \ + { \ + fwprintf(stderr, L"UNKNOWN ERROR.\n"); \ + extraCatchCode \ + } + +static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) + { + case WM_CREATE: + // This is intentionally assigned here because we are now inside CreateWindow, before it returns. + g_hWnd = hWnd; + try + { + InitializeApplication(); + } + CATCH_PRINT_ERROR(return -1;) + //PrintAllocatorStats(); + return 0; + + case WM_DESTROY: + try + { + FinalizeApplication(); + } + CATCH_PRINT_ERROR(;) + PostQuitMessage(0); + return 0; + + // This prevents app from freezing when left Alt is pressed + // (which normally enters modal menu loop). + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + return 0; + + case WM_SIZE: + if((wParam == SIZE_MAXIMIZED) || (wParam == SIZE_RESTORED)) + { + try + { + HandlePossibleSizeChange(); + } + CATCH_PRINT_ERROR(DestroyWindow(hWnd);) + } + return 0; + + case WM_EXITSIZEMOVE: + try + { + HandlePossibleSizeChange(); + } + CATCH_PRINT_ERROR(DestroyWindow(hWnd);) + return 0; + + case WM_KEYDOWN: + switch(wParam) + { + case VK_ESCAPE: + PostMessage(hWnd, WM_CLOSE, 0, 0); + break; + case 'T': + try + { + Test(); + } + CATCH_PRINT_ERROR(;) + break; + case 'S': + try + { + if(g_SparseBindingEnabled) + { + try + { + TestSparseBinding(); + } + CATCH_PRINT_ERROR(;) + } + else + { + printf("Sparse binding not supported.\n"); + } + } + catch(const std::exception& ex) + { + printf("ERROR: %s\n", ex.what()); + } + break; + } + return 0; + + default: + break; + } + + return DefWindowProc(hWnd, msg, wParam, lParam); +} + +static void PrintLogo() +{ + wprintf(L"%s\n", APP_TITLE_W); +} + +static void PrintHelp() +{ + wprintf( + L"Command line syntax:\n" + L"-h, --Help Print this information\n" + L"-l, --List Print list of GPUs\n" + L"-g S, --GPU S Select GPU with name containing S\n" + L"-i N, --GPUIndex N Select GPU index N\n" + ); +} + +int MainWindow() +{ + WNDCLASSEX wndClassDesc = { sizeof(WNDCLASSEX) }; + wndClassDesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS; + wndClassDesc.hbrBackground = NULL; + wndClassDesc.hCursor = LoadCursor(NULL, IDC_CROSS); + wndClassDesc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wndClassDesc.hInstance = g_hAppInstance; + wndClassDesc.lpfnWndProc = WndProc; + wndClassDesc.lpszClassName = WINDOW_CLASS_NAME; + + const ATOM hWndClass = RegisterClassEx(&wndClassDesc); + assert(hWndClass); + + const DWORD style = WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME; + const DWORD exStyle = 0; + + RECT rect = { 0, 0, g_SizeX, g_SizeY }; + AdjustWindowRectEx(&rect, style, FALSE, exStyle); + + CreateWindowEx( + exStyle, WINDOW_CLASS_NAME, APP_TITLE_W, style, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, g_hAppInstance, NULL); + + MSG msg; + for(;;) + { + if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + if(msg.message == WM_QUIT) + break; + TranslateMessage(&msg); + DispatchMessage(&msg); + } + if(g_hDevice != VK_NULL_HANDLE) + DrawFrame(); + } + + return (int)msg.wParam;; +} + +int Main2(int argc, wchar_t** argv) +{ + PrintLogo(); + + if(!g_CommandLineParameters.Parse(argc, argv)) + { + wprintf(L"ERROR: Invalid command line syntax.\n"); + PrintHelp(); + return (int)ExitCode::CommandLineError; + } + + if(g_CommandLineParameters.m_Help) + { + PrintHelp(); + return (int)ExitCode::Help; + } + + VulkanUsage vulkanUsage; + vulkanUsage.Init(); + + if(g_CommandLineParameters.m_List) + { + vulkanUsage.PrintPhysicalDeviceList(); + return (int)ExitCode::GPUList; + } + + g_hPhysicalDevice = vulkanUsage.SelectPhysicalDevice(g_CommandLineParameters.m_GPUSelection); + TEST(g_hPhysicalDevice); + + return MainWindow(); +} + +int wmain(int argc, wchar_t** argv) +{ + try + { + return Main2(argc, argv); + TEST(g_CpuAllocCount.load() == 0); + } + CATCH_PRINT_ERROR(return (int)ExitCode::RuntimeError;) +} + +#else // #ifdef _WIN32 + +#include "VmaUsage.h" + +int main() +{ +} + +#endif // #ifdef _WIN32
diff --git a/src/vk_mem_alloc.natvis b/src/vk_mem_alloc.natvis index 0477fa7..85c7533 100644 --- a/src/vk_mem_alloc.natvis +++ b/src/vk_mem_alloc.natvis
@@ -1,40 +1,40 @@ -<?xml version="1.0" encoding="utf-8"?> -<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> - <Type Name="VmaRawList<*>"> - <DisplayString>{{ Count={m_Count} }}</DisplayString> - <Expand> - <Item Name="[Count]">m_Count</Item> - <LinkedListItems> - <Size>m_Count</Size> - <HeadPointer>m_pFront</HeadPointer> - <NextPointer>pNext</NextPointer> - <ValueNode>Value</ValueNode> - </LinkedListItems> - </Expand> - </Type> - - <Type Name="VmaList<*>"> - <DisplayString>{{ Count={m_RawList.m_Count} }}</DisplayString> - <Expand> - <Item Name="[Count]">m_RawList.m_Count</Item> - <LinkedListItems> - <Size>m_RawList.m_Count</Size> - <HeadPointer>m_RawList.m_pFront</HeadPointer> - <NextPointer>pNext</NextPointer> - <ValueNode>Value</ValueNode> - </LinkedListItems> - </Expand> - </Type> - - <Type Name="VmaVector<*>"> - <DisplayString>{{ Count={m_Count} }}</DisplayString> - <Expand> - <Item Name="[Count]">m_Count</Item> - <Item Name="[Capacity]">m_Capacity</Item> - <ArrayItems> - <Size>m_Count</Size> - <ValuePointer>m_pArray</ValuePointer> - </ArrayItems> - </Expand> - </Type> +<?xml version="1.0" encoding="utf-8"?> +<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> + <Type Name="VmaRawList<*>"> + <DisplayString>{{ Count={m_Count} }}</DisplayString> + <Expand> + <Item Name="[Count]">m_Count</Item> + <LinkedListItems> + <Size>m_Count</Size> + <HeadPointer>m_pFront</HeadPointer> + <NextPointer>pNext</NextPointer> + <ValueNode>Value</ValueNode> + </LinkedListItems> + </Expand> + </Type> + + <Type Name="VmaList<*>"> + <DisplayString>{{ Count={m_RawList.m_Count} }}</DisplayString> + <Expand> + <Item Name="[Count]">m_RawList.m_Count</Item> + <LinkedListItems> + <Size>m_RawList.m_Count</Size> + <HeadPointer>m_RawList.m_pFront</HeadPointer> + <NextPointer>pNext</NextPointer> + <ValueNode>Value</ValueNode> + </LinkedListItems> + </Expand> + </Type> + + <Type Name="VmaVector<*>"> + <DisplayString>{{ Count={m_Count} }}</DisplayString> + <Expand> + <Item Name="[Count]">m_Count</Item> + <Item Name="[Capacity]">m_Capacity</Item> + <ArrayItems> + <Size>m_Count</Size> + <ValuePointer>m_pArray</ValuePointer> + </ArrayItems> + </Expand> + </Type> </AutoVisualizer> \ No newline at end of file
diff --git a/tools/VmaDumpVis/README.md b/tools/VmaDumpVis/README.md index 97a396b..f238001 100644 --- a/tools/VmaDumpVis/README.md +++ b/tools/VmaDumpVis/README.md
@@ -1,42 +1,42 @@ -# VMA Dump Vis - -Vulkan Memory Allocator Dump Visualization. It is an auxiliary tool that can visualize internal state of [Vulkan Memory Allocator](../../README.md) library on a picture. It is a Python script that must be launched from command line with appropriate parameters. - -## Requirements - -- Python 3 installed -- [Pillow](http://python-pillow.org/) - Python Imaging Library (Fork) installed - -## Usage - -``` -python VmaDumpVis.py -o OUTPUT_FILE INPUT_FILE -``` - -* `INPUT_FILE` - path to source file to be read, containing dump of internal state of the VMA library in JSON format (encoding: UTF-8), generated using `vmaBuildStatsString()` function. -* `OUTPUT_FILE` - path to destination file to be written that will contain generated image. Image format is automatically recognized based on file extension. List of supported formats can be found [here](http://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html) and includes: BMP, GIF, JPEG, PNG, TGA. - -You can also use typical options: - -* `-h` - to see help on command line syntax -* `-v` - to see program version number - -## Example output - - - -## Legend - -*  Light gray without border - a space in Vulkan device memory block unused by any allocation. -*  Buffer with usage containing INDIRECT_BUFFER, VERTEX_BUFFER, or INDEX_BUFFER. -*  Buffer with usage containing STORAGE_BUFFER or STORAGE_TEXEL_BUFFER. -*  Buffer with usage containing UNIFORM_BUFFER or UNIFORM_TEXEL_BUFFER. -*  Other buffer. -*  Image with OPTIMAL tiling and usage containing DEPTH_STENCIL_ATTACHMENT. -*  Image with OPTIMAL tiling and usage containing INPUT_ATTACHMENT, TRANSIENT_ATTACHMENT, or COLOR_ATTACHMENT. -*  Image with OPTIMAL tiling and usage containing SAMPLED. -*  Other image with OPTIMAL tiling. -*  Image with LINEAR tiling. -*  Image with tiling unknown to the allocator. -*  Allocation of unknown type. -*  Black bar - one or more allocations of any kind too small to be visualized as filled rectangles. +# VMA Dump Vis + +Vulkan Memory Allocator Dump Visualization. It is an auxiliary tool that can visualize internal state of [Vulkan Memory Allocator](../../README.md) library on a picture. It is a Python script that must be launched from command line with appropriate parameters. + +## Requirements + +- Python 3 installed +- [Pillow](http://python-pillow.org/) - Python Imaging Library (Fork) installed + +## Usage + +``` +python VmaDumpVis.py -o OUTPUT_FILE INPUT_FILE +``` + +* `INPUT_FILE` - path to source file to be read, containing dump of internal state of the VMA library in JSON format (encoding: UTF-8), generated using `vmaBuildStatsString()` function. +* `OUTPUT_FILE` - path to destination file to be written that will contain generated image. Image format is automatically recognized based on file extension. List of supported formats can be found [here](http://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html) and includes: BMP, GIF, JPEG, PNG, TGA. + +You can also use typical options: + +* `-h` - to see help on command line syntax +* `-v` - to see program version number + +## Example output + + + +## Legend + +*  Light gray without border - a space in Vulkan device memory block unused by any allocation. +*  Buffer with usage containing INDIRECT_BUFFER, VERTEX_BUFFER, or INDEX_BUFFER. +*  Buffer with usage containing STORAGE_BUFFER or STORAGE_TEXEL_BUFFER. +*  Buffer with usage containing UNIFORM_BUFFER or UNIFORM_TEXEL_BUFFER. +*  Other buffer. +*  Image with OPTIMAL tiling and usage containing DEPTH_STENCIL_ATTACHMENT. +*  Image with OPTIMAL tiling and usage containing INPUT_ATTACHMENT, TRANSIENT_ATTACHMENT, or COLOR_ATTACHMENT. +*  Image with OPTIMAL tiling and usage containing SAMPLED. +*  Other image with OPTIMAL tiling. +*  Image with LINEAR tiling. +*  Image with tiling unknown to the allocator. +*  Allocation of unknown type. +*  Black bar - one or more allocations of any kind too small to be visualized as filled rectangles.
diff --git a/tools/VmaDumpVis/Sample.json b/tools/VmaDumpVis/Sample.json index 9b62658..34698a3 100644 --- a/tools/VmaDumpVis/Sample.json +++ b/tools/VmaDumpVis/Sample.json
@@ -1,102 +1,102 @@ -{ - "Total": { - "Blocks": 2, - "Allocations": 4, - "UnusedRanges": 3, - "UsedBytes": 8062124, - "UnusedBytes": 59046740, - "AllocationSize": {"Min": 60, "Avg": 2015531, "Max": 7995760}, - "UnusedRangeSize": {"Min": 64708, "Avg": 19682247, "Max": 33554432} - }, - "Heap 0": { - "Size": 8304721920, - "Flags": ["DEVICE_LOCAL"], - "Stats": { - "Blocks": 1, - "Allocations": 4, - "UnusedRanges": 2, - "UsedBytes": 8062124, - "UnusedBytes": 25492308, - "AllocationSize": {"Min": 60, "Avg": 2015531, "Max": 7995760}, - "UnusedRangeSize": {"Min": 64708, "Avg": 12746154, "Max": 25427600} - }, - "Type 0": { - "Flags": ["DEVICE_LOCAL"], - "Stats": { - "Blocks": 1, - "Allocations": 4, - "UnusedRanges": 2, - "UsedBytes": 8062124, - "UnusedBytes": 25492308, - "AllocationSize": {"Min": 60, "Avg": 2015531, "Max": 7995760}, - "UnusedRangeSize": {"Min": 64708, "Avg": 12746154, "Max": 25427600} - } - } - }, - "Heap 1": { - "Size": 8285323264, - "Flags": [], - "Stats": { - "Blocks": 1, - "Allocations": 0, - "UnusedRanges": 1, - "UsedBytes": 0, - "UnusedBytes": 33554432 - }, - "Type 1": { - "Flags": ["HOST_VISIBLE", "HOST_COHERENT"], - "Stats": { - "Blocks": 1, - "Allocations": 0, - "UnusedRanges": 1, - "UsedBytes": 0, - "UnusedBytes": 33554432 - } - }, - "Type 3": { - "Flags": ["HOST_VISIBLE", "HOST_COHERENT", "HOST_CACHED"] - } - }, - "Heap 2": { - "Size": 268435456, - "Flags": ["DEVICE_LOCAL"], - "Type 2": { - "Flags": ["DEVICE_LOCAL", "HOST_VISIBLE", "HOST_COHERENT"] - } - }, - "DefaultPools": { - "Type 0": { - "PreferredBlockSize": 268435456, - "Blocks": { - "0": { - "TotalBytes": 33554432, - "UnusedBytes": 25492308, - "Allocations": 4, - "UnusedRanges": 2, - "Suballocations": [ - {"Offset": 0, "Type": "IMAGE_OPTIMAL", "Size": 65536, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 6}, - {"Offset": 65536, "Type": "BUFFER", "Size": 768, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 130}, - {"Offset": 66304, "Type": "BUFFER", "Size": 60, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 66}, - {"Offset": 66364, "Type": "FREE", "Size": 64708}, - {"Offset": 131072, "Type": "IMAGE_OPTIMAL", "Size": 7995760, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 32}, - {"Offset": 8126832, "Type": "FREE", "Size": 25427600} - ] - } - } - }, - "Type 1": { - "PreferredBlockSize": 268435456, - "Blocks": { - "0": { - "TotalBytes": 33554432, - "UnusedBytes": 33554432, - "Allocations": 0, - "UnusedRanges": 1, - "Suballocations": [ - {"Offset": 0, "Type": "FREE", "Size": 33554432} - ] - } - } - } - } +{ + "Total": { + "Blocks": 2, + "Allocations": 4, + "UnusedRanges": 3, + "UsedBytes": 8062124, + "UnusedBytes": 59046740, + "AllocationSize": {"Min": 60, "Avg": 2015531, "Max": 7995760}, + "UnusedRangeSize": {"Min": 64708, "Avg": 19682247, "Max": 33554432} + }, + "Heap 0": { + "Size": 8304721920, + "Flags": ["DEVICE_LOCAL"], + "Stats": { + "Blocks": 1, + "Allocations": 4, + "UnusedRanges": 2, + "UsedBytes": 8062124, + "UnusedBytes": 25492308, + "AllocationSize": {"Min": 60, "Avg": 2015531, "Max": 7995760}, + "UnusedRangeSize": {"Min": 64708, "Avg": 12746154, "Max": 25427600} + }, + "Type 0": { + "Flags": ["DEVICE_LOCAL"], + "Stats": { + "Blocks": 1, + "Allocations": 4, + "UnusedRanges": 2, + "UsedBytes": 8062124, + "UnusedBytes": 25492308, + "AllocationSize": {"Min": 60, "Avg": 2015531, "Max": 7995760}, + "UnusedRangeSize": {"Min": 64708, "Avg": 12746154, "Max": 25427600} + } + } + }, + "Heap 1": { + "Size": 8285323264, + "Flags": [], + "Stats": { + "Blocks": 1, + "Allocations": 0, + "UnusedRanges": 1, + "UsedBytes": 0, + "UnusedBytes": 33554432 + }, + "Type 1": { + "Flags": ["HOST_VISIBLE", "HOST_COHERENT"], + "Stats": { + "Blocks": 1, + "Allocations": 0, + "UnusedRanges": 1, + "UsedBytes": 0, + "UnusedBytes": 33554432 + } + }, + "Type 3": { + "Flags": ["HOST_VISIBLE", "HOST_COHERENT", "HOST_CACHED"] + } + }, + "Heap 2": { + "Size": 268435456, + "Flags": ["DEVICE_LOCAL"], + "Type 2": { + "Flags": ["DEVICE_LOCAL", "HOST_VISIBLE", "HOST_COHERENT"] + } + }, + "DefaultPools": { + "Type 0": { + "PreferredBlockSize": 268435456, + "Blocks": { + "0": { + "TotalBytes": 33554432, + "UnusedBytes": 25492308, + "Allocations": 4, + "UnusedRanges": 2, + "Suballocations": [ + {"Offset": 0, "Type": "IMAGE_OPTIMAL", "Size": 65536, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 6}, + {"Offset": 65536, "Type": "BUFFER", "Size": 768, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 130}, + {"Offset": 66304, "Type": "BUFFER", "Size": 60, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 66}, + {"Offset": 66364, "Type": "FREE", "Size": 64708}, + {"Offset": 131072, "Type": "IMAGE_OPTIMAL", "Size": 7995760, "CreationFrameIndex": 0, "LastUseFrameIndex": 0, "Usage": 32}, + {"Offset": 8126832, "Type": "FREE", "Size": 25427600} + ] + } + } + }, + "Type 1": { + "PreferredBlockSize": 268435456, + "Blocks": { + "0": { + "TotalBytes": 33554432, + "UnusedBytes": 33554432, + "Allocations": 0, + "UnusedRanges": 1, + "Suballocations": [ + {"Offset": 0, "Type": "FREE", "Size": 33554432} + ] + } + } + } + } } \ No newline at end of file
diff --git a/tools/VmaDumpVis/VmaDumpVis.py b/tools/VmaDumpVis/VmaDumpVis.py index 6b870ab..ee32005 100644 --- a/tools/VmaDumpVis/VmaDumpVis.py +++ b/tools/VmaDumpVis/VmaDumpVis.py
@@ -1,305 +1,305 @@ -# -# Copyright (c) 2018-2021 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. -# - -import argparse -import json -from PIL import Image, ImageDraw, ImageFont - - -PROGRAM_VERSION = 'VMA Dump Visualization 2.0.1' -IMG_SIZE_X = 1200 -IMG_MARGIN = 8 -FONT_SIZE = 10 -MAP_SIZE = 24 -COLOR_TEXT_H1 = (0, 0, 0, 255) -COLOR_TEXT_H2 = (150, 150, 150, 255) -COLOR_OUTLINE = (155, 155, 155, 255) -COLOR_OUTLINE_HARD = (0, 0, 0, 255) -COLOR_GRID_LINE = (224, 224, 224, 255) - - -argParser = argparse.ArgumentParser(description='Visualization of Vulkan Memory Allocator JSON dump.') -argParser.add_argument('DumpFile', type=argparse.FileType(mode='r', encoding='UTF-8'), help='Path to source JSON file with memory dump created by Vulkan Memory Allocator library') -argParser.add_argument('-v', '--version', action='version', version=PROGRAM_VERSION) -argParser.add_argument('-o', '--output', required=True, help='Path to destination image file (e.g. PNG)') -args = argParser.parse_args() - -data = {} - - -def ProcessBlock(dstBlockList, iBlockId, objBlock, sAlgorithm): - iBlockSize = int(objBlock['TotalBytes']) - arrSuballocs = objBlock['Suballocations'] - dstBlockObj = {'ID': iBlockId, 'Size':iBlockSize, 'Suballocations':[]} - dstBlockObj['Algorithm'] = sAlgorithm - for objSuballoc in arrSuballocs: - dstBlockObj['Suballocations'].append((objSuballoc['Type'], int(objSuballoc['Size']), int(objSuballoc['Usage']) if ('Usage' in objSuballoc) else 0)) - dstBlockList.append(dstBlockObj) - - -def GetDataForMemoryType(iMemTypeIndex): - global data - if iMemTypeIndex in data: - return data[iMemTypeIndex] - else: - newMemTypeData = {'DedicatedAllocations':[], 'DefaultPoolBlocks':[], 'CustomPools':{}} - data[iMemTypeIndex] = newMemTypeData - return newMemTypeData - - -def IsDataEmpty(): - global data - for dictMemType in data.values(): - if 'DedicatedAllocations' in dictMemType and len(dictMemType['DedicatedAllocations']) > 0: - return False - if 'DefaultPoolBlocks' in dictMemType and len(dictMemType['DefaultPoolBlocks']) > 0: - return False - if 'CustomPools' in dictMemType: - for lBlockList in dictMemType['CustomPools'].values(): - if len(lBlockList) > 0: - return False - return True - - -# Returns tuple: -# [0] image height : integer -# [1] pixels per byte : float -def CalcParams(): - global data - iImgSizeY = IMG_MARGIN - iImgSizeY += FONT_SIZE + IMG_MARGIN # Grid lines legend - sizes - iMaxBlockSize = 0 - for dictMemType in data.values(): - iImgSizeY += IMG_MARGIN + FONT_SIZE - lDedicatedAllocations = dictMemType['DedicatedAllocations'] - iImgSizeY += len(lDedicatedAllocations) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) - for tDedicatedAlloc in lDedicatedAllocations: - iMaxBlockSize = max(iMaxBlockSize, tDedicatedAlloc[1]) - lDefaultPoolBlocks = dictMemType['DefaultPoolBlocks'] - iImgSizeY += len(lDefaultPoolBlocks) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) - for objBlock in lDefaultPoolBlocks: - iMaxBlockSize = max(iMaxBlockSize, objBlock['Size']) - dCustomPools = dictMemType['CustomPools'] - for lBlocks in dCustomPools.values(): - iImgSizeY += len(lBlocks) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) - for objBlock in lBlocks: - iMaxBlockSize = max(iMaxBlockSize, objBlock['Size']) - fPixelsPerByte = (IMG_SIZE_X - IMG_MARGIN * 2) / float(iMaxBlockSize) - return iImgSizeY, fPixelsPerByte - - -def TypeToColor(sType, iUsage): - if sType == 'FREE': - return 220, 220, 220, 255 - elif sType == 'BUFFER': - if (iUsage & 0x1C0) != 0: # INDIRECT_BUFFER | VERTEX_BUFFER | INDEX_BUFFER - return 255, 148, 148, 255 # Red - elif (iUsage & 0x28) != 0: # STORAGE_BUFFER | STORAGE_TEXEL_BUFFER - return 255, 187, 121, 255 # Orange - elif (iUsage & 0x14) != 0: # UNIFORM_BUFFER | UNIFORM_TEXEL_BUFFER - return 255, 255, 0, 255 # Yellow - else: - return 255, 255, 165, 255 # Light yellow - elif sType == 'IMAGE_OPTIMAL': - if (iUsage & 0x20) != 0: # DEPTH_STENCIL_ATTACHMENT - return 246, 128, 255, 255 # Pink - elif (iUsage & 0xD0) != 0: # INPUT_ATTACHMENT | TRANSIENT_ATTACHMENT | COLOR_ATTACHMENT - return 179, 179, 255, 255 # Blue - elif (iUsage & 0x4) != 0: # SAMPLED - return 0, 255, 255, 255 # Aqua - else: - return 183, 255, 255, 255 # Light aqua - elif sType == 'IMAGE_LINEAR': - return 0, 255, 0, 255 # Green - elif sType == 'IMAGE_UNKNOWN': - return 0, 255, 164, 255 # Green/aqua - elif sType == 'UNKNOWN': - return 175, 175, 175, 255 # Gray - assert False - return 0, 0, 0, 255 - - -def DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc): - global fPixelsPerByte - iSizeBytes = tDedicatedAlloc[1] - iSizePixels = int(iSizeBytes * fPixelsPerByte) - draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor(tDedicatedAlloc[0], tDedicatedAlloc[2]), outline=COLOR_OUTLINE) - - -def DrawBlock(draw, y, objBlock): - global fPixelsPerByte - iSizeBytes = objBlock['Size'] - iSizePixels = int(iSizeBytes * fPixelsPerByte) - draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor('FREE', 0), outline=None) - iByte = 0 - iX = 0 - iLastHardLineX = -1 - for tSuballoc in objBlock['Suballocations']: - sType = tSuballoc[0] - iByteEnd = iByte + tSuballoc[1] - iXEnd = int(iByteEnd * fPixelsPerByte) - if sType != 'FREE': - if iXEnd > iX + 1: - iUsage = tSuballoc[2] - draw.rectangle([IMG_MARGIN + iX, y, IMG_MARGIN + iXEnd, y + MAP_SIZE], fill=TypeToColor(sType, iUsage), outline=COLOR_OUTLINE) - # Hard line was been overwritten by rectangle outline: redraw it. - if iLastHardLineX == iX: - draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD) - else: - draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD) - iLastHardLineX = iX - iByte = iByteEnd - iX = iXEnd - - -def BytesToStr(iBytes): - if iBytes < 1024: - return "%d B" % iBytes - iBytes /= 1024 - if iBytes < 1024: - return "%d KiB" % iBytes - iBytes /= 1024 - if iBytes < 1024: - return "%d MiB" % iBytes - iBytes /= 1024 - return "%d GiB" % iBytes - - -jsonSrc = json.load(args.DumpFile) -if 'DedicatedAllocations' in jsonSrc: - for tType in jsonSrc['DedicatedAllocations'].items(): - sType = tType[0] - assert sType[:5] == 'Type ' - iType = int(sType[5:]) - typeData = GetDataForMemoryType(iType) - for objAlloc in tType[1]: - typeData['DedicatedAllocations'].append((objAlloc['Type'], int(objAlloc['Size']), int(objAlloc['Usage']) if ('Usage' in objAlloc) else 0)) -if 'DefaultPools' in jsonSrc: - for tType in jsonSrc['DefaultPools'].items(): - sType = tType[0] - assert sType[:5] == 'Type ' - iType = int(sType[5:]) - typeData = GetDataForMemoryType(iType) - for sBlockId, objBlock in tType[1]['Blocks'].items(): - ProcessBlock(typeData['DefaultPoolBlocks'], int(sBlockId), objBlock, '') -if 'Pools' in jsonSrc: - objPools = jsonSrc['Pools'] - for sPoolId, objPool in objPools.items(): - iType = int(objPool['MemoryTypeIndex']) - typeData = GetDataForMemoryType(iType) - objBlocks = objPool['Blocks'] - sAlgorithm = objPool.get('Algorithm', '') - sName = objPool.get('Name', None) - if sName: - sFullName = sPoolId + ' "' + sName + '"' - else: - sFullName = sPoolId - dstBlockArray = [] - typeData['CustomPools'][sFullName] = dstBlockArray - for sBlockId, objBlock in objBlocks.items(): - ProcessBlock(dstBlockArray, int(sBlockId), objBlock, sAlgorithm) - -if IsDataEmpty(): - print("There is nothing to put on the image. Please make sure you generated the stats string with detailed map enabled.") - exit(1) - -iImgSizeY, fPixelsPerByte = CalcParams() - -img = Image.new('RGB', (IMG_SIZE_X, iImgSizeY), 'white') -draw = ImageDraw.Draw(img) - -try: - font = ImageFont.truetype('segoeuib.ttf') -except: - font = ImageFont.load_default() - -y = IMG_MARGIN - -# Draw grid lines -iBytesBetweenGridLines = 32 -while iBytesBetweenGridLines * fPixelsPerByte < 64: - iBytesBetweenGridLines *= 2 -iByte = 0 -TEXT_MARGIN = 4 -while True: - iX = int(iByte * fPixelsPerByte) - if iX > IMG_SIZE_X - 2 * IMG_MARGIN: - break - draw.line([iX + IMG_MARGIN, 0, iX + IMG_MARGIN, iImgSizeY], fill=COLOR_GRID_LINE) - if iByte == 0: - draw.text((iX + IMG_MARGIN + TEXT_MARGIN, y), "0", fill=COLOR_TEXT_H2, font=font) - else: - text = BytesToStr(iByte) - textSize = draw.textsize(text, font=font) - draw.text((iX + IMG_MARGIN - textSize[0] - TEXT_MARGIN, y), text, fill=COLOR_TEXT_H2, font=font) - iByte += iBytesBetweenGridLines -y += FONT_SIZE + IMG_MARGIN - -# Draw main content -for iMemTypeIndex in sorted(data.keys()): - dictMemType = data[iMemTypeIndex] - draw.text((IMG_MARGIN, y), "Memory type %d" % iMemTypeIndex, fill=COLOR_TEXT_H1, font=font) - y += FONT_SIZE + IMG_MARGIN - index = 0 - for tDedicatedAlloc in dictMemType['DedicatedAllocations']: - draw.text((IMG_MARGIN, y), "Dedicated allocation %d" % index, fill=COLOR_TEXT_H2, font=font) - y += FONT_SIZE + IMG_MARGIN - DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc) - y += MAP_SIZE + IMG_MARGIN - index += 1 - for objBlock in dictMemType['DefaultPoolBlocks']: - draw.text((IMG_MARGIN, y), "Default pool block %d" % objBlock['ID'], fill=COLOR_TEXT_H2, font=font) - y += FONT_SIZE + IMG_MARGIN - DrawBlock(draw, y, objBlock) - y += MAP_SIZE + IMG_MARGIN - index = 0 - for sPoolName, listPool in dictMemType['CustomPools'].items(): - for objBlock in listPool: - if 'Algorithm' in objBlock and objBlock['Algorithm']: - sAlgorithm = ' (Algorithm: %s)' % (objBlock['Algorithm']) - else: - sAlgorithm = '' - draw.text((IMG_MARGIN, y), "Custom pool %s%s block %d" % (sPoolName, sAlgorithm, objBlock['ID']), fill=COLOR_TEXT_H2, font=font) - y += FONT_SIZE + IMG_MARGIN - DrawBlock(draw, y, objBlock) - y += MAP_SIZE + IMG_MARGIN - index += 1 -del draw -img.save(args.output) - -""" -Main data structure - variable `data` - is a dictionary. Key is integer - memory type index. Value is dictionary of: -- Fixed key 'DedicatedAllocations'. Value is list of tuples, each containing: - - [0]: Type : string - - [1]: Size : integer - - [2]: Usage : integer (0 if unknown) -- Fixed key 'DefaultPoolBlocks'. Value is list of objects, each containing dictionary with: - - Fixed key 'ID'. Value is int. - - Fixed key 'Size'. Value is int. - - Fixed key 'Suballocations'. Value is list of tuples as above. -- Fixed key 'CustomPools'. Value is dictionary. - - Key is string with pool ID/name. Value is list of objects representing memory blocks, each containing dictionary with: - - Fixed key 'ID'. Value is int. - - Fixed key 'Size'. Value is int. - - Fixed key 'Algorithm'. Optional. Value is string. - - Fixed key 'Suballocations'. Value is list of tuples as above. -""" +# +# Copyright (c) 2018-2021 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. +# + +import argparse +import json +from PIL import Image, ImageDraw, ImageFont + + +PROGRAM_VERSION = 'VMA Dump Visualization 2.0.1' +IMG_SIZE_X = 1200 +IMG_MARGIN = 8 +FONT_SIZE = 10 +MAP_SIZE = 24 +COLOR_TEXT_H1 = (0, 0, 0, 255) +COLOR_TEXT_H2 = (150, 150, 150, 255) +COLOR_OUTLINE = (155, 155, 155, 255) +COLOR_OUTLINE_HARD = (0, 0, 0, 255) +COLOR_GRID_LINE = (224, 224, 224, 255) + + +argParser = argparse.ArgumentParser(description='Visualization of Vulkan Memory Allocator JSON dump.') +argParser.add_argument('DumpFile', type=argparse.FileType(mode='r', encoding='UTF-8'), help='Path to source JSON file with memory dump created by Vulkan Memory Allocator library') +argParser.add_argument('-v', '--version', action='version', version=PROGRAM_VERSION) +argParser.add_argument('-o', '--output', required=True, help='Path to destination image file (e.g. PNG)') +args = argParser.parse_args() + +data = {} + + +def ProcessBlock(dstBlockList, iBlockId, objBlock, sAlgorithm): + iBlockSize = int(objBlock['TotalBytes']) + arrSuballocs = objBlock['Suballocations'] + dstBlockObj = {'ID': iBlockId, 'Size':iBlockSize, 'Suballocations':[]} + dstBlockObj['Algorithm'] = sAlgorithm + for objSuballoc in arrSuballocs: + dstBlockObj['Suballocations'].append((objSuballoc['Type'], int(objSuballoc['Size']), int(objSuballoc['Usage']) if ('Usage' in objSuballoc) else 0)) + dstBlockList.append(dstBlockObj) + + +def GetDataForMemoryType(iMemTypeIndex): + global data + if iMemTypeIndex in data: + return data[iMemTypeIndex] + else: + newMemTypeData = {'DedicatedAllocations':[], 'DefaultPoolBlocks':[], 'CustomPools':{}} + data[iMemTypeIndex] = newMemTypeData + return newMemTypeData + + +def IsDataEmpty(): + global data + for dictMemType in data.values(): + if 'DedicatedAllocations' in dictMemType and len(dictMemType['DedicatedAllocations']) > 0: + return False + if 'DefaultPoolBlocks' in dictMemType and len(dictMemType['DefaultPoolBlocks']) > 0: + return False + if 'CustomPools' in dictMemType: + for lBlockList in dictMemType['CustomPools'].values(): + if len(lBlockList) > 0: + return False + return True + + +# Returns tuple: +# [0] image height : integer +# [1] pixels per byte : float +def CalcParams(): + global data + iImgSizeY = IMG_MARGIN + iImgSizeY += FONT_SIZE + IMG_MARGIN # Grid lines legend - sizes + iMaxBlockSize = 0 + for dictMemType in data.values(): + iImgSizeY += IMG_MARGIN + FONT_SIZE + lDedicatedAllocations = dictMemType['DedicatedAllocations'] + iImgSizeY += len(lDedicatedAllocations) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) + for tDedicatedAlloc in lDedicatedAllocations: + iMaxBlockSize = max(iMaxBlockSize, tDedicatedAlloc[1]) + lDefaultPoolBlocks = dictMemType['DefaultPoolBlocks'] + iImgSizeY += len(lDefaultPoolBlocks) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) + for objBlock in lDefaultPoolBlocks: + iMaxBlockSize = max(iMaxBlockSize, objBlock['Size']) + dCustomPools = dictMemType['CustomPools'] + for lBlocks in dCustomPools.values(): + iImgSizeY += len(lBlocks) * (IMG_MARGIN * 2 + FONT_SIZE + MAP_SIZE) + for objBlock in lBlocks: + iMaxBlockSize = max(iMaxBlockSize, objBlock['Size']) + fPixelsPerByte = (IMG_SIZE_X - IMG_MARGIN * 2) / float(iMaxBlockSize) + return iImgSizeY, fPixelsPerByte + + +def TypeToColor(sType, iUsage): + if sType == 'FREE': + return 220, 220, 220, 255 + elif sType == 'BUFFER': + if (iUsage & 0x1C0) != 0: # INDIRECT_BUFFER | VERTEX_BUFFER | INDEX_BUFFER + return 255, 148, 148, 255 # Red + elif (iUsage & 0x28) != 0: # STORAGE_BUFFER | STORAGE_TEXEL_BUFFER + return 255, 187, 121, 255 # Orange + elif (iUsage & 0x14) != 0: # UNIFORM_BUFFER | UNIFORM_TEXEL_BUFFER + return 255, 255, 0, 255 # Yellow + else: + return 255, 255, 165, 255 # Light yellow + elif sType == 'IMAGE_OPTIMAL': + if (iUsage & 0x20) != 0: # DEPTH_STENCIL_ATTACHMENT + return 246, 128, 255, 255 # Pink + elif (iUsage & 0xD0) != 0: # INPUT_ATTACHMENT | TRANSIENT_ATTACHMENT | COLOR_ATTACHMENT + return 179, 179, 255, 255 # Blue + elif (iUsage & 0x4) != 0: # SAMPLED + return 0, 255, 255, 255 # Aqua + else: + return 183, 255, 255, 255 # Light aqua + elif sType == 'IMAGE_LINEAR': + return 0, 255, 0, 255 # Green + elif sType == 'IMAGE_UNKNOWN': + return 0, 255, 164, 255 # Green/aqua + elif sType == 'UNKNOWN': + return 175, 175, 175, 255 # Gray + assert False + return 0, 0, 0, 255 + + +def DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc): + global fPixelsPerByte + iSizeBytes = tDedicatedAlloc[1] + iSizePixels = int(iSizeBytes * fPixelsPerByte) + draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor(tDedicatedAlloc[0], tDedicatedAlloc[2]), outline=COLOR_OUTLINE) + + +def DrawBlock(draw, y, objBlock): + global fPixelsPerByte + iSizeBytes = objBlock['Size'] + iSizePixels = int(iSizeBytes * fPixelsPerByte) + draw.rectangle([IMG_MARGIN, y, IMG_MARGIN + iSizePixels, y + MAP_SIZE], fill=TypeToColor('FREE', 0), outline=None) + iByte = 0 + iX = 0 + iLastHardLineX = -1 + for tSuballoc in objBlock['Suballocations']: + sType = tSuballoc[0] + iByteEnd = iByte + tSuballoc[1] + iXEnd = int(iByteEnd * fPixelsPerByte) + if sType != 'FREE': + if iXEnd > iX + 1: + iUsage = tSuballoc[2] + draw.rectangle([IMG_MARGIN + iX, y, IMG_MARGIN + iXEnd, y + MAP_SIZE], fill=TypeToColor(sType, iUsage), outline=COLOR_OUTLINE) + # Hard line was been overwritten by rectangle outline: redraw it. + if iLastHardLineX == iX: + draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD) + else: + draw.line([IMG_MARGIN + iX, y, IMG_MARGIN + iX, y + MAP_SIZE], fill=COLOR_OUTLINE_HARD) + iLastHardLineX = iX + iByte = iByteEnd + iX = iXEnd + + +def BytesToStr(iBytes): + if iBytes < 1024: + return "%d B" % iBytes + iBytes /= 1024 + if iBytes < 1024: + return "%d KiB" % iBytes + iBytes /= 1024 + if iBytes < 1024: + return "%d MiB" % iBytes + iBytes /= 1024 + return "%d GiB" % iBytes + + +jsonSrc = json.load(args.DumpFile) +if 'DedicatedAllocations' in jsonSrc: + for tType in jsonSrc['DedicatedAllocations'].items(): + sType = tType[0] + assert sType[:5] == 'Type ' + iType = int(sType[5:]) + typeData = GetDataForMemoryType(iType) + for objAlloc in tType[1]: + typeData['DedicatedAllocations'].append((objAlloc['Type'], int(objAlloc['Size']), int(objAlloc['Usage']) if ('Usage' in objAlloc) else 0)) +if 'DefaultPools' in jsonSrc: + for tType in jsonSrc['DefaultPools'].items(): + sType = tType[0] + assert sType[:5] == 'Type ' + iType = int(sType[5:]) + typeData = GetDataForMemoryType(iType) + for sBlockId, objBlock in tType[1]['Blocks'].items(): + ProcessBlock(typeData['DefaultPoolBlocks'], int(sBlockId), objBlock, '') +if 'Pools' in jsonSrc: + objPools = jsonSrc['Pools'] + for sPoolId, objPool in objPools.items(): + iType = int(objPool['MemoryTypeIndex']) + typeData = GetDataForMemoryType(iType) + objBlocks = objPool['Blocks'] + sAlgorithm = objPool.get('Algorithm', '') + sName = objPool.get('Name', None) + if sName: + sFullName = sPoolId + ' "' + sName + '"' + else: + sFullName = sPoolId + dstBlockArray = [] + typeData['CustomPools'][sFullName] = dstBlockArray + for sBlockId, objBlock in objBlocks.items(): + ProcessBlock(dstBlockArray, int(sBlockId), objBlock, sAlgorithm) + +if IsDataEmpty(): + print("There is nothing to put on the image. Please make sure you generated the stats string with detailed map enabled.") + exit(1) + +iImgSizeY, fPixelsPerByte = CalcParams() + +img = Image.new('RGB', (IMG_SIZE_X, iImgSizeY), 'white') +draw = ImageDraw.Draw(img) + +try: + font = ImageFont.truetype('segoeuib.ttf') +except: + font = ImageFont.load_default() + +y = IMG_MARGIN + +# Draw grid lines +iBytesBetweenGridLines = 32 +while iBytesBetweenGridLines * fPixelsPerByte < 64: + iBytesBetweenGridLines *= 2 +iByte = 0 +TEXT_MARGIN = 4 +while True: + iX = int(iByte * fPixelsPerByte) + if iX > IMG_SIZE_X - 2 * IMG_MARGIN: + break + draw.line([iX + IMG_MARGIN, 0, iX + IMG_MARGIN, iImgSizeY], fill=COLOR_GRID_LINE) + if iByte == 0: + draw.text((iX + IMG_MARGIN + TEXT_MARGIN, y), "0", fill=COLOR_TEXT_H2, font=font) + else: + text = BytesToStr(iByte) + textSize = draw.textsize(text, font=font) + draw.text((iX + IMG_MARGIN - textSize[0] - TEXT_MARGIN, y), text, fill=COLOR_TEXT_H2, font=font) + iByte += iBytesBetweenGridLines +y += FONT_SIZE + IMG_MARGIN + +# Draw main content +for iMemTypeIndex in sorted(data.keys()): + dictMemType = data[iMemTypeIndex] + draw.text((IMG_MARGIN, y), "Memory type %d" % iMemTypeIndex, fill=COLOR_TEXT_H1, font=font) + y += FONT_SIZE + IMG_MARGIN + index = 0 + for tDedicatedAlloc in dictMemType['DedicatedAllocations']: + draw.text((IMG_MARGIN, y), "Dedicated allocation %d" % index, fill=COLOR_TEXT_H2, font=font) + y += FONT_SIZE + IMG_MARGIN + DrawDedicatedAllocationBlock(draw, y, tDedicatedAlloc) + y += MAP_SIZE + IMG_MARGIN + index += 1 + for objBlock in dictMemType['DefaultPoolBlocks']: + draw.text((IMG_MARGIN, y), "Default pool block %d" % objBlock['ID'], fill=COLOR_TEXT_H2, font=font) + y += FONT_SIZE + IMG_MARGIN + DrawBlock(draw, y, objBlock) + y += MAP_SIZE + IMG_MARGIN + index = 0 + for sPoolName, listPool in dictMemType['CustomPools'].items(): + for objBlock in listPool: + if 'Algorithm' in objBlock and objBlock['Algorithm']: + sAlgorithm = ' (Algorithm: %s)' % (objBlock['Algorithm']) + else: + sAlgorithm = '' + draw.text((IMG_MARGIN, y), "Custom pool %s%s block %d" % (sPoolName, sAlgorithm, objBlock['ID']), fill=COLOR_TEXT_H2, font=font) + y += FONT_SIZE + IMG_MARGIN + DrawBlock(draw, y, objBlock) + y += MAP_SIZE + IMG_MARGIN + index += 1 +del draw +img.save(args.output) + +""" +Main data structure - variable `data` - is a dictionary. Key is integer - memory type index. Value is dictionary of: +- Fixed key 'DedicatedAllocations'. Value is list of tuples, each containing: + - [0]: Type : string + - [1]: Size : integer + - [2]: Usage : integer (0 if unknown) +- Fixed key 'DefaultPoolBlocks'. Value is list of objects, each containing dictionary with: + - Fixed key 'ID'. Value is int. + - Fixed key 'Size'. Value is int. + - Fixed key 'Suballocations'. Value is list of tuples as above. +- Fixed key 'CustomPools'. Value is dictionary. + - Key is string with pool ID/name. Value is list of objects representing memory blocks, each containing dictionary with: + - Fixed key 'ID'. Value is int. + - Fixed key 'Size'. Value is int. + - Fixed key 'Algorithm'. Optional. Value is string. + - Fixed key 'Suballocations'. Value is list of tuples as above. +"""